Console proxy refactoring incremental check-in - new VNC protocol implementation

This commit is contained in:
Kelven Yang 2012-02-28 17:00:17 -08:00
parent 8fb378ac53
commit 52ebf15e7c
32 changed files with 3029 additions and 857 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry combineaccessrules="false" kind="src" path="/deps"/>
<classpathentry combineaccessrules="false" kind="src" path="/console"/>
<classpathentry kind="output" path="bin"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry combineaccessrules="false" kind="src" path="/deps"/>
<classpathentry combineaccessrules="false" kind="src" path="/console"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -0,0 +1,27 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It 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 3 of the License, or any later version.
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.consoleproxy.util;
import java.awt.Rectangle;
import java.util.List;
public interface ITileScanListener {
boolean onTileChange(Rectangle rowMergedRect, int row, int col);
void onRegionChange(List<Region> regionList);
}

View File

@ -0,0 +1,225 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It 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 3 of the License, or any later version.
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.consoleproxy.util;
// logger facility for dynamic switch between console logger used in Applet and log4j based logger
public class Logger {
private static LoggerFactory factory = null;
public static final int LEVEL_TRACE = 1;
public static final int LEVEL_DEBUG = 2;
public static final int LEVEL_INFO = 3;
public static final int LEVEL_WARN = 4;
public static final int LEVEL_ERROR = 5;
private Class<?> clazz;
private Logger logger;
private static int level = LEVEL_INFO;
public static Logger getLogger(Class<?> clazz) {
return new Logger(clazz);
}
public static void setFactory(LoggerFactory f) {
factory = f;
}
public static void setLevel(int l) {
level = l;
}
public Logger(Class<?> clazz) {
this.clazz = clazz;
}
protected Logger() {
}
public boolean isTraceEnabled() {
if(factory != null) {
if(logger == null)
logger = factory.getLogger(clazz);
return logger.isTraceEnabled();
}
return level <= LEVEL_TRACE;
}
public boolean isDebugEnabled() {
if(factory != null) {
if(logger == null)
logger = factory.getLogger(clazz);
return logger.isDebugEnabled();
}
return level <= LEVEL_DEBUG;
}
public boolean isInfoEnabled() {
if(factory != null) {
if(logger == null)
logger = factory.getLogger(clazz);
return logger.isInfoEnabled();
}
return level <= LEVEL_INFO;
}
public void trace(Object message) {
if(factory != null) {
if(logger == null)
logger = factory.getLogger(clazz);
logger.trace(message);
} else {
if(level <= LEVEL_TRACE)
System.out.println(message);
}
}
public void trace(Object message, Throwable exception) {
if(factory != null) {
if(logger == null)
logger = factory.getLogger(clazz);
logger.trace(message, exception);
} else {
if(level <= LEVEL_TRACE) {
System.out.println(message);
if (exception != null) {
exception.printStackTrace(System.out);
}
}
}
}
public void info(Object message) {
if(factory != null) {
if(logger == null)
logger = factory.getLogger(clazz);
logger.info(message);
} else {
if(level <= LEVEL_INFO)
System.out.println(message);
}
}
public void info(Object message, Throwable exception) {
if(factory != null) {
if(logger == null)
logger = factory.getLogger(clazz);
logger.info(message, exception);
} else {
if(level <= LEVEL_INFO) {
System.out.println(message);
if (exception != null) {
exception.printStackTrace(System.out);
}
}
}
}
public void debug(Object message) {
if(factory != null) {
if(logger == null)
logger = factory.getLogger(clazz);
logger.debug(message);
} else {
if(level <= LEVEL_DEBUG)
System.out.println(message);
}
}
public void debug(Object message, Throwable exception) {
if(factory != null) {
if(logger == null)
logger = factory.getLogger(clazz);
logger.debug(message, exception);
} else {
if(level <= LEVEL_DEBUG) {
System.out.println(message);
if (exception != null) {
exception.printStackTrace(System.out);
}
}
}
}
public void warn(Object message) {
if(factory != null) {
if(logger == null)
logger = factory.getLogger(clazz);
logger.warn(message);
} else {
if(level <= LEVEL_WARN)
System.out.println(message);
}
}
public void warn(Object message, Throwable exception) {
if(factory != null) {
if(logger == null)
logger = factory.getLogger(clazz);
logger.warn(message, exception);
} else {
if(level <= LEVEL_WARN) {
System.out.println(message);
if (exception != null) {
exception.printStackTrace(System.out);
}
}
}
}
public void error(Object message) {
if(factory != null) {
if(logger == null)
logger = factory.getLogger(clazz);
logger.error(message);
} else {
if(level <= LEVEL_ERROR)
System.out.println(message);
}
}
public void error(Object message, Throwable exception) {
if(factory != null) {
if(logger == null)
logger = factory.getLogger(clazz);
logger.error(message, exception);
} else {
if(level <= LEVEL_ERROR) {
System.out.println(message);
if (exception != null) {
exception.printStackTrace(System.out);
}
}
}
}
}

View File

@ -0,0 +1,23 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It 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 3 of the License, or any later version.
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.consoleproxy.util;
public interface LoggerFactory {
Logger getLogger(Class<?> clazz);
}

View File

@ -0,0 +1,92 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It 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 3 of the License, or any later version.
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.consoleproxy.util;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
public class Region {
private Rectangle bound;
private List<Rectangle> rectList;
public Region() {
bound = new Rectangle(0, 0, 0, 0);
rectList = new ArrayList<Rectangle>();
}
public Region(Rectangle rect) {
bound = new Rectangle(rect.x, rect.y, rect.width, rect.height);
rectList = new ArrayList<Rectangle>();
rectList.add(rect);
}
public Rectangle getBound() {
return bound;
}
public void clearBound() {
assert(rectList.size() == 0);
bound.x = bound.y = bound.width = bound.height = 0;
}
public List<Rectangle> getRectangles() {
return rectList;
}
public boolean add(Rectangle rect) {
if(bound.isEmpty()) {
assert(rectList.size() == 0);
bound.x = rect.x;
bound.y = rect.y;
bound.width = rect.width;
bound.height = rect.height;
rectList.add(rect);
return true;
}
Rectangle rcInflated = new Rectangle(rect.x - 1, rect.y- 1, rect.width + 2, rect.height + 2);
if(!bound.intersects(rcInflated))
return false;
for(Rectangle r : rectList) {
if(r.intersects(rcInflated)) {
if(!r.contains(rect)) {
enlargeBound(rect);
rectList.add(rect);
return true;
}
}
}
return false;
}
private void enlargeBound(Rectangle rect) {
int boundLeft = Math.min(bound.x, rect.x);
int boundTop = Math.min(bound.y, rect.y);
int boundRight = Math.max(bound.x + bound.width, rect.x + rect.width);
int boundBottom = Math.max(bound.y + bound.height, rect.y + rect.height);
bound.x = boundLeft;
bound.y = boundTop;
bound.width = boundRight - boundLeft;
bound.height = boundBottom - boundTop;
}
}

