📄 vnccanvas.java
字号:
//
// 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.*;
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 VncCanvas extends Canvas
implements KeyListener, MouseListener, MouseMotionListener {
VncViewer viewer;
RfbProto rfb;
ColorModel cm8, 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];
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));
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);
if (viewer.showControls) {
viewer.buttonPanel.enableRemoteAccessControls(true);
}
} else if (!enable && inputEnabled) {
inputEnabled = false;
removeMouseListener(this);
removeMouseMotionListener(this);
if (viewer.showControls) {
viewer.buttonPanel.enableRemoteAccessControls(false);
}
}
}
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;
// 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];
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.
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;
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 != (bytesPixel == 1)) {
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;
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);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -