cloudstack/thirdparty/console/ConsoleCanvas.java
2011-01-28 16:07:46 -08:00

1977 lines
54 KiB
Java

//
// Copyright (C) 2004 Horizon Wimba. All Rights Reserved.
// Copyright (C) 2001-2003 HorizonLive.com, Inc. All Rights Reserved.
// Copyright (C) 2001,2002 Constantin Kaplinsky. All Rights Reserved.
// Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
//
// This is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This software is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this software; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
//
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.lang.*;
import java.util.zip.*;
//
// VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
//
class ConsoleCanvas extends Canvas
implements KeyListener, MouseListener, MouseMotionListener {
ConsoleViewer viewer;
RfbProto rfb;
ColorModel cm8, cm24;
Color[] colors;
int bytesPixel;
int maxWidth = 0, maxHeight = 0;
int scalingFactor;
int scaledWidth, scaledHeight;
Image memImage;
Graphics memGraphics;
Image rawPixelsImage;
MemoryImageSource pixelsSource;
byte[] pixels8;
int[] pixels24;
// ZRLE encoder's data.
byte[] zrleBuf;
int zrleBufLen = 0;
byte[] zrleTilePixels8;
int[] zrleTilePixels24;
ZlibInStream zrleInStream;
boolean zrleRecWarningShown = false;
// Zlib encoder's data.
byte[] zlibBuf;
int zlibBufLen = 0;
Inflater zlibInflater;
// Tight encoder's data.
final static int tightZlibBufferSize = 512;
Inflater[] tightInflaters;
// Since JPEG images are loaded asynchronously, we have to remember
// their position in the framebuffer. Also, this jpegRect object is
// used for synchronization between the rfbThread and a JVM's thread
// which decodes and loads JPEG images.
Rectangle jpegRect;
// True if we process keyboard and mouse events.
boolean inputEnabled;
//
// The constructors.
//
public ConsoleCanvas(ConsoleViewer v, int maxWidth_, int maxHeight_)
{
viewer = v;
maxWidth = maxWidth_;
maxHeight = maxHeight_;
//Sheng: Moved to VncViewer.java
rfb = viewer.rfb;
scalingFactor = viewer.options.scalingFactor;
tightInflaters = new Inflater[4];
cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
colors = new Color[256];
for (int i = 0; i < 256; i++)
colors[i] = new Color(cm8.getRGB(i));
/*
Logger.log(Logger.INFO, "Set size to 800x600 3");
memImage = viewer.vncContainer.createImage(800, 600);
memGraphics = memImage.getGraphics();
setSize(800,600);
*/
// setPixelFormat(); //Sheng: Moved to VncViewer
inputEnabled = false;
if (!viewer.options.viewOnly)
enableInput(true);
// Keyboard listener is enabled even in view-only mode, to catch
// 'r' or 'R' key presses used to request screen update.
addKeyListener(this);
}
public ConsoleCanvas(ConsoleViewer v) throws IOException {
this(v, 0, 0);
}
//
// Callback methods to determine geometry of our Component.
//
public Dimension getPreferredSize() {
return new Dimension(scaledWidth, scaledHeight);
}
public Dimension getMinimumSize() {
return new Dimension(scaledWidth, scaledHeight);
}
public Dimension getMaximumSize() {
return new Dimension(scaledWidth, scaledHeight);
}
//
// All painting is performed here.
//
public void update(Graphics g) {
paint(g);
}
public void paint(Graphics g) {
// Logger.log(Logger.INFO, "VncCanvas.paint called g=" + g.hashCode() + g +
// " memImage=" + memImage);
if (rfb == null) {
return;
}
synchronized (memImage) {
if (rfb.framebufferWidth == scaledWidth) {
g.drawImage(memImage, 0, 0, null);
} else {
paintScaledFrameBuffer(g);
}
}
if (showSoftCursor) {
int x0 = cursorX - hotX, y0 = cursorY - hotY;
Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
if (r.intersects(g.getClipBounds())) {
g.drawImage(softCursor, x0, y0, null);
}
}
}
public void paintScaledFrameBuffer(Graphics g) {
g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
}
//
// Override the ImageObserver interface method to handle drawing of
// JPEG-encoded data.
//
public boolean imageUpdate(Image img, int infoflags,
int x, int y, int width, int height) {
if ((infoflags & (ALLBITS | ABORT)) == 0) {
return true; // We need more image data.
} else {
// If the whole image is available, draw it now.
if ((infoflags & ALLBITS) != 0) {
if (jpegRect != null) {
synchronized(jpegRect) {
memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
scheduleRepaint(jpegRect.x, jpegRect.y,
jpegRect.width, jpegRect.height);
jpegRect.notify();
}
}
}
return false; // All image data was processed.
}
}
//
// Start/stop receiving mouse events. Keyboard events are received
// even in view-only mode, because we want to map the 'r' key to the
// screen refreshing function.
//
public synchronized void enableInput(boolean enable) {
if (enable && !inputEnabled) {
inputEnabled = true;
addMouseListener(this);
addMouseMotionListener(this);
if (viewer.showControls) {
viewer.buttonPanel.enableRemoteAccessControls(true);
}
createSoftCursor(); // scaled cursor
} else if (!enable && inputEnabled) {
inputEnabled = false;
removeMouseListener(this);
removeMouseMotionListener(this);
if (viewer.showControls) {
viewer.buttonPanel.enableRemoteAccessControls(false);
}
createSoftCursor(); // non-scaled cursor
}
}
public void setPixelFormat() throws IOException {
if (viewer.options.eightBitColors) {
rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6);
bytesPixel = 1;
} else {
rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, 0);
bytesPixel = 4;
}
updateFramebufferSize();
}
void updateFramebufferSize() {
// Useful shortcuts.
int fbWidth = rfb.framebufferWidth;
int fbHeight = rfb.framebufferHeight;
// Calculate scaling factor for auto scaling.
if (maxWidth > 0 && maxHeight > 0) {
int f1 = maxWidth * 100 / fbWidth;
int f2 = maxHeight * 100 / fbHeight;
scalingFactor = Math.min(f1, f2);
if (scalingFactor > 100)
scalingFactor = 100;
Logger.log(Logger.INFO, "Scaling desktop at " + scalingFactor + "%");
}
// Update scaled framebuffer geometry.
scaledWidth = (fbWidth * scalingFactor + 50) / 100;
scaledHeight = (fbHeight * scalingFactor + 50) / 100;
// Create new off-screen image either if it does not exist, or if
// its geometry should be changed. It's not necessary to replace
// existing image if only pixel format should be changed.
if (memImage == null) {
// memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
memImage = new BufferedImage(fbWidth, fbHeight, BufferedImage.TYPE_4BYTE_ABGR);
memGraphics = memImage.getGraphics();
} else if (memImage.getWidth(null) != fbWidth ||
memImage.getHeight(null) != fbHeight) {
synchronized(memImage) {
// memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
memImage = new BufferedImage(fbWidth, fbHeight, BufferedImage.TYPE_4BYTE_ABGR);
memGraphics = memImage.getGraphics();
}
}
// Images with raw pixels should be re-allocated on every change
// of geometry or pixel format.
if (bytesPixel == 1) {
pixels24 = null;
pixels8 = new byte[fbWidth * fbHeight];
pixelsSource =
new MemoryImageSource(fbWidth, fbHeight, cm8, pixels8, 0, fbWidth);
zrleTilePixels24 = null;
zrleTilePixels8 = new byte[64 * 64];
} else {
pixels8 = null;
pixels24 = new int[fbWidth * fbHeight];
pixelsSource =
new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
zrleTilePixels8 = null;
zrleTilePixels24 = new int[64 * 64];
}
pixelsSource.setAnimated(true);
rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource);
if (!viewer.inProxyMode) {
// Update the size of desktop containers.
if (viewer.inSeparateFrame) {
if (viewer.desktopScrollPane != null)
resizeDesktopFrame();
} else {
setSize(scaledWidth, scaledHeight);
}
viewer.moveFocusToDesktop();
}
}
void resizeDesktopFrame() {
setSize(scaledWidth, scaledHeight);
// FIXME: Find a better way to determine correct size of a
// ScrollPane. -- const
Insets insets = viewer.desktopScrollPane.getInsets();
viewer.desktopScrollPane.setSize(scaledWidth +
2 * Math.min(insets.left, insets.right),
scaledHeight +
2 * Math.min(insets.top, insets.bottom));
viewer.vncFrame.pack();
// Try to limit the frame size to the screen size.
Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
Dimension frameSize = viewer.vncFrame.getSize();
Dimension newSize = frameSize;
// Reduce Screen Size by 30 pixels in each direction;
// This is a (poor) attempt to account for
// 1) Menu bar on Macintosh (should really also account for
// Dock on OSX). Usually 22px on top of screen.
// 2) Taxkbar on Windows (usually about 28 px on bottom)
// 3) Other obstructions.
screenSize.height -= 30;
screenSize.width -= 30;
boolean needToResizeFrame = false;
if (frameSize.height > screenSize.height) {
newSize.height = screenSize.height;
needToResizeFrame = true;
}
if (frameSize.width > screenSize.width) {
newSize.width = screenSize.width;
needToResizeFrame = true;
}
if (needToResizeFrame) {
viewer.vncFrame.setSize(newSize);
}
viewer.desktopScrollPane.doLayout();
}
void writeToClientStream(byte[] bs) {
// writeToClientStream swallows exceptions to make sure problems writing
// to client stream do not impact the main loop
if (viewer.clientStream != null) {
try {
viewer.lastUsedTime = System.currentTimeMillis();
synchronized (viewer.clientStream) {
viewer.clientStream.write(bs);
viewer.clientStream.flush();
}
} catch (IOException e) {
Logger.log(Logger.INFO, "Writing to client stream failed, ignored " + e);
Logger.log(Logger.DEBUG, e.toString(), e);
try {
viewer.clientStream.close();
} catch (IOException ioe){
// ignore
}
viewer.clientStream = null;
viewer.clientStreamInfo = null;
}
}
}
//
// processNormalProtocol() - executed by the rfbThread to deal with the
// RFB socket.
//
long timestamp = 0;
long bytes_sent_last = 0;
long rate_limit = 100000;
public void processNormalProtocol() throws Exception {
try {
timestamp = 0;
// main dispatch loop
while (true) {
processNormalProtocol2();
}
} catch (IOException e) {
// Server connection get disrupted.
// Send a special msg to the client stream so that the client can blank out
// the screen.
byte[] bs = new byte[2];
bs[0] = (byte)250;
bs[1] = 1;
writeToClientStream(bs);
// rethrow the exception;
throw e;
}
}
private void processNormalProtocol2() throws Exception {
byte[] bs = rfb.sis.getSplit();
// if (viewer.inProxyMode) {
// Logger.log(Logger.DEBUG, "bs != null = " + (bs != null) + " viewer.clientStream = " + viewer.clientStream + " info = " + viewer.clientStreamInfo);
// }
if (bs != null && viewer.inProxyMode && viewer.clientStream != null) {
Logger.log(Logger.DEBUG, "getSplit got " + bs.length + " bytes");
int bytes_sent;
if (viewer.compressServerMessage && bs.length > 10000) {
ByteArrayOutputStream bos = new ByteArrayOutputStream(256000);
GZIPOutputStream gos = new GZIPOutputStream(bos, 65536);
gos.write(bs);
gos.finish();
byte[] nbs = bos.toByteArray();
gos.close();
int n = nbs.length;
Logger.log(Logger.DEBUG, "Compressed " + bs.length + "=>" + n);
byte[] b = new byte[6];
b[0] = (byte) 250;
b[1] = 2;
b[2] = (byte) ((n >> 24) & 0xff);
b[3] = (byte) ((n >> 16) & 0xff);
b[4] = (byte) ((n >> 8) & 0xff);
b[5] = (byte) (n & 0xff);
writeToClientStream(b);
writeToClientStream(nbs);
bytes_sent = n;
} else {
writeToClientStream(bs);
bytes_sent = bs.length;
}
/*
long now = System.currentTimeMillis();
long interval = now - timestamp;
if (timestamp != 0 && interval > 50) { // Anything taking less than 50ms is too small a packet
long rate = bytes_sent_last * 1000 / interval;
Logger.log(Logger.DEBUG, "Rate = " + rate + "bytes/sec");
if (rate > rate_limit) {
long wait_time = bytes_sent_last * 1000 / rate_limit - interval;
try {
Logger.log(Logger.DEBUG, "Waiting for " + wait_time + "ms in order to throttle bandwidth");
Thread.sleep(wait_time);
} catch (InterruptedException e) {
//ignored
}
}
}
timestamp = now;
bytes_sent_last = bytes_sent;
*/
}
rfb.sis.setSplit();
// Read message type from the server.
int msgType = rfb.readServerMessageType();
Logger.log(Logger.DEBUG, "S->C RFB msg type=" + msgType);
// Process the message depending on its type.
switch (msgType) {
case 250:
int b = rfb.is.read();
if (b == 1) {
viewer.vc.paintErrorString("Disconnected");
break;
} else if (b == 2) {
int n = rfb.is.readInt();
Logger.log(Logger.DEBUG, "Reading compressed data size=" + n);
byte[] buf = new byte[n];
rfb.is.readFully(buf);
ByteArrayInputStream bis = new ByteArrayInputStream(buf);
DataInputStream oldis = rfb.is;
// swap out the underlying input stream
rfb.is = new DataInputStream(new GZIPInputStream(bis, 65536));
try {
processNormalProtocol2();
} finally {
rfb.is = oldis;
}
break;
} else if (b == 3) {
// Send liveliness message
rfb.writeClientCustomMessage(0);
break;
}
throw new Exception ("Message type 250 comes with bad submessage type: " + b);
case RfbProto.FramebufferUpdate:
rfb.readFramebufferUpdate();
boolean cursorPosReceived = false;
for (int i = 0; i < rfb.updateNRects; i++) {
rfb.readFramebufferUpdateRectHdr();
int rx = rfb.updateRectX, ry = rfb.updateRectY;
int rw = rfb.updateRectW, rh = rfb.updateRectH;
if (rfb.updateRectEncoding == rfb.EncodingLastRect)
break;
if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
rfb.setFramebufferSize(rw, rh);
updateFramebufferSize();
break;
}
if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
rfb.updateRectEncoding == rfb.EncodingRichCursor) {
handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
continue;
}
if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
softCursorMove(rx, ry);
cursorPosReceived = true;
continue;
}
rfb.startTiming();
// Logger.log(Logger.INFO, "RFB Update Rect Encoding: " + rfb.updateRectEncoding);
switch (rfb.updateRectEncoding) {
case RfbProto.EncodingRaw:
handleRawRect(rx, ry, rw, rh);
break;
case RfbProto.EncodingCopyRect:
handleCopyRect(rx, ry, rw, rh);
break;
case RfbProto.EncodingRRE:
handleRRERect(rx, ry, rw, rh);
break;
case RfbProto.EncodingCoRRE:
handleCoRRERect(rx, ry, rw, rh);
break;
case RfbProto.EncodingHextile:
handleHextileRect(rx, ry, rw, rh);
break;
case RfbProto.EncodingZRLE:
handleZRLERect(rx, ry, rw, rh);
break;
case RfbProto.EncodingZlib:
handleZlibRect(rx, ry, rw, rh);
break;
case RfbProto.EncodingTight:
handleTightRect(rx, ry, rw, rh);
break;
default:
throw new Exception("Unknown RFB rectangle encoding " +
rfb.updateRectEncoding);
}
rfb.stopTiming();
}
boolean fullUpdateNeeded = false;
// Start/stop session recording if necessary. Request full
// update if a new session file was opened.
if (viewer.checkRecordingStatus())
fullUpdateNeeded = true;
// Defer framebuffer update request if necessary. But wake up
// immediately on keyboard or mouse event. Also, don't sleep
// if there is some data to receive, or if the last update
// included a PointerPos message.
if (viewer.deferUpdateRequests > 0 &&
rfb.is.available() == 0 && !cursorPosReceived) {
synchronized(rfb) {
try {
rfb.wait(viewer.deferUpdateRequests);
} catch (InterruptedException e) {
}
}
}
// Before requesting framebuffer update, check if the pixel
// format should be changed. If it should, request full update
// instead of an incremental one.
if (viewer.options.eightBitColors != (bytesPixel == 1)) {
setPixelFormat();
fullUpdateNeeded = true;
}
// viewer.autoSelectEncodings();
rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
rfb.framebufferHeight,
!fullUpdateNeeded);
break;
case RfbProto.SetColourMapEntries:
throw new Exception("Can't handle SetColourMapEntries message");
case RfbProto.Bell:
if (!viewer.inProxyMode)
Toolkit.getDefaultToolkit().beep();
break;
case RfbProto.ServerCutText:
String s = rfb.readServerCutText();
// viewer.clipboard.setCutText(s);
break;
default:
throw new Exception("Unknown RFB message type " + msgType);
}
}
//
// Handle a raw rectangle. The second form with paint==false is used
// by the Hextile decoder for raw-encoded tiles.
//
void handleRawRect(int x, int y, int w, int h) throws IOException {
handleRawRect(x, y, w, h, true);
}
void handleRawRect(int x, int y, int w, int h, boolean paint)
throws IOException {
//Logger.log(Logger.INFO, "handleRawRect " + " x=" + x + " y=" + y + " w=" + w + " h=" + h + " paint=" + paint);
if (bytesPixel == 1) {
for (int dy = y; dy < y + h; dy++) {
rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
if (rfb.rec != null) {
rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
}
}
} else {
byte[] buf = new byte[w * 4];
int i, offset;
for (int dy = y; dy < y + h; dy++) {
rfb.readFully(buf);
if (rfb.rec != null) {
rfb.rec.write(buf);
}
offset = dy * rfb.framebufferWidth + x;
for (i = 0; i < w; i++) {
pixels24[offset + i] =
(buf[i * 4 + 2] & 0xFF) << 16 |
(buf[i * 4 + 1] & 0xFF) << 8 |
(buf[i * 4] & 0xFF);
}
}
}
handleUpdatedPixels(x, y, w, h);
if (paint)
scheduleRepaint(x, y, w, h);
}
//
// Handle a CopyRect rectangle.
//
void handleCopyRect(int x, int y, int w, int h) throws IOException {
//Logger.log(Logger.INFO, "handleCopyRect " + " x=" + x + " y=" + y + " w=" + w + " h=" + h);
rfb.readCopyRect();
memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
scheduleRepaint(x, y, w, h);
}
//
// Handle an RRE-encoded rectangle.
//
void handleRRERect(int x, int y, int w, int h) throws IOException {
//Logger.log(Logger.INFO, "handleRRERect " + " x=" + x + " y=" + y + " w=" + w + " h=" + h);
int nSubrects = rfb.is.readInt();
byte[] bg_buf = new byte[bytesPixel];
rfb.readFully(bg_buf);
Color pixel;
if (bytesPixel == 1) {
pixel = colors[bg_buf[0] & 0xFF];
} else {
pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
}
memGraphics.setColor(pixel);
memGraphics.fillRect(x, y, w, h);
byte[] buf = new byte[nSubrects * (bytesPixel + 8)];
rfb.readFully(buf);
DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf));
if (rfb.rec != null) {
rfb.rec.writeIntBE(nSubrects);
rfb.rec.write(bg_buf);
rfb.rec.write(buf);
}
int sx, sy, sw, sh;
for (int j = 0; j < nSubrects; j++) {
if (bytesPixel == 1) {
pixel = colors[ds.readUnsignedByte()];
} else {
ds.skip(4);
pixel = new Color(buf[j*12+2] & 0xFF,
buf[j*12+1] & 0xFF,
buf[j*12] & 0xFF);
}
sx = x + ds.readUnsignedShort();
sy = y + ds.readUnsignedShort();
sw = ds.readUnsignedShort();
sh = ds.readUnsignedShort();
memGraphics.setColor(pixel);
memGraphics.fillRect(sx, sy, sw, sh);
}
scheduleRepaint(x, y, w, h);
}
//
// Handle a CoRRE-encoded rectangle.
//
void handleCoRRERect(int x, int y, int w, int h) throws IOException {
//Logger.log(Logger.INFO, "handleCpRRERect " + " x=" + x + " y=" + y + " w=" + w + " h=" + h);
int nSubrects = rfb.is.readInt();
byte[] bg_buf = new byte[bytesPixel];
rfb.readFully(bg_buf);
Color pixel;
if (bytesPixel == 1) {
pixel = colors[bg_buf[0] & 0xFF];
} else {
pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
}
memGraphics.setColor(pixel);
memGraphics.fillRect(x, y, w, h);
byte[] buf = new byte[nSubrects * (bytesPixel + 4)];
rfb.readFully(buf);
if (rfb.rec != null) {
rfb.rec.writeIntBE(nSubrects);
rfb.rec.write(bg_buf);
rfb.rec.write(buf);
}
int sx, sy, sw, sh;
int i = 0;
for (int j = 0; j < nSubrects; j++) {
if (bytesPixel == 1) {
pixel = colors[buf[i++] & 0xFF];
} else {
pixel = new Color(buf[i+2] & 0xFF, buf[i+1] & 0xFF, buf[i] & 0xFF);
i += 4;
}
sx = x + (buf[i++] & 0xFF);
sy = y + (buf[i++] & 0xFF);
sw = buf[i++] & 0xFF;
sh = buf[i++] & 0xFF;
memGraphics.setColor(pixel);
memGraphics.fillRect(sx, sy, sw, sh);
}
scheduleRepaint(x, y, w, h);
}
//
// Handle a Hextile-encoded rectangle.
//
// These colors should be kept between handleHextileSubrect() calls.
private Color hextile_bg, hextile_fg;
void handleHextileRect(int x, int y, int w, int h) throws IOException {
//Logger.log(Logger.INFO, "handleHextileRect " + " x=" + x + " y=" + y + " w=" + w + " h=" + h);
hextile_bg = new Color(0);
hextile_fg = new Color(0);
for (int ty = y; ty < y + h; ty += 16) {
int th = 16;
if (y + h - ty < 16)
th = y + h - ty;
for (int tx = x; tx < x + w; tx += 16) {
int tw = 16;
if (x + w - tx < 16)
tw = x + w - tx;
handleHextileSubrect(tx, ty, tw, th);
}
// Finished with a row of tiles, now let's show it.
}
scheduleRepaint(x, y, w, h);
}
//
// Handle one tile in the Hextile-encoded data.
//
void handleHextileSubrect(int tx, int ty, int tw, int th)
throws IOException {
int subencoding = rfb.is.readUnsignedByte();
if (rfb.rec != null) {
rfb.rec.writeByte(subencoding);
}
// Is it a raw-encoded sub-rectangle?
if ((subencoding & rfb.HextileRaw) != 0) {
handleRawRect(tx, ty, tw, th, false);
return;
}
// Read and draw the background if specified.
byte[] cbuf = new byte[bytesPixel];
if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
rfb.readFully(cbuf);
if (bytesPixel == 1) {
hextile_bg = colors[cbuf[0] & 0xFF];
} else {
hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
}
if (rfb.rec != null) {
rfb.rec.write(cbuf);
}
}
memGraphics.setColor(hextile_bg);
memGraphics.fillRect(tx, ty, tw, th);
// Read the foreground color if specified.
if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
rfb.readFully(cbuf);
if (bytesPixel == 1) {
hextile_fg = colors[cbuf[0] & 0xFF];
} else {
hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
}
if (rfb.rec != null) {
rfb.rec.write(cbuf);
}
}
// Done with this tile if there is no sub-rectangles.
if ((subencoding & rfb.HextileAnySubrects) == 0)
return;
int nSubrects = rfb.is.readUnsignedByte();
int bufsize = nSubrects * 2;
if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
bufsize += nSubrects * bytesPixel;
}
byte[] buf = new byte[bufsize];
rfb.readFully(buf);
if (rfb.rec != null) {
rfb.rec.writeByte(nSubrects);
rfb.rec.write(buf);
}
int b1, b2, sx, sy, sw, sh;
int i = 0;
if ((subencoding & rfb.HextileSubrectsColoured) == 0) {
// Sub-rectangles are all of the same color.
memGraphics.setColor(hextile_fg);
for (int j = 0; j < nSubrects; j++) {
b1 = buf[i++] & 0xFF;
b2 = buf[i++] & 0xFF;
sx = tx + (b1 >> 4);
sy = ty + (b1 & 0xf);
sw = (b2 >> 4) + 1;
sh = (b2 & 0xf) + 1;
memGraphics.fillRect(sx, sy, sw, sh);
}
} else if (bytesPixel == 1) {
// BGR233 (8-bit color) version for colored sub-rectangles.
for (int j = 0; j < nSubrects; j++) {
hextile_fg = colors[buf[i++] & 0xFF];
b1 = buf[i++] & 0xFF;
b2 = buf[i++] & 0xFF;
sx = tx + (b1 >> 4);
sy = ty + (b1 & 0xf);
sw = (b2 >> 4) + 1;
sh = (b2 & 0xf) + 1;
memGraphics.setColor(hextile_fg);
memGraphics.fillRect(sx, sy, sw, sh);
}
} else {
// Full-color (24-bit) version for colored sub-rectangles.
for (int j = 0; j < nSubrects; j++) {
hextile_fg = new Color(buf[i+2] & 0xFF,
buf[i+1] & 0xFF,
buf[i] & 0xFF);
i += 4;
b1 = buf[i++] & 0xFF;
b2 = buf[i++] & 0xFF;
sx = tx + (b1 >> 4);
sy = ty + (b1 & 0xf);
sw = (b2 >> 4) + 1;
sh = (b2 & 0xf) + 1;
memGraphics.setColor(hextile_fg);
memGraphics.fillRect(sx, sy, sw, sh);
}
}
}
//
// Handle a ZRLE-encoded rectangle.
//
// FIXME: Currently, session recording is not fully supported for ZRLE.
//
void handleZRLERect(int x, int y, int w, int h) throws Exception {
//Logger.log(Logger.INFO, "handleZRLERect " + " x=" + x + " y=" + y + " w=" + w + " h=" + h);
if (zrleInStream == null)
zrleInStream = new ZlibInStream();
int nBytes = rfb.is.readInt();
if (nBytes > 64 * 1024 * 1024)
throw new Exception("ZRLE decoder: illegal compressed data size");
if (zrleBuf == null || zrleBufLen < nBytes) {
zrleBufLen = nBytes + 4096;
zrleBuf = new byte[zrleBufLen];
}
// FIXME: Do not wait for all the data before decompression.
rfb.readFully(zrleBuf, 0, nBytes);
if (rfb.rec != null) {
if (rfb.recordFromBeginning) {
rfb.rec.writeIntBE(nBytes);
rfb.rec.write(zrleBuf, 0, nBytes);
} else if (!zrleRecWarningShown) {
Logger.log(Logger.INFO, "Warning: ZRLE session can be recorded" +
" only from the beginning");
Logger.log(Logger.INFO, "Warning: Recorded file may be corrupted");
zrleRecWarningShown = true;
}
}
zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes);
for (int ty = y; ty < y+h; ty += 64) {
int th = Math.min(y+h-ty, 64);
for (int tx = x; tx < x+w; tx += 64) {
int tw = Math.min(x+w-tx, 64);
int mode = zrleInStream.readU8();
boolean rle = (mode & 128) != 0;
int palSize = mode & 127;
int[] palette = new int[128];
readZrlePalette(palette, palSize);
if (palSize == 1) {
int pix = palette[0];
Color c = (bytesPixel == 1) ?
colors[pix] : new Color(0xFF000000 | pix);
memGraphics.setColor(c);
memGraphics.fillRect(tx, ty, tw, th);
continue;
}
if (!rle) {
if (palSize == 0) {
readZrleRawPixels(tw, th);
} else {
readZrlePackedPixels(tw, th, palette, palSize);
}
} else {
if (palSize == 0) {
readZrlePlainRLEPixels(tw, th);
} else {
readZrlePackedRLEPixels(tw, th, palette);
}
}
handleUpdatedZrleTile(tx, ty, tw, th);
}
}
zrleInStream.reset();
scheduleRepaint(x, y, w, h);
}
int readPixel(InStream is) throws Exception {
int pix;
if (bytesPixel == 1) {
pix = is.readU8();
} else {
int p1 = is.readU8();
int p2 = is.readU8();
int p3 = is.readU8();
pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF);
}
return pix;
}
void readPixels(InStream is, int[] dst, int count) throws Exception {
int pix;
if (bytesPixel == 1) {
byte[] buf = new byte[count];
is.readBytes(buf, 0, count);
for (int i = 0; i < count; i++) {
dst[i] = (int)buf[i] & 0xFF;
}
} else {
byte[] buf = new byte[count * 3];
is.readBytes(buf, 0, count * 3);
for (int i = 0; i < count; i++) {
dst[i] = ((buf[i*3+2] & 0xFF) << 16 |
(buf[i*3+1] & 0xFF) << 8 |
(buf[i*3] & 0xFF));
}
}
}
void readZrlePalette(int[] palette, int palSize) throws Exception {
readPixels(zrleInStream, palette, palSize);
}
void readZrleRawPixels(int tw, int th) throws Exception {
if (bytesPixel == 1) {
zrleInStream.readBytes(zrleTilePixels8, 0, tw * th);
} else {
readPixels(zrleInStream, zrleTilePixels24, tw * th); ///
}
}
void readZrlePackedPixels(int tw, int th, int[] palette, int palSize)
throws Exception {
int bppp = ((palSize > 16) ? 8 :
((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1)));
int ptr = 0;
for (int i = 0; i < th; i++) {
int eol = ptr + tw;
int b = 0;
int nbits = 0;
while (ptr < eol) {
if (nbits == 0) {
b = zrleInStream.readU8();
nbits = 8;
}
nbits -= bppp;
int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
if (bytesPixel == 1) {
zrleTilePixels8[ptr++] = (byte)palette[index];
} else {
zrleTilePixels24[ptr++] = palette[index];
}
}
}
}
void readZrlePlainRLEPixels(int tw, int th) throws Exception {
int ptr = 0;
int end = ptr + tw * th;
while (ptr < end) {
int pix = readPixel(zrleInStream);
int len = 1;
int b;
do {
b = zrleInStream.readU8();
len += b;
} while (b == 255);
if (!(len <= end - ptr))
throw new Exception("ZRLE decoder: assertion failed" +
" (len <= end-ptr)");
if (bytesPixel == 1) {
while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
} else {
while (len-- > 0) zrleTilePixels24[ptr++] = pix;
}
}
}
void readZrlePackedRLEPixels(int tw, int th, int[] palette)
throws Exception {
int ptr = 0;
int end = ptr + tw * th;
while (ptr < end) {
int index = zrleInStream.readU8();
int len = 1;
if ((index & 128) != 0) {
int b;
do {
b = zrleInStream.readU8();
len += b;
} while (b == 255);
if (!(len <= end - ptr))
throw new Exception("ZRLE decoder: assertion failed" +
" (len <= end - ptr)");
}
index &= 127;
int pix = palette[index];
if (bytesPixel == 1) {
while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
} else {
while (len-- > 0) zrleTilePixels24[ptr++] = pix;
}
}
}
//
// Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
//
void handleUpdatedZrleTile(int x, int y, int w, int h) {
Object src, dst;
if (bytesPixel == 1) {
src = zrleTilePixels8; dst = pixels8;
} else {
src = zrleTilePixels24; dst = pixels24;
}
int offsetSrc = 0;
int offsetDst = (y * rfb.framebufferWidth + x);
for (int j = 0; j < h; j++) {
System.arraycopy(src, offsetSrc, dst, offsetDst, w);
offsetSrc += w;
offsetDst += rfb.framebufferWidth;
}
handleUpdatedPixels(x, y, w, h);
}
//
// Handle a Zlib-encoded rectangle.
//
void handleZlibRect(int x, int y, int w, int h) throws Exception {
//Logger.log(Logger.INFO, "handleZlibRect " + " x=" + x + " y=" + y + " w=" + w + " h=" + h);
int nBytes = rfb.is.readInt();
if (zlibBuf == null || zlibBufLen < nBytes) {
zlibBufLen = nBytes * 2;
zlibBuf = new byte[zlibBufLen];
}
rfb.readFully(zlibBuf, 0, nBytes);
if (rfb.rec != null && rfb.recordFromBeginning) {
rfb.rec.writeIntBE(nBytes);
rfb.rec.write(zlibBuf, 0, nBytes);
}
if (zlibInflater == null) {
zlibInflater = new Inflater();
}
zlibInflater.setInput(zlibBuf, 0, nBytes);
if (bytesPixel == 1) {
for (int dy = y; dy < y + h; dy++) {
zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w);
if (rfb.rec != null && !rfb.recordFromBeginning)
rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
}
} else {
byte[] buf = new byte[w * 4];
int i, offset;
for (int dy = y; dy < y + h; dy++) {
zlibInflater.inflate(buf);
offset = dy * rfb.framebufferWidth + x;
for (i = 0; i < w; i++) {
pixels24[offset + i] =
(buf[i * 4 + 2] & 0xFF) << 16 |
(buf[i * 4 + 1] & 0xFF) << 8 |
(buf[i * 4] & 0xFF);
}
if (rfb.rec != null && !rfb.recordFromBeginning)
rfb.rec.write(buf);
}
}
handleUpdatedPixels(x, y, w, h);
scheduleRepaint(x, y, w, h);
}
//
// Handle a Tight-encoded rectangle.
//
void handleTightRect(int x, int y, int w, int h) throws Exception {
//Logger.log(Logger.INFO, "handleTightRect " + " x=" + x + " y=" + y + " w=" + w + " h=" + h);
int comp_ctl = rfb.is.readUnsignedByte();
if (rfb.rec != null) {
if (rfb.recordFromBeginning ||
comp_ctl == (rfb.TightFill << 4) ||
comp_ctl == (rfb.TightJpeg << 4)) {
// Send data exactly as received.
rfb.rec.writeByte(comp_ctl);
} else {
// Tell the decoder to flush each of the four zlib streams.
rfb.rec.writeByte(comp_ctl | 0x0F);
}
}
// Flush zlib streams if we are told by the server to do so.
for (int stream_id = 0; stream_id < 4; stream_id++) {
if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
tightInflaters[stream_id] = null;
}
comp_ctl >>= 1;
}
// Check correctness of subencoding value.
if (comp_ctl > rfb.TightMaxSubencoding) {
throw new Exception("Incorrect tight subencoding: " + comp_ctl);
}
// Handle solid-color rectangles.
if (comp_ctl == rfb.TightFill) {
if (bytesPixel == 1) {
int idx = rfb.is.readUnsignedByte();
memGraphics.setColor(colors[idx]);
if (rfb.rec != null) {
rfb.rec.writeByte(idx);
}
} else {
byte[] buf = new byte[3];
rfb.readFully(buf);
if (rfb.rec != null) {
rfb.rec.write(buf);
}
Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16 |
(buf[1] & 0xFF) << 8 | (buf[2] & 0xFF));
memGraphics.setColor(bg);
}
memGraphics.fillRect(x, y, w, h);
scheduleRepaint(x, y, w, h);
return;
}
if (comp_ctl == rfb.TightJpeg) {
// Read JPEG data.
byte[] jpegData = new byte[rfb.readCompactLen()];
rfb.readFully(jpegData);
if (rfb.rec != null) {
if (!rfb.recordFromBeginning) {
rfb.recordCompactLen(jpegData.length);
}
rfb.rec.write(jpegData);
}
// Create an Image object from the JPEG data.
Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
// Remember the rectangle where the image should be drawn.
jpegRect = new Rectangle(x, y, w, h);
// Let the imageUpdate() method do the actual drawing, here just
// wait until the image is fully loaded and drawn.
synchronized(jpegRect) {
Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
try {
// Wait no longer than three seconds.
jpegRect.wait(3000);
} catch (InterruptedException e) {
throw new Exception("Interrupted while decoding JPEG image");
}
}
// Done, jpegRect is not needed any more.
jpegRect = null;
return;
}
// Read filter id and parameters.
int numColors = 0, rowSize = w;
byte[] palette8 = new byte[2];
int[] palette24 = new int[256];
boolean useGradient = false;
if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
int filter_id = rfb.is.readUnsignedByte();
if (rfb.rec != null) {
rfb.rec.writeByte(filter_id);
}
if (filter_id == rfb.TightFilterPalette) {
numColors = rfb.is.readUnsignedByte() + 1;
if (rfb.rec != null) {
rfb.rec.writeByte(numColors - 1);
}
if (bytesPixel == 1) {
if (numColors != 2) {
throw new Exception("Incorrect tight palette size: " + numColors);
}
rfb.readFully(palette8);
if (rfb.rec != null) {
rfb.rec.write(palette8);
}
} else {
byte[] buf = new byte[numColors * 3];
rfb.readFully(buf);
if (rfb.rec != null) {
rfb.rec.write(buf);
}
for (int i = 0; i < numColors; i++) {
palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
(buf[i * 3 + 1] & 0xFF) << 8 |
(buf[i * 3 + 2] & 0xFF));
}
}
if (numColors == 2)
rowSize = (w + 7) / 8;
} else if (filter_id == rfb.TightFilterGradient) {
useGradient = true;
} else if (filter_id != rfb.TightFilterCopy) {
throw new Exception("Incorrect tight filter id: " + filter_id);
}
}
if (numColors == 0 && bytesPixel == 4)
rowSize *= 3;
// Read, optionally uncompress and decode data.
int dataSize = h * rowSize;
if (dataSize < rfb.TightMinToCompress) {
// Data size is small - not compressed with zlib.
if (numColors != 0) {
// Indexed colors.
byte[] indexedData = new byte[dataSize];
rfb.readFully(indexedData);
if (rfb.rec != null) {
rfb.rec.write(indexedData);
}
if (numColors == 2) {
// Two colors.
if (bytesPixel == 1) {
decodeMonoData(x, y, w, h, indexedData, palette8);
} else {
decodeMonoData(x, y, w, h, indexedData, palette24);
}
} else {
// 3..255 colors (assuming bytesPixel == 4).
int i = 0;
for (int dy = y; dy < y + h; dy++) {
for (int dx = x; dx < x + w; dx++) {
pixels24[dy * rfb.framebufferWidth + dx] =
palette24[indexedData[i++] & 0xFF];
}
}
}
} else if (useGradient) {
// "Gradient"-processed data
byte[] buf = new byte[w * h * 3];
rfb.readFully(buf);
if (rfb.rec != null) {
rfb.rec.write(buf);
}
decodeGradientData(x, y, w, h, buf);
} else {
// Raw truecolor data.
if (bytesPixel == 1) {
for (int dy = y; dy < y + h; dy++) {
rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
if (rfb.rec != null) {
rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
}
}
} else {
byte[] buf = new byte[w * 3];
int i, offset;
for (int dy = y; dy < y + h; dy++) {
rfb.readFully(buf);
if (rfb.rec != null) {
rfb.rec.write(buf);
}
offset = dy * rfb.framebufferWidth + x;
for (i = 0; i < w; i++) {
pixels24[offset + i] =
(buf[i * 3] & 0xFF) << 16 |
(buf[i * 3 + 1] & 0xFF) << 8 |
(buf[i * 3 + 2] & 0xFF);
}
}
}
}
} else {
// Data was compressed with zlib.
int zlibDataLen = rfb.readCompactLen();
byte[] zlibData = new byte[zlibDataLen];
rfb.readFully(zlibData);
if (rfb.rec != null && rfb.recordFromBeginning) {
rfb.rec.write(zlibData);
}
int stream_id = comp_ctl & 0x03;
if (tightInflaters[stream_id] == null) {
tightInflaters[stream_id] = new Inflater();
}
Inflater myInflater = tightInflaters[stream_id];
myInflater.setInput(zlibData);
byte[] buf = new byte[dataSize];
myInflater.inflate(buf);
if (rfb.rec != null && !rfb.recordFromBeginning) {
rfb.recordCompressedData(buf);
}
if (numColors != 0) {
// Indexed colors.
if (numColors == 2) {
// Two colors.
if (bytesPixel == 1) {
decodeMonoData(x, y, w, h, buf, palette8);
} else {
decodeMonoData(x, y, w, h, buf, palette24);
}
} else {
// More than two colors (assuming bytesPixel == 4).
int i = 0;
for (int dy = y; dy < y + h; dy++) {
for (int dx = x; dx < x + w; dx++) {
pixels24[dy * rfb.framebufferWidth + dx] =
palette24[buf[i++] & 0xFF];
}
}
}
} else if (useGradient) {
// Compressed "Gradient"-filtered data (assuming bytesPixel == 4).
decodeGradientData(x, y, w, h, buf);
} else {
// Compressed truecolor data.
if (bytesPixel == 1) {
int destOffset = y * rfb.framebufferWidth + x;
for (int dy = 0; dy < h; dy++) {
System.arraycopy(buf, dy * w, pixels8, destOffset, w);
destOffset += rfb.framebufferWidth;
}
} else {
int srcOffset = 0;
int destOffset, i;
for (int dy = 0; dy < h; dy++) {
myInflater.inflate(buf);
destOffset = (y + dy) * rfb.framebufferWidth + x;
for (i = 0; i < w; i++) {
pixels24[destOffset + i] =
(buf[srcOffset] & 0xFF) << 16 |
(buf[srcOffset + 1] & 0xFF) << 8 |
(buf[srcOffset + 2] & 0xFF);
srcOffset += 3;
}
}
}
}
}
handleUpdatedPixels(x, y, w, h);
scheduleRepaint(x, y, w, h);
}
//
// Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
//
void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
int dx, dy, n;
int i = y * rfb.framebufferWidth + x;
int rowBytes = (w + 7) / 8;
byte b;
for (dy = 0; dy < h; dy++) {
for (dx = 0; dx < w / 8; dx++) {
b = src[dy*rowBytes+dx];
for (n = 7; n >= 0; n--)
pixels8[i++] = palette[b >> n & 1];
}
for (n = 7; n >= 8 - w % 8; n--) {
pixels8[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
}
i += (rfb.framebufferWidth - w);
}
}
void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
int dx, dy, n;
int i = y * rfb.framebufferWidth + x;
int rowBytes = (w + 7) / 8;
byte b;
for (dy = 0; dy < h; dy++) {
for (dx = 0; dx < w / 8; dx++) {
b = src[dy*rowBytes+dx];
for (n = 7; n >= 0; n--)
pixels24[i++] = palette[b >> n & 1];
}
for (n = 7; n >= 8 - w % 8; n--) {
pixels24[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
}
i += (rfb.framebufferWidth - w);
}
}
//
// Decode data processed with the "Gradient" filter.
//
void decodeGradientData (int x, int y, int w, int h, byte[] buf) {
int dx, dy, c;
byte[] prevRow = new byte[w * 3];
byte[] thisRow = new byte[w * 3];
byte[] pix = new byte[3];
int[] est = new int[3];
int offset = y * rfb.framebufferWidth + x;
for (dy = 0; dy < h; dy++) {
/* First pixel in a row */
for (c = 0; c < 3; c++) {
pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
thisRow[c] = pix[c];
}
pixels24[offset++] =
(pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
/* Remaining pixels of a row */
for (dx = 1; dx < w; dx++) {
for (c = 0; c < 3; c++) {
est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
(prevRow[(dx-1) * 3 + c] & 0xFF));
if (est[c] > 0xFF) {
est[c] = 0xFF;
} else if (est[c] < 0x00) {
est[c] = 0x00;
}
pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
thisRow[dx * 3 + c] = pix[c];
}
pixels24[offset++] =
(pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
}
System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
offset += (rfb.framebufferWidth - w);
}
}
//
// Display newly updated area of pixels.
//
void handleUpdatedPixels(int x, int y, int w, int h) {
// Logger.log(Logger.INFO, "handleUpdatedPixels x=" + x + " y=" + y + " w=" + w + " h=" + h + " memGraphics=" + memGraphics.hashCode() + memGraphics + " rawPixselsImage=" + rawPixelsImage);
// Draw updated pixels of the off-screen image.
pixelsSource.newPixels(x, y, w, h);
memGraphics.setClip(x, y, w, h);
memGraphics.drawImage(rawPixelsImage, 0, 0, null);
memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
}
//
// Tell JVM to repaint specified desktop area.
//
void scheduleRepaint(int x, int y, int w, int h) {
// Logger.log(Logger.INFO, "scheduleRepaint " + " x=" + x + " y=" + y + " w=" + w + " h=" + h);
// Request repaint, deferred if necessary.
if (rfb.framebufferWidth == scaledWidth) {
repaint(viewer.deferScreenUpdates, x, y, w, h);
} else {
int sx = x * scalingFactor / 100;
int sy = y * scalingFactor / 100;
int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1;
int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1;
repaint(viewer.deferScreenUpdates, sx, sy, sw, sh);
}
}
//
// Handle events.
//
public void keyPressed(KeyEvent evt) {
if (rfb != null)
processLocalKeyEvent(evt);
}
public void keyReleased(KeyEvent evt) {
if (rfb != null)
processLocalKeyEvent(evt);
}
public void keyTyped(KeyEvent evt) {
if (rfb != null)
evt.consume();
}
public void mousePressed(MouseEvent evt) {
if (rfb != null)
processLocalMouseEvent(evt, false);
}
public void mouseReleased(MouseEvent evt) {
if (rfb != null)
processLocalMouseEvent(evt, false);
}
public void mouseMoved(MouseEvent evt) {
if (rfb != null)
processLocalMouseEvent(evt, true);
}
public void mouseDragged(MouseEvent evt) {
if (rfb != null)
processLocalMouseEvent(evt, true);
}
public void processLocalKeyEvent(KeyEvent evt) {
if (viewer.rfb != null && rfb.inNormalProtocol) {
if (!inputEnabled) {
if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R')
&& evt.getID() == KeyEvent.KEY_PRESSED) {
// Request screen update.
try {
rfb.writeFramebufferUpdateRequest(0, 0,
rfb.framebufferWidth, rfb.framebufferHeight,
false);
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
// Input enabled.
synchronized (rfb) {
try {
// Convert Ctrl-Alt-Ins to Ctrl-Alt-Del
if (evt.getKeyCode() == 155
&& evt.getModifiers() == (InputEvent.CTRL_MASK | InputEvent.ALT_MASK)) {
evt.setKeyCode(127);
}
rfb.writeKeyEvent(evt);
} catch (IOException e) {
// Just close the underlying stream so the main loop will error out.
synchronized (viewer) {
if (rfb != null)
rfb.close();
}
}
rfb.notify();
}
}
}
// Don't ever pass keyboard events to AWT for default processing.
// Otherwise, pressing Tab would switch focus to ButtonPanel etc.
evt.consume();
}
public void processLocalMouseEvent(MouseEvent evt, boolean moved) {
if (viewer.rfb != null && rfb.inNormalProtocol) {
if (moved) {
softCursorMove(evt.getX(), evt.getY());
}
if (rfb.framebufferWidth != scaledWidth) {
int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor;
int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor;
evt.translatePoint(sx - evt.getX(), sy - evt.getY());
}
synchronized(rfb) {
try {
rfb.writePointerEvent(evt);
} catch (Exception e) {
// Just close the underlying stream so the main loop will error out.
synchronized (viewer) {
if (rfb != null)
rfb.close();
}
}
rfb.notify();
}
}
}
//
// Ignored events.
//
public void mouseClicked(MouseEvent evt) {}
public void mouseEntered(MouseEvent evt) {}
public void mouseExited(MouseEvent evt) {}
//////////////////////////////////////////////////////////////////
//
// Handle cursor shape updates (XCursor and RichCursor encodings).
//
boolean showSoftCursor = false;
MemoryImageSource softCursorSource;
Image softCursor;
int cursorX = 0, cursorY = 0;
int cursorWidth, cursorHeight;
int origCursorWidth, origCursorHeight;
int hotX, hotY;
int origHotX, origHotY;
//
// Handle cursor shape update (XCursor and RichCursor encodings).
//
synchronized void
handleCursorShapeUpdate(int encodingType,
int xhot, int yhot, int width, int height)
throws IOException {
softCursorFree();
if (width * height == 0)
return;
// Ignore cursor shape data if requested by user.
if (viewer.options.ignoreCursorUpdates) {
int bytesPerRow = (width + 7) / 8;
int bytesMaskData = bytesPerRow * height;
if (encodingType == rfb.EncodingXCursor) {
rfb.is.skipBytes(6 + bytesMaskData * 2);
} else {
// rfb.EncodingRichCursor
rfb.is.skipBytes(width * height + bytesMaskData);
}
return;
}
// Decode cursor pixel data.
softCursorSource = decodeCursorShape(encodingType, width, height);
// Set original (non-scaled) cursor dimensions.
origCursorWidth = width;
origCursorHeight = height;
origHotX = xhot;
origHotY = yhot;
// Create off-screen cursor image.
createSoftCursor();
// Show the cursor.
showSoftCursor = true;
repaint(viewer.deferCursorUpdates,
cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
}
//
// decodeCursorShape(). Decode cursor pixel data and return
// corresponding MemoryImageSource instance.
//
synchronized MemoryImageSource
decodeCursorShape(int encodingType, int width, int height)
throws IOException {
int bytesPerRow = (width + 7) / 8;
int bytesMaskData = bytesPerRow * height;
int[] softCursorPixels = new int[width * height];
if (encodingType == rfb.EncodingXCursor) {
// Read foreground and background colors of the cursor.
byte[] rgb = new byte[6];
rfb.readFully(rgb);
int[] colors = { (0xFF000000 | (rgb[3] & 0xFF) << 16 |
(rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
(0xFF000000 | (rgb[0] & 0xFF) << 16 |
(rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) };
// Read pixel and mask data.
byte[] pixBuf = new byte[bytesMaskData];
rfb.readFully(pixBuf);
byte[] maskBuf = new byte[bytesMaskData];
rfb.readFully(maskBuf);
// Decode pixel data into softCursorPixels[].
byte pixByte, maskByte;
int x, y, n, result;
int i = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width / 8; x++) {
pixByte = pixBuf[y * bytesPerRow + x];
maskByte = maskBuf[y * bytesPerRow + x];
for (n = 7; n >= 0; n--) {
if ((maskByte >> n & 1) != 0) {
result = colors[pixByte >> n & 1];
} else {
result = 0; // Transparent pixel
}
softCursorPixels[i++] = result;
}
}
for (n = 7; n >= 8 - width % 8; n--) {
if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
} else {
result = 0; // Transparent pixel
}
softCursorPixels[i++] = result;
}
}
} else {
// encodingType == rfb.EncodingRichCursor
// Read pixel and mask data.
byte[] pixBuf = new byte[width * height * bytesPixel];
rfb.readFully(pixBuf);
byte[] maskBuf = new byte[bytesMaskData];
rfb.readFully(maskBuf);
// Decode pixel data into softCursorPixels[].
byte pixByte, maskByte;
int x, y, n, result;
int i = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width / 8; x++) {
maskByte = maskBuf[y * bytesPerRow + x];
for (n = 7; n >= 0; n--) {
if ((maskByte >> n & 1) != 0) {
if (bytesPixel == 1) {
result = cm8.getRGB(pixBuf[i]);
} else {
result = 0xFF000000 |
(pixBuf[i * 4 + 2] & 0xFF) << 16 |
(pixBuf[i * 4 + 1] & 0xFF) << 8 |
(pixBuf[i * 4] & 0xFF);
}
} else {
result = 0; // Transparent pixel
}
softCursorPixels[i++] = result;
}
}
for (n = 7; n >= 8 - width % 8; n--) {
if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
if (bytesPixel == 1) {
result = cm8.getRGB(pixBuf[i]);
} else {
result = 0xFF000000 |
(pixBuf[i * 4 + 2] & 0xFF) << 16 |
(pixBuf[i * 4 + 1] & 0xFF) << 8 |
(pixBuf[i * 4] & 0xFF);
}
} else {
result = 0; // Transparent pixel
}
softCursorPixels[i++] = result;
}
}
}
return new MemoryImageSource(width, height, softCursorPixels, 0, width);
}
//
// createSoftCursor(). Assign softCursor new Image (scaled if necessary).
// Uses softCursorSource as a source for new cursor image.
//
synchronized void
createSoftCursor() {
if (softCursorSource == null)
return;
int scaleCursor = viewer.options.scaleCursor;
if (scaleCursor == 0 || !inputEnabled)
scaleCursor = 100;
// Save original cursor coordinates.
int x = cursorX - hotX;
int y = cursorY - hotY;
int w = cursorWidth;
int h = cursorHeight;
cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
hotX = (origHotX * scaleCursor + 50) / 100;
hotY = (origHotY * scaleCursor + 50) / 100;
softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
if (scaleCursor != 100) {
softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight,
Image.SCALE_SMOOTH);
}
if (showSoftCursor) {
// Compute screen area to update.
x = Math.min(x, cursorX - hotX);
y = Math.min(y, cursorY - hotY);
w = Math.max(w, cursorWidth);
h = Math.max(h, cursorHeight);
repaint(viewer.deferCursorUpdates, x, y, w, h);
}
}
//
// softCursorMove(). Moves soft cursor into a particular location.
//
synchronized void softCursorMove(int x, int y) {
int oldX = cursorX;
int oldY = cursorY;
cursorX = x;
cursorY = y;
if (showSoftCursor) {
repaint(viewer.deferCursorUpdates,
oldX - hotX, oldY - hotY, cursorWidth, cursorHeight);
repaint(viewer.deferCursorUpdates,
cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
}
}
//
// softCursorFree(). Remove soft cursor, dispose resources.
//
synchronized void softCursorFree() {
if (showSoftCursor) {
showSoftCursor = false;
softCursor = null;
softCursorSource = null;
repaint(viewer.deferCursorUpdates,
cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
}
}
public void paintErrorString(String msg) {
memGraphics.setColor(Color.BLACK);
memGraphics.fillRect(0,0,memImage.getWidth(null),memImage.getHeight(null));
memGraphics.setColor(Color.WHITE);
memGraphics.setFont(new Font(null, Font.PLAIN, 20));
FontMetrics fm = memGraphics.getFontMetrics();
int width = fm.stringWidth(msg);
int startx = (memImage.getWidth(null) - width) / 2;
if (startx < 0) startx = 0;
memGraphics.drawString(msg, startx, memImage.getHeight(null) / 2);
scheduleRepaint(0,0,memImage.getWidth(null),memImage.getHeight(null));
}
}