View File

@ -0,0 +1,61 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It 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 3 of the License, or any later version.
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.consoleproxy.util;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
public class RegionClassifier {
private List<Region> regionList;
public RegionClassifier() {
regionList = new ArrayList<Region>();
}
public void add(Rectangle rect) {
// quickly identify that if we need a new region
boolean newRegion = true;
Rectangle rcInflated = new Rectangle(rect.x - 1, rect.y - 1, rect.width + 2, rect.height + 2);
for(Region region : regionList) {
if(region.getBound().intersects(rcInflated)) {
newRegion = false;
break;
}
}
if(newRegion) {
regionList.add(new Region(rect));
} else {
for(Region region : regionList) {
if(region.add(rect))
return;
}
regionList.add(new Region(rect));
}
}
public List<Region> getRegionList() {
return regionList;
}
public void clear() {
regionList.clear();
}
}

View File

@ -0,0 +1,57 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It 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 3 of the License, or any later version.
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.consoleproxy.util;
import java.awt.Rectangle;
public class TileInfo {
private int row;
private int col;
private Rectangle tileRect;
public TileInfo(int row, int col, Rectangle tileRect) {
this.row = row;
this.col = col;
this.tileRect = tileRect;
}
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
public int getCol() {
return col;
}
public void setCol(int col) {
this.col = col;
}
public Rectangle getTileRect() {
return tileRect;
}
public void setTileRect(Rectangle tileRect) {
this.tileRect = tileRect;
}
}

View File

@ -0,0 +1,271 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It 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 3 of the License, or any later version.
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.consoleproxy.util;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
public class TileTracker {
// 2 dimension tile status snapshot, a true value means the corresponding tile has been invalidated
private boolean[][] snapshot;
private int tileWidth = 0;
private int tileHeight = 0;
private int trackWidth = 0;
private int trackHeight = 0;
public TileTracker() {
}
public int getTileWidth() {
return tileWidth;
}
public void setTileWidth(int tileWidth) {
this.tileWidth = tileWidth;
}
public int getTileHeight() {
return tileHeight;
}
public void setTileHeight(int tileHeight) {
this.tileHeight = tileHeight;
}
public int getTrackWidth() {
return trackWidth;
}
public void setTrackWidth(int trackWidth) {
this.trackWidth = trackWidth;
}
public int getTrackHeight() {
return trackHeight;
}
public void setTrackHeight(int trackHeight) {
this.trackHeight = trackHeight;
}
public void initTracking(int tileWidth, int tileHeight, int trackWidth, int trackHeight) {
assert(tileWidth > 0);
assert(tileHeight > 0);
assert(trackWidth > 0);
assert(trackHeight > 0);
assert(tileWidth <= trackWidth);
assert(tileHeight <= trackHeight);
this.tileWidth = tileWidth;
this.tileHeight = tileHeight;
this.trackWidth = trackWidth;
this.trackHeight = trackHeight;
int cols = getTileCols();
int rows = getTileRows();
snapshot = new boolean[rows][cols];
for(int i = 0; i < rows; i++)
for(int j = 0; j < cols; j++)
snapshot[i][j] = false;
}
public synchronized void resize(int trackWidth, int trackHeight) {
assert(tileWidth > 0);
assert(tileHeight > 0);
assert(trackWidth > 0);
assert(trackHeight > 0);
this.trackWidth = trackWidth;
this.trackHeight = trackHeight;
int cols = getTileCols();
int rows = getTileRows();
snapshot = new boolean[rows][cols];
for(int i = 0; i < rows; i++)
for(int j = 0; j < cols; j++)
snapshot[i][j] = true;
}
public void invalidate(Rectangle rect) {
setTileFlag(rect, true);
}
public void validate(Rectangle rect) {
setTileFlag(rect, false);
}
public List<TileInfo> scan(boolean init) {
List<TileInfo> l = new ArrayList<TileInfo>();
synchronized(this) {
for(int i = 0; i < getTileRows(); i++) {
for(int j = 0; j < getTileCols(); j++) {
if(init || snapshot[i][j]) {
Rectangle rect = new Rectangle();
rect.y = i*tileHeight;
rect.x = j*tileWidth;
rect.width = Math.min(trackWidth - rect.x, tileWidth);
rect.height = Math.min(trackHeight - rect.y, tileHeight);
l.add(new TileInfo(i, j, rect));
snapshot[i][j] = false;
}
}
}
return l;
}
}
public boolean hasFullCoverage() {
synchronized(this) {
for(int i = 0; i < getTileRows(); i++) {
for(int j = 0; j < getTileCols(); j++) {
if(!snapshot[i][j])
return false;
}
}
}
return true;
}
public void initCoverageTest() {
synchronized(this) {
for(int i = 0; i < getTileRows(); i++) {
for(int j = 0; j < getTileCols(); j++) {
snapshot[i][j] = false;
}
}
}
}
// listener will be called while holding the object lock, use it
// with care to avoid deadlock condition being formed
public synchronized void scan(int nStartRow, int nStartCol, ITileScanListener listener) {
assert(listener != null);
int cols = getTileCols();
int rows = getTileRows();
nStartRow = nStartRow % rows;
nStartCol = nStartCol % cols;
int nPos = nStartRow*cols + nStartCol;
int nUnits = rows*cols;
int nStartPos = nPos;
int nRow;
int nCol;
do {
nRow = nPos / cols;
nCol = nPos % cols;
if(snapshot[nRow][nCol]) {
int nEndCol = nCol;
for(; nEndCol < cols && snapshot[nRow][nEndCol]; nEndCol++) {
snapshot[nRow][nEndCol] = false;
}
Rectangle rect = new Rectangle();
rect.y = nRow*tileHeight;
rect.height = tileHeight;
rect.x = nCol*tileWidth;
rect.width = (nEndCol - nCol)*tileWidth;
if(!listener.onTileChange(rect, nRow, nEndCol))
break;
}
nPos = (nPos + 1) % nUnits;
} while(nPos != nStartPos);
}
public void capture(ITileScanListener listener) {
assert(listener != null);
int cols = getTileCols();
int rows = getTileRows();
RegionClassifier classifier = new RegionClassifier();
int left, top, right, bottom;
synchronized(this) {
for(int i = 0; i < rows; i++) {
top = i*tileHeight;
bottom = Math.min(top + tileHeight, trackHeight);
for(int j = 0; j < cols; j++) {
left = j*tileWidth;
right = Math.min(left + tileWidth, trackWidth);
if(snapshot[i][j]) {
snapshot[i][j] = false;
classifier.add(new Rectangle(left, top, right - left, bottom - top));
}
}
}
}
listener.onRegionChange(classifier.getRegionList());
}
private synchronized void setTileFlag(Rectangle rect, boolean flag) {
int nStartTileRow;
int nStartTileCol;
int nEndTileRow;
int nEndTileCol;
int cols = getTileCols();
int rows = getTileRows();
if(rect != null) {
nStartTileRow = Math.min(getTileYPos(rect.y), rows - 1);
nStartTileCol = Math.min(getTileXPos(rect.x), cols - 1);
nEndTileRow = Math.min(getTileYPos(rect.y + rect.height - 1), rows -1);
nEndTileCol = Math.min(getTileXPos(rect.x + rect.width - 1), cols -1);
} else {
nStartTileRow = 0;
nStartTileCol = 0;
nEndTileRow = rows - 1;
nEndTileCol = cols - 1;
}
for(int i = nStartTileRow; i <= nEndTileRow; i++)
for(int j = nStartTileCol; j <= nEndTileCol; j++)
snapshot[i][j] = flag;
}
private int getTileRows() {
return (trackHeight + tileHeight - 1) / tileHeight;
}
private int getTileCols() {
return (trackWidth + tileWidth - 1) / tileWidth;
}
private int getTileXPos(int x) {
return x / tileWidth;
}
public int getTileYPos(int y) {
return y / tileHeight;
}
}

