You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1673 lines
40 KiB
1673 lines
40 KiB
//
|
|
// Copyright (C) 2001,2002 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.Canvas;
|
|
import java.awt.Color;
|
|
import java.awt.Dimension;
|
|
import java.awt.Graphics;
|
|
import java.awt.Image;
|
|
import java.awt.Insets;
|
|
import java.awt.Rectangle;
|
|
import java.awt.Toolkit;
|
|
import java.awt.event.KeyEvent;
|
|
import java.awt.event.KeyListener;
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.event.MouseListener;
|
|
import java.awt.event.MouseMotionListener;
|
|
import java.awt.image.ColorModel;
|
|
import java.awt.image.DirectColorModel;
|
|
import java.awt.image.MemoryImageSource;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.DataInputStream;
|
|
import java.io.IOException;
|
|
import java.util.zip.Inflater;
|
|
|
|
|
|
//
|
|
// VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
|
|
//
|
|
|
|
class VncCanvas
|
|
extends Canvas
|
|
implements KeyListener, MouseListener, MouseMotionListener {
|
|
|
|
VncViewer viewer;
|
|
private RfbProto rfb;
|
|
ColorModel cm8_256c, cm8_64c, cm8_8c, cm24;
|
|
Color[] colors;
|
|
int bytesPixel;
|
|
|
|
Image memImage;
|
|
Graphics memGraphics;
|
|
|
|
Image rawPixelsImage;
|
|
MemoryImageSource pixelsSource;
|
|
byte[] pixels8;
|
|
int[] pixels24;
|
|
|
|
// 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 constructor.
|
|
//
|
|
|
|
VncCanvas(VncViewer v) throws IOException {
|
|
viewer = v;
|
|
rfb = viewer.rfb;
|
|
|
|
tightInflaters = new Inflater[4];
|
|
|
|
// sf@2005 - Adding more color modes
|
|
cm8_256c = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
|
|
cm8_64c = new DirectColorModel(8, (3 << 4), (3 << 2), (3 << 0));
|
|
cm8_8c = new DirectColorModel(8, (1 << 2), (1 << 1), (1 << 0));
|
|
|
|
cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
|
|
|
|
colors = new Color[256];
|
|
// sf@2005 - Now Default
|
|
for (int i = 0; i < 256; i++)
|
|
colors[i] = new Color(cm8_256c.getRGB(i));
|
|
|
|
setPixelFormat();
|
|
|
|
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);
|
|
}
|
|
|
|
//
|
|
// Callback methods to determine geometry of our Component.
|
|
//
|
|
|
|
public Dimension getPreferredSize() {
|
|
return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
|
|
}
|
|
|
|
public Dimension getMinimumSize() {
|
|
return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
|
|
}
|
|
|
|
public Dimension getMaximumSize() {
|
|
return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
|
|
}
|
|
|
|
//
|
|
// All painting is performed here.
|
|
//
|
|
|
|
public void update(Graphics g) {
|
|
paint(g);
|
|
}
|
|
|
|
public void paint(Graphics g) {
|
|
synchronized (memImage) {
|
|
g.drawImage(memImage, 0, 0, null);
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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);
|
|
} else if (!enable && inputEnabled) {
|
|
inputEnabled = false;
|
|
removeMouseListener(this);
|
|
removeMouseMotionListener(this);
|
|
}
|
|
}
|
|
|
|
|
|
public void setPixelFormat() throws IOException {
|
|
// sf@2005 - Adding more color modes
|
|
if (viewer.options.eightBitColors > 0)
|
|
{
|
|
viewer.options.oldEightBitColors = viewer.options.eightBitColors;
|
|
switch (viewer.options.eightBitColors)
|
|
{
|
|
case 1: // 256
|
|
for (int i = 0; i < 256; i++)
|
|
colors[i] = new Color(cm8_256c.getRGB(i));
|
|
rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6, false);
|
|
break;
|
|
case 2: // 64
|
|
for (int i = 0; i < 256; i++)
|
|
colors[i] = new Color(cm8_64c.getRGB(i));
|
|
rfb.writeSetPixelFormat(8, 6, false, true, 3, 3, 3, 4, 2, 0, false);
|
|
break;
|
|
case 3: // 8
|
|
for (int i = 0; i < 256; i++)
|
|
colors[i] = new Color(cm8_8c.getRGB(i));
|
|
rfb.writeSetPixelFormat(8, 3, false, true, 1, 1, 1, 2, 1, 0, false);
|
|
break;
|
|
case 4: // 4 (Grey)
|
|
for (int i = 0; i < 256; i++)
|
|
colors[i] = new Color(cm8_64c.getRGB(i));
|
|
rfb.writeSetPixelFormat(8, 6, false, true, 3, 3, 3, 4, 2, 0, true);
|
|
break;
|
|
case 5: // 2 (B&W)
|
|
for (int i = 0; i < 256; i++)
|
|
colors[i] = new Color(cm8_8c.getRGB(i));
|
|
rfb.writeSetPixelFormat(8, 3, false, true, 1, 1, 1, 2, 1, 0, true);
|
|
break;
|
|
}
|
|
bytesPixel = 1;
|
|
}
|
|
else
|
|
{
|
|
rfb.writeSetPixelFormat(
|
|
32,
|
|
24,
|
|
false,
|
|
true,
|
|
255,
|
|
255,
|
|
255,
|
|
16,
|
|
8,
|
|
0,
|
|
false);
|
|
bytesPixel = 4;
|
|
}
|
|
updateFramebufferSize();
|
|
}
|
|
|
|
void updateFramebufferSize() {
|
|
|
|
// Useful shortcuts.
|
|
int fbWidth = rfb.framebufferWidth;
|
|
int fbHeight = rfb.framebufferHeight;
|
|
|
|
// 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.createImage(fbWidth, fbHeight);
|
|
memGraphics = memImage.getGraphics();
|
|
} else if (
|
|
memImage.getWidth(null) != fbWidth
|
|
|| memImage.getHeight(null) != fbHeight) {
|
|
synchronized (memImage) {
|
|
memImage = viewer.createImage(fbWidth, fbHeight);
|
|
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];
|
|
|
|
// sf@2005
|
|
ColorModel cml = cm8_8c;
|
|
|
|
// sf@2005
|
|
switch (viewer.options.eightBitColors)
|
|
{
|
|
case 1:
|
|
cml = cm8_256c;
|
|
break;
|
|
|
|
case 2:
|
|
case 4:
|
|
cml = cm8_64c;
|
|
break;
|
|
case 3:
|
|
case 5:
|
|
cml = cm8_8c;
|
|
break;
|
|
}
|
|
|
|
pixelsSource =
|
|
new MemoryImageSource(
|
|
fbWidth,
|
|
fbHeight,
|
|
cml,
|
|
pixels8,
|
|
0,
|
|
fbWidth);
|
|
} else {
|
|
pixels8 = null;
|
|
pixels24 = new int[fbWidth * fbHeight];
|
|
|
|
pixelsSource =
|
|
new MemoryImageSource(
|
|
fbWidth,
|
|
fbHeight,
|
|
cm24,
|
|
pixels24,
|
|
0,
|
|
fbWidth);
|
|
}
|
|
pixelsSource.setAnimated(true);
|
|
rawPixelsImage = createImage(pixelsSource);
|
|
|
|
// Update the size of desktop containers.
|
|
if (viewer.inSeparateFrame) {
|
|
if (viewer.desktopScrollPane != null)
|
|
resizeDesktopFrame();
|
|
} else {
|
|
setSize(fbWidth, fbHeight);
|
|
}
|
|
}
|
|
|
|
void resizeDesktopFrame() {
|
|
setSize(rfb.framebufferWidth, rfb.framebufferHeight);
|
|
|
|
// FIXME: Find a better way to determine correct size of a
|
|
// ScrollPane. -- const
|
|
Insets insets = viewer.desktopScrollPane.getInsets();
|
|
viewer.desktopScrollPane.setSize(
|
|
rfb.framebufferWidth + 2 * Math.min(insets.left, insets.right),
|
|
rfb.framebufferHeight + 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;
|
|
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();
|
|
}
|
|
|
|
//
|
|
// processNormalProtocol() - executed by the rfbThread to deal with the
|
|
// RFB socket.
|
|
//
|
|
|
|
public void processNormalProtocol() throws Exception {
|
|
|
|
// Start/stop session recording if necessary.
|
|
viewer.checkRecordingStatus();
|
|
|
|
rfb.writeFramebufferUpdateRequest(
|
|
0,
|
|
0,
|
|
rfb.framebufferWidth,
|
|
rfb.framebufferHeight,
|
|
false);
|
|
|
|
//
|
|
// main dispatch loop
|
|
//
|
|
|
|
while (true) {
|
|
// Read message type from the server.
|
|
int msgType = rfb.readServerMessageType();
|
|
|
|
// Process the message depending on its type.
|
|
switch (msgType) {
|
|
case RfbProto.FramebufferUpdate :
|
|
rfb.readFramebufferUpdate();
|
|
|
|
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;
|
|
}
|
|
|
|
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.EncodingZlib :
|
|
handleZlibRect(rx, ry, rw, rh);
|
|
break;
|
|
case RfbProto.EncodingTight :
|
|
handleTightRect(rx, ry, rw, rh);
|
|
break;
|
|
// marscha - PointerPos
|
|
case RfbProto.EncodingPointerPos :
|
|
handleCursorPosUpdate(rx, ry);
|
|
break;
|
|
default :
|
|
throw new Exception(
|
|
"Unknown RFB rectangle encoding "
|
|
+ rfb.updateRectEncoding);
|
|
}
|
|
}
|
|
|
|
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.
|
|
if (viewer.deferUpdateRequests > 0) {
|
|
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 > 0) && (bytesPixel != 1)
|
|
||
|
|
(viewer.options.eightBitColors == 0) && (bytesPixel == 1)
|
|
||
|
|
(viewer.options.eightBitColors != viewer.options.oldEightBitColors)
|
|
)
|
|
{
|
|
setPixelFormat();
|
|
fullUpdateNeeded = true;
|
|
}
|
|
|
|
rfb.writeFramebufferUpdateRequest(
|
|
0,
|
|
0,
|
|
rfb.framebufferWidth,
|
|
rfb.framebufferHeight,
|
|
!fullUpdateNeeded);
|
|
|
|
break;
|
|
|
|
case RfbProto.SetColourMapEntries :
|
|
throw new Exception("Can't handle SetColourMapEntries message");
|
|
|
|
case RfbProto.Bell :
|
|
Toolkit.getDefaultToolkit().beep();
|
|
break;
|
|
|
|
case RfbProto.ServerCutText :
|
|
String s = rfb.readServerCutText();
|
|
viewer.clipboard.setCutText(s);
|
|
break;
|
|
|
|
case RfbProto.rfbFileTransfer :
|
|
viewer.rfb.readRfbFileTransferMsg();
|
|
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 {
|
|
|
|
if (bytesPixel == 1) {
|
|
for (int dy = y; dy < y + h; dy++) {
|
|
rfb.is.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.is.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 {
|
|
|
|
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 {
|
|
|
|
int nSubrects = rfb.is.readInt();
|
|
|
|
byte[] bg_buf = new byte[bytesPixel];
|
|
rfb.is.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.is.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 {
|
|
int nSubrects = rfb.is.readInt();
|
|
|
|
byte[] bg_buf = new byte[bytesPixel];
|
|
rfb.is.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.is.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 {
|
|
|
|
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.is.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.is.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.is.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 Zlib-encoded rectangle.
|
|
//
|
|
|
|
void handleZlibRect(int x, int y, int w, int h) throws Exception {
|
|
|
|
int nBytes = rfb.is.readInt();
|
|
|
|
if (zlibBuf == null || zlibBufLen < nBytes) {
|
|
zlibBufLen = nBytes * 2;
|
|
zlibBuf = new byte[zlibBufLen];
|
|
}
|
|
|
|
rfb.is.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 {
|
|
|
|
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.is.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.is.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.is.readFully(palette8);
|
|
if (rfb.rec != null) {
|
|
rfb.rec.write(palette8);
|
|
}
|
|
} else {
|
|
byte[] buf = new byte[numColors * 3];
|
|
rfb.is.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.is.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.is.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.is.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.is.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.is.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) {
|
|
|
|
// 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) {
|
|
// Request repaint, deferred if necessary.
|
|
repaint(viewer.deferScreenUpdates, x, y, w, h);
|
|
}
|
|
|
|
//
|
|
// Handle events.
|
|
//
|
|
|
|
public void keyPressed(KeyEvent evt) {
|
|
processLocalKeyEvent(evt);
|
|
}
|
|
public void keyReleased(KeyEvent evt) {
|
|
processLocalKeyEvent(evt);
|
|
}
|
|
public void keyTyped(KeyEvent evt) {
|
|
evt.consume();
|
|
}
|
|
|
|
public void mousePressed(MouseEvent evt) {
|
|
processLocalMouseEvent(evt, false);
|
|
}
|
|
public void mouseReleased(MouseEvent evt) {
|
|
processLocalMouseEvent(evt, false);
|
|
}
|
|
public void mouseMoved(MouseEvent evt) {
|
|
processLocalMouseEvent(evt, true);
|
|
}
|
|
public void mouseDragged(MouseEvent evt) {
|
|
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 {
|
|
rfb.writeKeyEvent(evt);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
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());
|
|
}
|
|
synchronized (rfb) {
|
|
try {
|
|
rfb.writePointerEvent(evt);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
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;
|
|
|
|
int[] softCursorPixels;
|
|
MemoryImageSource softCursorSource;
|
|
Image softCursor;
|
|
|
|
int cursorX = 0, cursorY = 0;
|
|
int cursorWidth, cursorHeight;
|
|
int hotX, hotY;
|
|
|
|
//
|
|
// Handle cursor shape update (XCursor and RichCursor encodings).
|
|
//
|
|
|
|
synchronized void handleCursorShapeUpdate(
|
|
int encodingType,
|
|
int xhot,
|
|
int yhot,
|
|
int width,
|
|
int height)
|
|
throws IOException {
|
|
|
|
int bytesPerRow = (width + 7) / 8;
|
|
int bytesMaskData = bytesPerRow * height;
|
|
|
|
softCursorFree();
|
|
|
|
if (width * height == 0)
|
|
return;
|
|
|
|
// Ignore cursor shape data if requested by user.
|
|
|
|
if (viewer.options.ignoreCursorUpdates) {
|
|
if (encodingType == rfb.EncodingXCursor) {
|
|
rfb.is.skipBytes(6 + bytesMaskData * 2);
|
|
} else {
|
|
// rfb.EncodingRichCursor
|
|
rfb.is.skipBytes(width * height + bytesMaskData);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Decode cursor pixel data.
|
|
|
|
softCursorPixels = new int[width * height];
|
|
|
|
if (encodingType == rfb.EncodingXCursor) {
|
|
|
|
// Read foreground and background colors of the cursor.
|
|
byte[] rgb = new byte[6];
|
|
rfb.is.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.is.readFully(pixBuf);
|
|
byte[] maskBuf = new byte[bytesMaskData];
|
|
rfb.is.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.is.readFully(pixBuf);
|
|
byte[] maskBuf = new byte[bytesMaskData];
|
|
rfb.is.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 = 0;
|
|
// sf@2005
|
|
switch (viewer.options.eightBitColors)
|
|
{
|
|
case 1:
|
|
result = cm8_256c.getRGB(pixBuf[i]);
|
|
break;
|
|
case 2:
|
|
case 4:
|
|
result = cm8_64c.getRGB(pixBuf[i]);
|
|
break;
|
|
case 3:
|
|
case 5:
|
|
result = cm8_8c.getRGB(pixBuf[i]);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result =
|
|
0xFF000000 | (pixBuf[i * 4 + 1] & 0xFF)
|
|
<< 16 | (pixBuf[i * 4 + 2] & 0xFF)
|
|
<< 8 | (pixBuf[i * 4 + 3] & 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 = 0;
|
|
// sf@2005
|
|
switch (viewer.options.eightBitColors)
|
|
{
|
|
case 1:
|
|
result = cm8_256c.getRGB(pixBuf[i]);
|
|
break;
|
|
case 2:
|
|
case 4:
|
|
result = cm8_64c.getRGB(pixBuf[i]);
|
|
break;
|
|
case 3:
|
|
case 5:
|
|
result = cm8_8c.getRGB(pixBuf[i]);
|
|
break;
|
|
} }
|
|
else
|
|
{
|
|
result =
|
|
0xFF000000 | (pixBuf[i * 4 + 1] & 0xFF)
|
|
<< 16 | (pixBuf[i * 4 + 2] & 0xFF)
|
|
<< 8 | (pixBuf[i * 4 + 3] & 0xFF);
|
|
}
|
|
} else {
|
|
result = 0; // Transparent pixel
|
|
}
|
|
softCursorPixels[i++] = result;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Draw the cursor on an off-screen image.
|
|
|
|
softCursorSource =
|
|
new MemoryImageSource(width, height, softCursorPixels, 0, width);
|
|
softCursor = createImage(softCursorSource);
|
|
|
|
// Set remaining data associated with cursor.
|
|
|
|
cursorWidth = width;
|
|
cursorHeight = height;
|
|
hotX = xhot;
|
|
hotY = yhot;
|
|
|
|
showSoftCursor = true;
|
|
|
|
// Show the cursor.
|
|
|
|
repaint(
|
|
viewer.deferCursorUpdates,
|
|
cursorX - hotX,
|
|
cursorY - hotY,
|
|
cursorWidth,
|
|
cursorHeight);
|
|
}
|
|
|
|
//
|
|
// marscha - PointerPos
|
|
// Handle cursor position update (PointerPos encoding).
|
|
//
|
|
|
|
synchronized void handleCursorPosUpdate(
|
|
int x,
|
|
int y) {
|
|
if (x >= rfb.framebufferWidth)
|
|
x = rfb.framebufferWidth - 1;
|
|
if (y >= rfb.framebufferHeight)
|
|
y = rfb.framebufferHeight - 1;
|
|
|
|
softCursorMove(x, y);
|
|
}
|
|
|
|
//
|
|
// softCursorMove(). Moves soft cursor into a particular location.
|
|
//
|
|
|
|
synchronized void softCursorMove(int x, int y) {
|
|
if (showSoftCursor) {
|
|
repaint(
|
|
viewer.deferCursorUpdates,
|
|
cursorX - hotX,
|
|
cursorY - hotY,
|
|
cursorWidth,
|
|
cursorHeight);
|
|
repaint(
|
|
viewer.deferCursorUpdates,
|
|
x - hotX,
|
|
y - hotY,
|
|
cursorWidth,
|
|
cursorHeight);
|
|
}
|
|
|
|
cursorX = x;
|
|
cursorY = y;
|
|
}
|
|
|
|
//
|
|
// softCursorFree(). Remove soft cursor, dispose resources.
|
|
//
|
|
|
|
synchronized void softCursorFree() {
|
|
if (showSoftCursor) {
|
|
showSoftCursor = false;
|
|
softCursor = null;
|
|
softCursorSource = null;
|
|
softCursorPixels = null;
|
|
|
|
repaint(
|
|
viewer.deferCursorUpdates,
|
|
cursorX - hotX,
|
|
cursorY - hotY,
|
|
cursorWidth,
|
|
cursorHeight);
|
|
}
|
|
}
|
|
}
|
|
|