blob: 7bb8d29621640da5d22284b96c7dd638629257f2 [file] [log] [blame]
//
// Copyright (C) 2004 Horizon Wimba. All Rights Reserved.
// Copyright (C) 2001-2003 HorizonLive.com, Inc. All Rights Reserved.
// Copyright (C) 2001 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.
//
package com.tightvnc.rfbplayer;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import java.util.zip.*;
//
// VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
//
class VncCanvas extends Component
implements Observer {
RfbPlayer appClass;
RfbProto rfb;
ColorModel cm8, cm24;
int bytesPixel;
Image memImage;
Graphics memGraphics;
Image offImage;
Graphics offGraphics;
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;
// When we're in the seeking mode, we should not update the desktop.
// This variable helps us to remember that repainting the desktop at
// once is necessary when the seek operation is finished.
boolean seekMode;
// Distance of mouse from viewer border to trigger automatic scrolling
final static int SCROLL_MARGIN = 50;
// Color for border around small shared area
static final Color DARK_GRAY = new Color(132, 138, 156);
//
// The constructor.
//
VncCanvas(RfbPlayer player) throws IOException {
this.appClass = player;
rfb = appClass.rfb;
seekMode = false;
cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
setPixelFormat();
// Style canvas.
setBackground(Color.white);
}
//
// Override the 1.4 focus traversal keys accesor to allow
// our keyListener to receive tab presses.
//
public boolean getFocusTraversalKeysEnabled() {
return false;
}
//
// Callback methods to determine geometry of our Component.
//
public Dimension getPreferredSize() {
Dimension d = appClass.desktopScrollPane.getViewportSize();
Dimension sz = new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
if (d.width > sz.width)
sz.width = d.width;
if (d.height > sz.height)
sz.height = d.height;
return sz;
}
public Dimension getMinimumSize() {
return getPreferredSize();
}
public Dimension getMaximumSize() {
return getPreferredSize();
}
Point getImageOrigin() {
int x = 0, y = 0;
Dimension d = appClass.desktopScrollPane.getViewportSize();
if (rfb.framebufferWidth < d.width)
x = d.width / 2 - rfb.framebufferWidth / 2;
if (rfb.framebufferHeight < d.height)
y = d.height / 2 - rfb.framebufferHeight / 2;
return new Point(x, y);
}
//
// All painting is performed here.
//
public void update(Graphics g) {
paint(g);
}
public void paint(Graphics g) {
Point o = getImageOrigin();
Dimension d = getPreferredSize();
// create new offscreen
if (offImage == null) {
offImage = createImage(d.width, d.height);
offGraphics = offImage.getGraphics();
} else if (offImage.getWidth(null) != d.width ||
offImage.getHeight(null) != d.height) {
offGraphics.dispose();
offGraphics = null;
offImage.flush();
offImage = null;
offImage = createImage(d.width, d.height);
offGraphics = offImage.getGraphics();
}
synchronized(memImage) {
offGraphics.drawImage(memImage, o.x, o.y, null);
}
// fill in background
if (o.x > 0 || o.y > 0) {
// left, right, top, bottom
Color c = g.getColor();
offGraphics.setColor(Color.white);
offGraphics.fillRect(0, 0, o.x, d.height);
offGraphics.fillRect(o.x + rfb.framebufferWidth, 0, d.width - (o.x +
rfb.framebufferWidth), d.height);
offGraphics.fillRect(o.x, 0, rfb.framebufferWidth, o.y);
offGraphics.fillRect(o.x, o.y + rfb.framebufferHeight,
rfb.framebufferWidth, d.height - (o.y +
rfb.framebufferHeight));
offGraphics.setColor(c);
}
// draw border
Color oc = g.getColor();
offGraphics.setColor(DARK_GRAY);
offGraphics.drawRect(o.x - 1, o.y - 1, rfb.framebufferWidth + 1,
rfb.framebufferHeight + 1);
offGraphics.setColor(oc);
// draw cursor
if (showSoftCursor) {
int x0 = cursorX - hotX, y0 = cursorY - hotY;
x0 += o.x;
y0 += o.y;
Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
if (r.intersects(g.getClipBounds())) {
offGraphics.setClip(o.x, o.y, rfb.framebufferWidth,
rfb.framebufferHeight);
offGraphics.drawImage(softCursor, x0, y0, null);
offGraphics.setClip(null);
}
}
// draw offscreen
g.drawImage(offImage, 0, 0, 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.
}
}
private void setPixelFormat() throws IOException {
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 = appClass.createImage(fbWidth, fbHeight);
memGraphics = memImage.getGraphics();
} else if (memImage.getWidth(null) != fbWidth ||
memImage.getHeight(null) != fbHeight) {
synchronized(memImage) {
memImage = appClass.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];
pixelsSource =
new MemoryImageSource(fbWidth, fbHeight, cm8, 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 unless seeking
if (!seekMode && appClass.desktopScrollPane != null) {
if (appClass.inSeparateFrame) {
resizeDesktopFrame();
} else {
resizeEmbeddedApplet();
// if the framebuffer shrunk, need to clear and repaint
appClass.desktopScrollPane.clearAndRepaint();
}
}
}
void resizeDesktopFrame() {
// determine screen size
Dimension screenSize = appClass.vncFrame.getToolkit().getScreenSize();
Dimension scrollSize = appClass.desktopScrollPane.getSize();
// 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;
// Further reduce the screen size to account for the
// scroll pane's insets.
Insets insets = appClass.desktopScrollPane.getInsets();
screenSize.height -= insets.top + insets.bottom;
screenSize.width -= insets.left + insets.right;
// Limit pane size to fit on screen.
boolean needResize = false;
if (scrollSize.width != rfb.framebufferWidth ||
scrollSize.height != rfb.framebufferHeight)
needResize = true;
int w = rfb.framebufferWidth, h = rfb.framebufferHeight;
if (w > screenSize.width) {
w = screenSize.width;
needResize = true;
}
if (h > screenSize.height) {
h = screenSize.height;
needResize = true;
}
if (needResize) {
appClass.desktopScrollPane.setSize(w, h);
appClass.desktopScrollPane.validate();
}
// size the canvas
setSize(getPreferredSize());
appClass.vncFrame.pack();
}
void resizeEmbeddedApplet() {
// resize scroll pane if necessary
Dimension scrollSize = appClass.desktopScrollPane.getSize();
if (scrollSize.width != appClass.dispW ||
scrollSize.height != appClass.dispH) {
appClass.desktopScrollPane.setSize(appClass.dispW, appClass.dispH);
}
appClass.desktopScrollPane.validate();
// size the canvas
setSize(getPreferredSize());
}
//
// processNormalProtocol() - executed by the rfbThread to deal with
// the RFB data.
//
public void processNormalProtocol() throws Exception {
zlibInflater = new Inflater();
tightInflaters = new Inflater[4];
// Show current time position in the control panel.
appClass.updatePos();
// Tell our FbsInputStream object to notify us when it goes to the
// `paused' mode.
appClass.fbs.addObserver(this);
//
// main dispatch loop
//
while (true) {
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) {
if (rw != 0 && rh != 0) {
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);
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;
default:
throw new Exception("Unknown RFB rectangle encoding " +
Integer.toString(rfb.updateRectEncoding, 16));
}
}
break;
case RfbProto.SetColourMapEntries:
throw new Exception("Can't handle SetColourMapEntries message");
case RfbProto.Bell:
Toolkit.getDefaultToolkit().beep();
break;
case RfbProto.ServerCutText:
rfb.readServerCutText();
break;
default:
throw new Exception("Unknown RFB message type " + msgType);
}
appClass.updatePos();
}
}
//
// 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();
int sx, sy, sw, sh;
byte[] buf = new byte[4];
rfb.is.readFully(buf);
Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
memGraphics.setColor(pixel);
memGraphics.fillRect(x, y, w, h);
for (int j = 0; j < nSubrects; j++) {
rfb.is.readFully(buf);
pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
sx = x + rfb.is.readUnsignedShort();
sy = y + rfb.is.readUnsignedShort();
sw = rfb.is.readUnsignedShort();
sh = rfb.is.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();
int sx, sy, sw, sh;
byte[] buf = new byte[4];
rfb.is.readFully(buf);
Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
memGraphics.setColor(pixel);
memGraphics.fillRect(x, y, w, h);
for (int j = 0; j < nSubrects; j++) {
rfb.is.readFully(buf);
pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
sx = x + rfb.is.readUnsignedByte();
sy = y + rfb.is.readUnsignedByte();
sw = rfb.is.readUnsignedByte();
sh = rfb.is.readUnsignedByte();
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 {
byte[] buf = new byte[256 * 4];
int subencoding = rfb.is.readUnsignedByte();
// Is it a raw-encoded sub-rectangle?
if ((subencoding & rfb.HextileRaw) != 0) {
int count, offset;
for (int j = ty; j < ty + th; j++) {
rfb.is.readFully(buf, 0, tw * 4);
offset = j * rfb.framebufferWidth + tx;
for (count = 0; count < tw; count++) {
pixels24[offset + count] =
(buf[count * 4 + 2] & 0xFF) << 16 |
(buf[count * 4 + 1] & 0xFF) << 8 |
(buf[count * 4] & 0xFF);
}
}
handleUpdatedPixels(tx, ty, tw, th);
return;
}
// Read and draw the background if specified.
if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
rfb.is.readFully(buf, 0, 4);
hextile_bg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
}
memGraphics.setColor(hextile_bg);
memGraphics.fillRect(tx, ty, tw, th);
// Read the foreground color if specified.
if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
rfb.is.readFully(buf, 0, 4);
hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
}
// Done with this tile if there is no sub-rectangles.
if ((subencoding & rfb.HextileAnySubrects) == 0)
return;
int nSubrects = rfb.is.readUnsignedByte();
int b1, b2, sx, sy, sw, sh;
if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
for (int j = 0; j < nSubrects; j++) {
rfb.is.readFully(buf, 0, 4);
hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
b1 = rfb.is.readUnsignedByte();
b2 = rfb.is.readUnsignedByte();
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 {
memGraphics.setColor(hextile_fg);
for (int j = 0; j < nSubrects; j++) {
b1 = rfb.is.readUnsignedByte();
b2 = rfb.is.readUnsignedByte();
sx = tx + (b1 >> 4);
sy = ty + (b1 & 0xf);
sw = (b2 >> 4) + 1;
sh = (b2 & 0xf) + 1;
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);
zlibInflater.setInput(zlibBuf, 0, nBytes);
try {
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);
}
}
} catch (DataFormatException dfe) {
throw new Exception(dfe.toString());
}
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();
// 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) {
byte[] buf = new byte[3];
rfb.is.readFully(buf);
Color bg = new Color(buf[0] & 0xFF, buf[1] & 0xFF, 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);
// 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;
int[] palette24 = new int[256];
boolean useGradient = false;
if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
int filter_id = rfb.is.readUnsignedByte();
if (filter_id == rfb.TightFilterPalette) {
numColors = rfb.is.readUnsignedByte() + 1;
byte[] buf = new byte[numColors * 3];
rfb.is.readFully(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 (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);
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);
int stream_id = comp_ctl & 0x03;
if (tightInflaters[stream_id] == null) {
tightInflaters[stream_id] = new Inflater();
}
Inflater myInflater = tightInflaters[stream_id];
myInflater.setInput(zlibData);
try {
if (numColors != 0) {
// Indexed colors.
byte[] indexedData = new byte[dataSize];
myInflater.inflate(indexedData);
if (numColors == 2) {
// Two colors.
decodeMonoData(x, y, w, h, indexedData, palette24);
} else {
// More than two colors.
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) {
// Compressed "Gradient"-filtered data.
byte[] buf = new byte[w * h * 3];
myInflater.inflate(buf);
decodeGradientData(x, y, w, h, buf);
} else {
// Compressed truecolor data.
byte[] buf = new byte[w * 3];
int i, offset;
for (int dy = y; dy < y + h; dy++) {
myInflater.inflate(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);
}
}
}
} catch (DataFormatException dfe) {
throw new Exception(dfe.toString());
}
}
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) {
if (appClass.fbs.isSeeking()) {
// Do nothing, and remember we are seeking.
seekMode = true;
} else {
if (seekMode) {
// Immediate repaint of the whole desktop after seeking.
seekMode = false;
if (showSoftCursor)
scrollToPoint(cursorX, cursorY);
updateFramebufferSize();
repaint();
} else {
// Usual incremental repaint.
Point o = getImageOrigin();
repaint(appClass.deferScreenUpdates, o.x + x, o.y + y, w, h);
}
}
}
//////////////////////////////////////////////////////////////////
//
// 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 (appClass.options.ignoreCursorUpdates) {
if (false) {
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;
Point p = getImageOrigin();
scheduleCursorRepaint(cursorX - hotX + p.x, cursorY - hotY + p.y,
cursorWidth, cursorHeight, 5);
}
//
// 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.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 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 = (pixBuf[i * 4] & 0xFF) << 24 |
(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 = cm8.getRGB(pixBuf[i]);
} 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;
}
}
}
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 = 100;
//int scaleCursor = appClass.options.scaleCursor;
//if (scaleCursor == 0 || !inputEnabled)
//scaleCursor = 100;
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);
}
}
private void scrollToPoint(int x, int y) {
// Automatic viewport scrolling
if (appClass.desktopScrollPane != null) {
boolean needScroll = false;
Dimension d = appClass.desktopScrollPane.getViewportSize();
Point topLeft = appClass.desktopScrollPane.getScrollPosition();
Point botRight = new Point(topLeft.x + d.width, topLeft.y + d.height);
if (x < topLeft.x + SCROLL_MARGIN) {
// shift left
topLeft.x = x - SCROLL_MARGIN;
needScroll = true;
} else if (x > botRight.x - SCROLL_MARGIN) {
// shift right
topLeft.x = x - d.width + SCROLL_MARGIN;
needScroll = true;
}
if (y < topLeft.y + SCROLL_MARGIN) {
// shift up
topLeft.y = y - SCROLL_MARGIN;
needScroll = true;
} else if (y > botRight.y - SCROLL_MARGIN) {
// shift down
topLeft.y = y - d.height + SCROLL_MARGIN;
needScroll = true;
}
if (needScroll)
appClass.desktopScrollPane.setScrollPosition(topLeft.x, topLeft.y);
}
}
//
// softCursorMove(). Moves soft cursor into a particular location.
//
synchronized void softCursorMove(int x, int y) {
Point o = getImageOrigin();
int oldX = cursorX + o.x;
int oldY = cursorY + o.y;
cursorX = x;
cursorY = y;
if (showSoftCursor) {
scheduleCursorRepaint(oldX - hotX, oldY - hotY, cursorWidth, cursorHeight,
0);
scheduleCursorRepaint(cursorX - hotX + o.x, cursorY - hotY + o.y,
cursorWidth, cursorHeight, 1);
if (!seekMode)
scrollToPoint(x, y);
}
}
//
// softCursorFree(). Remove soft cursor, dispose resources.
//
synchronized void softCursorFree() {
if (showSoftCursor) {
showSoftCursor = false;
softCursor = null;
softCursorSource = null;
Point p = getImageOrigin();
scheduleCursorRepaint(cursorX - hotX + p.x, cursorY - hotY + p.y,
cursorWidth, cursorHeight, 3);
}
}
Point getFrameCenter() {
Dimension d = appClass.desktopScrollPane.getViewportSize();
Point p = appClass.desktopScrollPane.getScrollPosition();
if (d.width > rfb.framebufferWidth)
p.x = d.width / 2;
else
p.x += d.width / 2;
if (d.height > rfb.framebufferHeight)
p.y = d.height / 2;
else
p.y += d.height / 2;
return p;
}
//
// We are observing our FbsInputStream object to get notified on
// switching to the `paused' mode. In such cases we want to repaint
// our desktop if we were seeking.
//
public void update(Observable o, Object arg) {
// Immediate repaint of the whole desktop after seeking.
repaint();
// Let next scheduleRepaint() call invoke incremental drawing.
seekMode = false;
}
void scheduleCursorRepaint(int x, int y, int w, int h, int n) {
if (appClass.fbs.isSeeking()) {
// Do nothing, and remember we are seeking.
seekMode = true;
} else {
if (seekMode) {
// Immediate repaint of the whole desktop after seeking.
seekMode = false;
if (showSoftCursor)
scrollToPoint(cursorX, cursorY);
updateFramebufferSize();
repaint();
} else {
// Usual incremental repaint.
repaint(appClass.deferScreenUpdates, x, y, w, h);
}
}
}
}