View File

@ -0,0 +1,69 @@
package com.cloud.consoleproxy.vnc;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
/**
* A <code>BuffereImageCanvas</code> component represents frame buffer image on the
* screen. It also notifies its subscribers when screen is repainted.
*/
public class BufferedImageCanvas extends Canvas {
private static final long serialVersionUID = 1L;
// Offline screen buffer
private BufferedImage offlineImage;
// Cached Graphics2D object for offline screen buffer
private Graphics2D graphics;
private PaintNotificationListener listener;
public BufferedImageCanvas(PaintNotificationListener listener, int width, int height) {
super();
this.listener = listener;
setBackground(Color.black);
setFocusable(true);
// Don't intercept TAB key
setFocusTraversalKeysEnabled(false);
setCanvasSize(width, height);
}
public void setCanvasSize(int width, int height) {
this.offlineImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
graphics = offlineImage.createGraphics();
setSize(offlineImage.getWidth(), offlineImage.getHeight());
}
@Override
public void update(Graphics g) {
// Call paint() directly, without clearing screen first
paint(g);
}
@Override
public void paint(Graphics g) {
// Only part of image, requested with repaint(Rectangle), will be
// painted on screen.
g.drawImage(offlineImage, 0, 0, this);
// Notify server that update is painted on screen
listener.imagePaintedOnScreen();
}
public BufferedImage getOfflineImage() {
return offlineImage;
}
public Graphics2D getOfflineGraphics() {
return graphics;
}
}

View File

@ -0,0 +1,10 @@
package com.cloud.consoleproxy.vnc;
public interface FrameBufferUpdateListener {
/**
* Notify listener, that frame buffer update packet is received, so client is
* permitted (but not obligated) to ask server to send another update.
*/
void frameBufferPacketReceived();
}

View File

@ -0,0 +1,11 @@
package com.cloud.consoleproxy.vnc;
public interface PaintNotificationListener {
/**
* Notify subscriber that screen is updated, so client can send another frame
* buffer update request to server.
*/
void imagePaintedOnScreen();
}

View File

@ -0,0 +1,65 @@
package com.cloud.consoleproxy.vnc;
import java.nio.charset.Charset;
public interface RfbConstants {
public static final String RFB_PROTOCOL_VERSION_MAJOR = "RFB 003.";
// public static final String VNC_PROTOCOL_VERSION_MINOR = "003";
public static final String VNC_PROTOCOL_VERSION_MINOR = "003";
public static final String RFB_PROTOCOL_VERSION = RFB_PROTOCOL_VERSION_MAJOR + VNC_PROTOCOL_VERSION_MINOR;
/**
* Server message types.
*/
final static int SERVER_FRAMEBUFFER_UPDATE = 0, SERVER_SET_COLOURMAP_ENTRIES = 1, SERVER_BELL = 2, SERVER_CUT_TEXT = 3;
/**
* Client message types.
*/
public static final int CLIENT_SET_PIXEL_FORMAT = 0, CLIENT_FIX_COLOURMAP_ENTRIES = 1, CLIENT_SET_ENCODINGS = 2, CLIENT_FRAMEBUFFER_UPDATE_REQUEST = 3,
CLIENT_KEYBOARD_EVENT = 4, CLIENT_POINTER_EVENT = 5, CLIENT_CUT_TEXT = 6;
/**
* Server authorization type
*/
public final static int CONNECTION_FAILED = 0, NO_AUTH = 1, VNC_AUTH = 2;
/**
* Server authorization reply.
*/
public final static int VNC_AUTH_OK = 0, VNC_AUTH_FAILED = 1, VNC_AUTH_TOO_MANY = 2;
/**
* Encodings.
*/
public final static int ENCODING_RAW = 0, ENCODING_COPY_RECT = 1, ENCODING_RRE = 2, ENCODING_CO_RRE = 4, ENCODING_HEXTILE = 5, ENCODING_ZRLE = 16;
/**
* Pseudo-encodings.
*/
public final static int ENCODING_CURSOR = -239 /*0xFFFFFF11*/, ENCODING_DESKTOP_SIZE = -223 /*0xFFFFFF21*/;
/**
* Encodings, which we support.
*/
public final static int[] SUPPORTED_ENCODINGS_ARRAY = { ENCODING_RAW, ENCODING_COPY_RECT, ENCODING_DESKTOP_SIZE };
/**
* Frame buffer update request type: update of whole screen or partial update.
*/
public static final int FRAMEBUFFER_FULL_UPDATE_REQUEST = 0, FRAMEBUFFER_INCREMENTAL_UPDATE_REQUEST = 1;
public static final int KEY_UP = 0, KEY_DOWN = 1;
public static final int LITTLE_ENDIAN = 0, BIG_ENDIAN = 1;
public static final int EXCLUSIVE_ACCESS = 0, SHARED_ACCESS = 1;
public static final int PALETTE = 0, TRUE_COLOR = 1;
/**
* Default charset to use when communicating with server.
*/
public static final Charset CHARSET = Charset.availableCharsets().get("US-ASCII");
}

View File

@ -0,0 +1,40 @@
package com.cloud.consoleproxy.vnc;
public class SimpleLogger {
public static void log(String message) {
System.out.println(getPrefix(1) + " LOG: " + message);
}
public static void log(int skipFrames, String message) {
System.out.println(getPrefix(1+skipFrames) + " LOG: " + message);
}
public static void debug(String message) {
System.out.println(getPrefix(1) + " DEBUG: " + message);
}
public static void info(String message) {
System.out.println(getPrefix(1) + " INFO: " + message);
}
public static void warn(String message) {
System.err.println(getPrefix(1) + " WARN: " + message);
}
public static void error(String message) {
System.err.println(getPrefix(1) + " ERROR: " + message);
}
private static String getPrefix(int skipFrames) {
StackTraceElement frame;
try {
throw new RuntimeException();
} catch (Exception e) {
frame = e.getStackTrace()[1+skipFrames];
}
return "(" + frame.getFileName() + ":" + frame.getLineNumber() + ") " + frame.getMethodName() + "()";
}
}

View File

@ -0,0 +1,350 @@
package com.cloud.consoleproxy.vnc;
import java.awt.Frame;
import java.awt.ScrollPane;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
public class VncClient {
private Socket socket;
private DataInputStream is;
private DataOutputStream os;
private VncScreenDescription screen = new VncScreenDescription();
private VncClientPacketSender sender;
private VncServerPacketReceiver receiver;
public static void main(String args[]) {
if (args.length < 3) {
printHelpMessage();
System.exit(1);
}
String host = args[0];
String port = args[1];
String password = args[2];
try {
new VncClient(host, Integer.parseInt(port), password);
} catch (NumberFormatException e) {
SimpleLogger.error("Incorrect VNC server port number: " + port + ".");
System.exit(1);
} catch (UnknownHostException e) {
SimpleLogger.error("Incorrect VNC server host name: " + host + ".");
System.exit(1);
} catch (IOException e) {
SimpleLogger.error("Cannot communicate with VNC server: " + e.getMessage());
System.exit(1);
} catch (Throwable e) {
SimpleLogger.error("An error happened: " + e.getMessage());
System.exit(1);
}
System.exit(0);
}
private static void printHelpMessage() {
/* LOG */SimpleLogger.info("Usage: HOST PORT PASSWORD.");
}
public VncClient(String host, int port, String password) throws UnknownHostException, IOException {
connectTo(host, port, password);
}
void shutdown() {
sender.closeConnection();
receiver.closeConnection();
try {
is.close();
} catch (Throwable e) {
}
try {
os.close();
} catch (Throwable e) {
}
try {
socket.close();
} catch (Throwable e) {
}
}
public void connectTo(String host, int port, String password) throws UnknownHostException, IOException {
// If port number is too small, then interpret it as display number.
if (port < 100)
port += 5900;
// Connect to server
SimpleLogger.info("Connecting to VNC server " + host + ":" + port + "...");
this.socket = new Socket(host, port);
is = new DataInputStream(socket.getInputStream());
os = new DataOutputStream(socket.getOutputStream());
// Initialize connection
handshake();
authenticate(password);
initialize();
// Run client-to-server packet sender
sender = new VncClientPacketSender(os, screen, this);
// Create buffered image canvas
BufferedImageCanvas canvas = new BufferedImageCanvas(sender, screen.getFramebufferWidth(), screen.getFramebufferHeight());
// Subscribe packet sender to various events
canvas.addMouseListener(sender);
canvas.addMouseMotionListener(sender);
canvas.addKeyListener(sender);
Frame frame = createVncClientMainWindow(canvas, screen.getDesktopName());
new Thread(sender).start();
// Run server-to-client packet receiver
receiver = new VncServerPacketReceiver(is, canvas, screen, this, sender);
try {
receiver.run();
} finally {
frame.setVisible(false);
frame.dispose();
this.shutdown();
}
}
private Frame createVncClientMainWindow(BufferedImageCanvas canvas, String title) {
// Create AWT windows
final Frame frame = new Frame(title + " - VNCle");
// Use scrolling pane to support screens, which are larger than ours
ScrollPane scroller = new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED);
scroller.add(canvas);
scroller.setSize(screen.getFramebufferWidth(), screen.getFramebufferHeight());
frame.add(scroller);
frame.pack();
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent evt) {
frame.setVisible(false);
shutdown();
}
});
return frame;
}
/**
* Handshake with VNC server.
*/
private void handshake() throws IOException {
// Read protocol version
byte[] buf = new byte[12];
is.readFully(buf);
String rfbProtocol = new String(buf);
// Server should use RFB protocol 3.x
if (!rfbProtocol.contains(RfbConstants.RFB_PROTOCOL_VERSION_MAJOR))
throw new RuntimeException("Cannot handshake with VNC server. Unsupported protocol version: \"" + rfbProtocol + "\".");
// Send response: we support RFB 3.3 only
String ourProtocolString = RfbConstants.RFB_PROTOCOL_VERSION + "\n";
os.write(ourProtocolString.getBytes());
os.flush();
}
/**
* VNC authentication.
*/
private void authenticate(String password) throws IOException {
// Read security type
int authType = is.readInt();
switch (authType) {
case RfbConstants.CONNECTION_FAILED: {
// Server forbids to connect. Read reason and throw exception
int length = is.readInt();
byte[] buf = new byte[length];
is.readFully(buf);
String reason = new String(buf, RfbConstants.CHARSET);
throw new RuntimeException("Authentication to VNC server is failed. Reason: " + reason);
}
case RfbConstants.NO_AUTH: {
// Client can connect without authorization. Nothing to do.
break;
}
case RfbConstants.VNC_AUTH: {
doVncAuth(password);
break;
}
default:
throw new RuntimeException("Unsupported VNC protocol authorization scheme, scheme code: " + authType + ".");
}
}
/**
* Encode client password and send it to server.
*/
private void doVncAuth(String password) throws IOException {
// Read challenge
byte[] challenge = new byte[16];
is.readFully(challenge);
// Encode challenge with password
byte[] response;
try {
response = encodePassword(challenge, password);
} catch (Exception e) {
throw new RuntimeException("Cannot encrypt client password to send to server: " + e.getMessage());
}
// Send encoded challenge
os.write(response);
os.flush();
// Read security result
int authResult = is.readInt();
switch (authResult) {
case RfbConstants.VNC_AUTH_OK: {
// Nothing to do
break;
}
case RfbConstants.VNC_AUTH_TOO_MANY:
throw new RuntimeException("Connection to VNC server failed: too many wrong attempts.");
case RfbConstants.VNC_AUTH_FAILED:
throw new RuntimeException("Connection to VNC server failed: wrong password.");
default:
throw new RuntimeException("Connection to VNC server failed, reason code: " + authResult);
}
}
/**
* Encode password using DES encryption with given challenge.
*
* @param challenge
* a random set of bytes.
* @param password
* a password
* @return DES hash of password and challenge
*/
public byte[] encodePassword(byte[] challenge, String password) throws Exception {
// VNC password consist of up to eight ASCII characters.
byte[] key = { 0, 0, 0, 0, 0, 0, 0, 0 }; // Padding
byte[] passwordAsciiBytes = password.getBytes(RfbConstants.CHARSET);
System.arraycopy(passwordAsciiBytes, 0, key, 0, Math.min(password.length(), 8));
// Flip bytes (reverse bits) in key
for (int i = 0; i < key.length; i++) {
key[i] = flipByte(key[i]);
}
KeySpec desKeySpec = new DESKeySpec(key);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec);
Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] response = cipher.doFinal(challenge);
return response;
}
/**
* Reverse bits in byte, so least significant bit will be most significant
* bit. E.g. 01001100 will become 00110010.
*
* See also: http://www.vidarholen.net/contents/junk/vnc.html ,
* http://bytecrafter .blogspot.com/2010/09/des-encryption-as-used-in-vnc.html
*
* @param b
* a byte
* @return byte in reverse order
*/
private static byte flipByte(byte b) {
int b1_8 = (b & 0x1) << 7;
int b2_7 = (b & 0x2) << 5;
int b3_6 = (b & 0x4) << 3;
int b4_5 = (b & 0x8) << 1;
int b5_4 = (b & 0x10) >>> 1;
int b6_3 = (b & 0x20) >>> 3;
int b7_2 = (b & 0x40) >>> 5;
int b8_1 = (b & 0x80) >>> 7;
byte c = (byte) (b1_8 | b2_7 | b3_6 | b4_5 | b5_4 | b6_3 | b7_2 | b8_1);
return c;
}
private void initialize() throws IOException {
// Send client initialization message
{
// Send shared flag
os.writeByte(RfbConstants.EXCLUSIVE_ACCESS);
os.flush();
}
// Read server initialization message
{
// Read frame buffer size
int framebufferWidth = is.readUnsignedShort();
int framebufferHeight = is.readUnsignedShort();
screen.setFramebufferSize(framebufferWidth, framebufferHeight);
}
// Read pixel format
{
int bitsPerPixel = is.readUnsignedByte();
int depth = is.readUnsignedByte();
int bigEndianFlag = is.readUnsignedByte();
int trueColorFlag = is.readUnsignedByte();
int redMax = is.readUnsignedShort();
int greenMax = is.readUnsignedShort();
int blueMax = is.readUnsignedShort();
int redShift = is.readUnsignedByte();
int greenShift = is.readUnsignedByte();
int blueShift = is.readUnsignedByte();
// Skip padding
is.skipBytes(3);
screen.setPixelFormat(bitsPerPixel, depth, bigEndianFlag, trueColorFlag, redMax, greenMax, blueMax, redShift, greenShift, blueShift);
}
// Read desktop name
{
int length = is.readInt();
byte buf[] = new byte[length];
is.readFully(buf);
String desktopName = new String(buf, RfbConstants.CHARSET);
screen.setDesktopName(desktopName);
}
}
}

View File

@ -0,0 +1,237 @@
package com.cloud.consoleproxy.vnc;
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.io.DataOutputStream;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import com.cloud.consoleproxy.vnc.packet.client.ClientPacket;
import com.cloud.consoleproxy.vnc.packet.client.FramebufferUpdateRequestPacket;
import com.cloud.consoleproxy.vnc.packet.client.KeyboardEventPacket;
import com.cloud.consoleproxy.vnc.packet.client.MouseEventPacket;
import com.cloud.consoleproxy.vnc.packet.client.SetEncodingsPacket;
import com.cloud.consoleproxy.vnc.packet.client.SetPixelFormatPacket;
public class VncClientPacketSender implements Runnable, PaintNotificationListener, KeyListener, MouseListener, MouseMotionListener, FrameBufferUpdateListener {
// Queue for outgoing packets
private final BlockingQueue<ClientPacket> queue = new ArrayBlockingQueue<ClientPacket>(30);
private final DataOutputStream os;
private final VncScreenDescription screen;
private final VncClient vncConnection;
private boolean connectionAlive = true;
// Don't send update request again until we receive next frame buffer update
private boolean updateRequestSent = false;
public VncClientPacketSender(DataOutputStream os, VncScreenDescription screen, VncClient vncConnection) {
this.os = os;
this.screen = screen;
this.vncConnection = vncConnection;
sendSetPixelFormat();
sendSetEncodings();
requestFullScreenUpdate();
}
@Override
public void run() {
try {
while (connectionAlive) {
ClientPacket packet = queue.poll(1, TimeUnit.SECONDS);
if (packet != null) {
packet.write(os);
os.flush();
}
}
} catch (Throwable e) {
if (connectionAlive) {
closeConnection();
vncConnection.shutdown();
}
}
}
private void sendSetEncodings() {
queue.add(new SetEncodingsPacket(RfbConstants.SUPPORTED_ENCODINGS_ARRAY));
}
private void sendSetPixelFormat() {
if (!screen.isRGB888_32_LE()) {
queue.add(new SetPixelFormatPacket(screen, 32, 24, RfbConstants.LITTLE_ENDIAN, RfbConstants.TRUE_COLOR, 255, 255, 255, 16, 8, 0));
}
}
public void closeConnection() {
connectionAlive = false;
}
public void requestFullScreenUpdate() {
queue.add(new FramebufferUpdateRequestPacket(RfbConstants.FRAMEBUFFER_FULL_UPDATE_REQUEST, 0, 0, screen.getFramebufferWidth(), screen
.getFramebufferHeight()));
updateRequestSent = true;
}
@Override
public void imagePaintedOnScreen() {
if (!updateRequestSent) {
queue.add(new FramebufferUpdateRequestPacket(RfbConstants.FRAMEBUFFER_INCREMENTAL_UPDATE_REQUEST, 0, 0, screen.getFramebufferWidth(), screen
.getFramebufferHeight()));
updateRequestSent = true;
}
}
@Override
public void frameBufferPacketReceived() {
updateRequestSent = false;
}
@Override
public void mouseDragged(MouseEvent e) {
queue.add(new MouseEventPacket(mapAwtModifiersToVncButtonMask(e.getModifiersEx()), e.getX(), e.getY()));
}
@Override
public void mouseMoved(MouseEvent e) {
queue.add(new MouseEventPacket(mapAwtModifiersToVncButtonMask(e.getModifiersEx()), e.getX(), e.getY()));
}
@Override
public void mouseClicked(MouseEvent e) {
// Nothing to do
}
@Override
public void mousePressed(MouseEvent e) {
queue.add(new MouseEventPacket(mapAwtModifiersToVncButtonMask(e.getModifiersEx()), e.getX(), e.getY()));
}
@Override
public void mouseReleased(MouseEvent e) {
queue.add(new MouseEventPacket(mapAwtModifiersToVncButtonMask(e.getModifiersEx()), e.getX(), e.getY()));
}
@Override
public void mouseEntered(MouseEvent e) {
// Nothing to do
}
@Override
public void mouseExited(MouseEvent e) {
// Nothing to do
}
/**
* Current state of buttons 1 to 8 are represented by bits 0 to 7 of
* button-mask respectively, 0 meaning up, 1 meaning down (pressed). On a
* conventional mouse, buttons 1, 2 and 3 correspond to the left, middle and
* right buttons on the mouse. On a wheel mouse, each step of the wheel
* upwards is represented by a press and release of button 4, and each step
* downwards is represented by a press and release of button 5.
*
* @param modifiers
* extended modifiers from AWT mouse event
* @return VNC mouse button mask
*/
public static int mapAwtModifiersToVncButtonMask(int modifiers) {
int mask = (((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) ? 0x1 : 0) | (((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) ? 0x2 : 0)
| (((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) ? 0x4 : 0);
return mask;
}
@Override
public void keyTyped(KeyEvent e) {
// Do nothing
}
@Override
public void keyPressed(KeyEvent e) {
ClientPacket request = new KeyboardEventPacket(RfbConstants.KEY_DOWN, mapAwtKeyToVncKey(e.getKeyCode()));
queue.add(request);
}
@Override
public void keyReleased(KeyEvent e) {
ClientPacket request = new KeyboardEventPacket(RfbConstants.KEY_UP, mapAwtKeyToVncKey(e.getKeyCode()));
queue.add(request);
}
private int mapAwtKeyToVncKey(int key) {
switch (key) {
case KeyEvent.VK_BACK_SPACE:
return 0xff08;
case KeyEvent.VK_TAB:
return 0xff09;
case KeyEvent.VK_ENTER:
return 0xff0d;
case KeyEvent.VK_ESCAPE:
return 0xff1b;
case KeyEvent.VK_INSERT:
return 0xff63;
case KeyEvent.VK_DELETE:
return 0xffff;
case KeyEvent.VK_HOME:
return 0xff50;
case KeyEvent.VK_END:
return 0xff57;
case KeyEvent.VK_PAGE_UP:
return 0xff55;
case KeyEvent.VK_PAGE_DOWN:
return 0xff56;
case KeyEvent.VK_LEFT:
return 0xff51;
case KeyEvent.VK_UP:
return 0xff52;
case KeyEvent.VK_RIGHT:
return 0xff53;
case KeyEvent.VK_DOWN:
return 0xff54;
case KeyEvent.VK_F1:
return 0xffbe;
case KeyEvent.VK_F2:
return 0xffbf;
case KeyEvent.VK_F3:
return 0xffc0;
case KeyEvent.VK_F4:
return 0xffc1;
case KeyEvent.VK_F5:
return 0xffc2;
case KeyEvent.VK_F6:
return 0xffc3;
case KeyEvent.VK_F7:
return 0xffc4;
case KeyEvent.VK_F8:
return 0xffc5;
case KeyEvent.VK_F9:
return 0xffc6;
case KeyEvent.VK_F10:
return 0xffc7;
case KeyEvent.VK_F11:
return 0xffc8;
case KeyEvent.VK_F12:
return 0xffc9;
case KeyEvent.VK_SHIFT:
return 0xffe1;
case KeyEvent.VK_CONTROL:
return 0xffe3;
case KeyEvent.VK_META:
return 0xffe7;
case KeyEvent.VK_ALT:
return 0xffe9;
case KeyEvent.VK_ALT_GRAPH:
return 0xffea;
case KeyEvent.VK_BACK_QUOTE:
return 0x0060;
}
return key;
}
}

View File

@ -0,0 +1,74 @@
package com.cloud.consoleproxy.vnc;
/**
* VncScreenDescription - contains information about remote VNC screen.
*/
public class VncScreenDescription {
// Frame buffer size
private int framebufferWidth = -1;
private int framebufferHeight = -1;
// Desktop name
private String desktopName;
// Bytes per pixel
private int bytesPerPixel;
// Indicates that screen uses format which we want to use:
// RGB 24bit packed into 32bit little-endian int.
private boolean rgb888_32_le = false;
public VncScreenDescription() {
}
/**
* Store information about server pixel format.
*/
public void setPixelFormat(int bitsPerPixel, int depth, int bigEndianFlag, int trueColorFlag, int redMax, int greenMax, int blueMax, int redShift,
int greenShift, int blueShift) {
bytesPerPixel = (bitsPerPixel + 7) / 8;
rgb888_32_le = (depth == 24 && bitsPerPixel == 32 && redShift == 16 && greenShift == 8 && blueShift == 0 && redMax == 255 && greenMax == 255
&& blueMax == 255 && bigEndianFlag == RfbConstants.LITTLE_ENDIAN && trueColorFlag == RfbConstants.TRUE_COLOR);
}
/**
* Store information about server screen size.
*/
public void setFramebufferSize(int framebufferWidth, int framebufferHeight) {
this.framebufferWidth = framebufferWidth;
this.framebufferHeight = framebufferHeight;
}
/**
* Store server desktop name.
*/
public void setDesktopName(String desktopName) {
this.desktopName = desktopName;
}
// Getters for variables, as usual
public String getDesktopName() {
return desktopName;
}
public int getBytesPerPixel() {
return bytesPerPixel;
}
public int getFramebufferHeight() {
return framebufferHeight;
}
public int getFramebufferWidth() {
return framebufferWidth;
}
public boolean isRGB888_32_LE() {
return rgb888_32_le;
}
}

View File

@ -0,0 +1,94 @@
package com.cloud.consoleproxy.vnc;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.io.DataInputStream;
import java.io.IOException;
import com.cloud.consoleproxy.vnc.packet.server.FramebufferUpdatePacket;
import com.cloud.consoleproxy.vnc.packet.server.ServerCutText;
public class VncServerPacketReceiver implements Runnable {
private final VncScreenDescription screen;
private BufferedImageCanvas canvas;
private DataInputStream is;
private boolean connectionAlive = true;
private VncClient vncConnection;
private final FrameBufferUpdateListener fburListener;
public VncServerPacketReceiver(DataInputStream is, BufferedImageCanvas canvas, VncScreenDescription screen, VncClient vncConnection,
FrameBufferUpdateListener fburListener) {
this.screen = screen;
this.canvas = canvas;
this.is = is;
this.vncConnection = vncConnection;
this.fburListener = fburListener;
}
@Override
public void run() {
try {
while (connectionAlive) {
// Read server message type
int messageType = is.readUnsignedByte();
// Invoke packet handler by packet type.
switch (messageType) {
case RfbConstants.SERVER_FRAMEBUFFER_UPDATE: {
// Notify sender that frame buffer update is received,
// so it can send another frame buffer update request
fburListener.frameBufferPacketReceived();
// Handle frame buffer update
new FramebufferUpdatePacket(canvas, screen, is);
break;
}
case RfbConstants.SERVER_BELL: {
serverBell();
break;
}
case RfbConstants.SERVER_CUT_TEXT: {
serverCutText(is);
break;
}
default:
throw new RuntimeException("Unknown server packet type: " + messageType + ".");
}
}
} catch (Throwable e) {
if (connectionAlive) {
closeConnection();
vncConnection.shutdown();
}
}
}
public void closeConnection() {
connectionAlive = false;
}
/**
* Handle server bell packet.
*/
private void serverBell() {
Toolkit.getDefaultToolkit().beep();
}
/**
* Handle packet with server clip-board.
*/
private void serverCutText(DataInputStream is) throws IOException {
ServerCutText clipboardContent = new ServerCutText(is);
StringSelection contents = new StringSelection(clipboardContent.getContent());
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(contents, null);
SimpleLogger.info("Server clipboard buffer: "+clipboardContent.getContent());
}
}

View File

@ -0,0 +1,10 @@
package com.cloud.consoleproxy.vnc.packet.client;
import java.io.DataOutputStream;
import java.io.IOException;
public interface ClientPacket {
void write(DataOutputStream os) throws IOException;
}

View File

@ -0,0 +1,38 @@
package com.cloud.consoleproxy.vnc.packet.client;
import java.io.DataOutputStream;
import java.io.IOException;
import com.cloud.consoleproxy.vnc.RfbConstants;
/**
* FramebufferUpdateRequestPacket
*
* @author Volodymyr M. Lisivka
*/
public class FramebufferUpdateRequestPacket implements ClientPacket {
private final int incremental;
private final int x, y, width, height;
public FramebufferUpdateRequestPacket(int incremental, int x, int y, int width, int height) {
this.incremental = incremental;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public void write(DataOutputStream os) throws IOException {
os.writeByte(RfbConstants.CLIENT_FRAMEBUFFER_UPDATE_REQUEST);
os.writeByte(incremental);
os.writeShort(x);
os.writeShort(y);
os.writeShort(width);
os.writeShort(height);
}
}

View File

@ -0,0 +1,26 @@
package com.cloud.consoleproxy.vnc.packet.client;
import java.io.DataOutputStream;
import java.io.IOException;
import com.cloud.consoleproxy.vnc.RfbConstants;
public class KeyboardEventPacket implements ClientPacket {
private final int downFlag, key;
public KeyboardEventPacket(int downFlag, int key) {
this.downFlag = downFlag;
this.key = key;
}
@Override
public void write(DataOutputStream os) throws IOException {
os.writeByte(RfbConstants.CLIENT_KEYBOARD_EVENT);
os.writeByte(downFlag);
os.writeShort(0); // padding
os.writeInt(key);
}
}

View File

@ -0,0 +1,27 @@
package com.cloud.consoleproxy.vnc.packet.client;
import java.io.DataOutputStream;
import java.io.IOException;
import com.cloud.consoleproxy.vnc.RfbConstants;
public class MouseEventPacket implements ClientPacket {
private final int buttonMask, x, y;
public MouseEventPacket(int buttonMask, int x, int y) {
this.buttonMask = buttonMask;
this.x = x;
this.y = y;
}
@Override
public void write(DataOutputStream os) throws IOException {
os.writeByte(RfbConstants.CLIENT_POINTER_EVENT);
os.writeByte(buttonMask);
os.writeShort(x);
os.writeShort(y);
}
}

View File

@ -0,0 +1,32 @@
package com.cloud.consoleproxy.vnc.packet.client;
import java.io.DataOutputStream;
import java.io.IOException;
import com.cloud.consoleproxy.vnc.RfbConstants;
public class SetEncodingsPacket implements ClientPacket {
private final int[] encodings;
public SetEncodingsPacket(int[] encodings)
{
this.encodings = encodings;
}
@Override
public void write(DataOutputStream os) throws IOException
{
os.writeByte(RfbConstants.CLIENT_SET_ENCODINGS);
os.writeByte(0);//padding
os.writeShort(encodings.length);
for(int i=0;i<encodings.length;i++)
{
os.writeInt(encodings[i]);
}
}
}

View File

@ -0,0 +1,59 @@
package com.cloud.consoleproxy.vnc.packet.client;
import java.io.DataOutputStream;
import java.io.IOException;
import com.cloud.consoleproxy.vnc.RfbConstants;
import com.cloud.consoleproxy.vnc.VncScreenDescription;
public class SetPixelFormatPacket implements ClientPacket {
private final int bitsPerPixel, depth, bigEndianFlag, trueColourFlag, redMax, greenMax, blueMax, redShift, greenShift, blueShift;
private final VncScreenDescription screen;
public SetPixelFormatPacket(VncScreenDescription screen, int bitsPerPixel, int depth, int bigEndianFlag, int trueColorFlag, int redMax, int greenMax,
int blueMax, int redShift, int greenShift, int blueShift) {
this.screen = screen;
this.bitsPerPixel = bitsPerPixel;
this.depth = depth;
this.bigEndianFlag = bigEndianFlag;
this.trueColourFlag = trueColorFlag;
this.redMax = redMax;
this.greenMax = greenMax;
this.blueMax = blueMax;
this.redShift = redShift;
this.greenShift = greenShift;
this.blueShift = blueShift;
}
@Override
public void write(DataOutputStream os) throws IOException {
os.writeByte(RfbConstants.CLIENT_SET_PIXEL_FORMAT);
// Padding
os.writeByte(0);
os.writeByte(0);
os.writeByte(0);
// Send pixel format
os.writeByte(bitsPerPixel);
os.writeByte(depth);
os.writeByte(bigEndianFlag);
os.writeByte(trueColourFlag);
os.writeShort(redMax);
os.writeShort(greenMax);
os.writeShort(blueMax);
os.writeByte(redShift);
os.writeByte(greenShift);
os.writeByte(blueShift);
// Padding
os.writeByte(0);
os.writeByte(0);
os.writeByte(0);
screen.setPixelFormat(bitsPerPixel, depth, bigEndianFlag, trueColourFlag, redMax, greenMax, blueMax, redShift, greenShift, blueShift);
}
}

View File

@ -0,0 +1,37 @@
package com.cloud.consoleproxy.vnc.packet.server;
public abstract class AbstractRect implements Rect {
protected final int x;
protected final int y;
protected final int width;
protected final int height;
public AbstractRect(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public int getX() {
return x;
}
@Override
public int getY() {
return y;
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
}

View File

@ -0,0 +1,23 @@
package com.cloud.consoleproxy.vnc.packet.server;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.DataInputStream;
import java.io.IOException;
public class CopyRect extends AbstractRect {
private final int srcX, srcY;
public CopyRect(int x, int y, int width, int height, DataInputStream is) throws IOException {
super(x, y, width, height);
srcX = is.readUnsignedShort();
srcY = is.readUnsignedShort();
}
@Override
public void paint(BufferedImage image, Graphics2D graphics) {
graphics.copyArea(srcX, srcY, width, height, x - srcX, y - srcY);
}
}

View File

@ -0,0 +1,22 @@
package com.cloud.consoleproxy.vnc.packet.server;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import com.cloud.consoleproxy.vnc.BufferedImageCanvas;
public class FrameBufferSizeChangeRequest extends AbstractRect {
private final BufferedImageCanvas canvas;
public FrameBufferSizeChangeRequest(BufferedImageCanvas canvas, int width, int height) {
super(0, 0, width, height);
this.canvas=canvas;
}
@Override
public void paint(BufferedImage offlineImage, Graphics2D graphics) {
canvas.setCanvasSize(width, height);
}
}

View File

@ -0,0 +1,77 @@
package com.cloud.consoleproxy.vnc.packet.server;
import java.io.DataInputStream;
import java.io.IOException;
import com.cloud.consoleproxy.vnc.BufferedImageCanvas;
import com.cloud.consoleproxy.vnc.RfbConstants;
import com.cloud.consoleproxy.vnc.VncScreenDescription;
import com.cloud.consoleproxy.vnc.packet.server.CopyRect;
import com.cloud.consoleproxy.vnc.packet.server.RawRect;
import com.cloud.consoleproxy.vnc.packet.server.Rect;
public class FramebufferUpdatePacket {
private final VncScreenDescription screen;
private final BufferedImageCanvas canvas;
public FramebufferUpdatePacket(BufferedImageCanvas canvas, VncScreenDescription screen, DataInputStream is) throws IOException {
this.screen = screen;
this.canvas = canvas;
readPacketData(is);
}
private void readPacketData(DataInputStream is) throws IOException {
is.skipBytes(1);// Skip padding
// Read number of rectangles
int numberOfRectangles = is.readUnsignedShort();
// For all rectangles
for (int i = 0; i < numberOfRectangles; i++) {
// Read coordinate of rectangle
int x = is.readUnsignedShort();
int y = is.readUnsignedShort();
int width = is.readUnsignedShort();
int height = is.readUnsignedShort();
int encodingType = is.readInt();
// Process rectangle
Rect rect;
switch (encodingType) {
case RfbConstants.ENCODING_RAW: {
rect = new RawRect(screen, x, y, width, height, is);
break;
}
case RfbConstants.ENCODING_COPY_RECT: {
rect = new CopyRect(x, y, width, height, is);
break;
}
case RfbConstants.ENCODING_DESKTOP_SIZE: {
rect = new FrameBufferSizeChangeRequest(canvas, width, height);
break;
}
default:
throw new RuntimeException("Unsupported ecnoding: " + encodingType);
}
paint(rect, canvas);
}
}
public void paint(Rect rect, BufferedImageCanvas canvas) {
// Draw rectangle on offline buffer
rect.paint(canvas.getOfflineImage(), canvas.getOfflineGraphics());
// Request update of repainted area
canvas.repaint(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
}
}

View File

@ -0,0 +1,60 @@
package com.cloud.consoleproxy.vnc.packet.server;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.io.DataInputStream;
import java.io.IOException;
import com.cloud.consoleproxy.vnc.VncScreenDescription;
public class RawRect extends AbstractRect {
private final int[] buf;
public RawRect(VncScreenDescription screen, int x, int y, int width, int height, DataInputStream is) throws IOException {
super(x, y, width, height);
byte[] bbuf = new byte[width * height * screen.getBytesPerPixel()];
is.readFully(bbuf);
// Convert array of bytes to array of int
int size = width * height;
buf = new int[size];
for (int i = 0, j = 0; i < size; i++, j += 4) {
buf[i] = (bbuf[j + 0] & 0xFF) | ((bbuf[j + 1] & 0xFF) << 8) | ((bbuf[j + 2] & 0xFF) << 16) | ((bbuf[j + 3] & 0xFF) << 24);
}
}
@Override
public void paint(BufferedImage image, Graphics2D graphics) {
DataBuffer dataBuf = image.getRaster().getDataBuffer();
switch (dataBuf.getDataType()) {
case DataBuffer.TYPE_INT: {
// We chose RGB888 model, so Raster will use DataBufferInt type
DataBufferInt dataBuffer = (DataBufferInt) dataBuf;
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
// Paint rectangle directly on buffer, line by line
int[] imageBuffer = dataBuffer.getData();
for (int srcLine = 0, dstLine = y; srcLine < height && dstLine < imageHeight; srcLine++, dstLine++) {
try {
System.arraycopy(buf, srcLine * width, imageBuffer, x + dstLine * imageWidth, width);
} catch (IndexOutOfBoundsException e) {
}
}
break;
}
default:
throw new RuntimeException("Unsupported data buffer in buffered image: expected data buffer of type int (DataBufferInt). Actual data buffer type: "
+ dataBuf.getClass().getSimpleName());
}
}
}

View File

@ -0,0 +1,14 @@
package com.cloud.consoleproxy.vnc.packet.server;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
public interface Rect {
void paint(BufferedImage offlineImage, Graphics2D graphics);
int getX();
int getY();
int getWidth();
int getHeight();
}

View File

@ -0,0 +1,32 @@
package com.cloud.consoleproxy.vnc.packet.server;
import java.io.DataInputStream;
import java.io.IOException;
import com.cloud.consoleproxy.vnc.RfbConstants;
import com.cloud.consoleproxy.vnc.SimpleLogger;
public class ServerCutText {
private String content;
public String getContent() {
return content;
}
public ServerCutText(DataInputStream is) throws IOException {
readPacketData(is);
}
private void readPacketData(DataInputStream is) throws IOException {
is.skipBytes(3);// Skip padding
int length = is.readInt();
byte buf[] = new byte[length];
is.readFully(buf);
content = new String(buf, RfbConstants.CHARSET);
/* LOG */SimpleLogger.log("Clippboard content: " + content);
}
}

View File

@ -131,7 +131,7 @@ public class RequestTest extends TestCase {
req.logD("Debug for Download");
DownloadAnswer answer = new DownloadAnswer("jobId", 50, "errorString", Status.ABANDONED, "filesystempath", "installpath", 10000000, 20000000);
DownloadAnswer answer = new DownloadAnswer("jobId", 50, "errorString", Status.ABANDONED, "filesystempath", "installpath", 10000000, 20000000, "chksum");
Response resp = new Response(req, answer);
resp.logD("Debug for Download");

File diff suppressed because it is too large Load Diff