Mercurial > hg > Members > nobuyasu > tightVNCClient
changeset 29:750ecaa1e1b9
add echoClient.java and waitreply.java. modify MyRfbProto.java
author | e085711 |
---|---|
date | Wed, 06 Jul 2011 11:56:15 +0900 (2011-07-06) |
parents | 68f0bc9c4211 |
children | a335a1038a23 |
files | RfbProto.java src/myVncClient/MyVncClient.java src/myVncClient/VncViewer.java src/myVncClient/echoClient.java src/myVncClient/waitreply.java |
diffstat | 5 files changed, 1647 insertions(+), 12 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/RfbProto.java Wed Jul 06 11:56:15 2011 +0900 @@ -0,0 +1,1379 @@ +package myVncClient; +// +// Copyright (C) 2001-2004 HorizonLive.com, Inc. All Rights Reserved. +// Copyright (C) 2001-2006 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. +// + +// +// RfbProto.java +// + +import java.io.*; +import java.awt.*; +import java.awt.event.*; +import java.net.Socket; +import java.net.ServerSocket; +import java.util.zip.*; +import java.nio.*; + +class RfbProto { + + final static String versionMsg_3_3 = "RFB 003.003\n", + versionMsg_3_7 = "RFB 003.007\n", versionMsg_3_8 = "RFB 003.008\n"; + + // Vendor signatures: standard VNC/RealVNC, TridiaVNC, and TightVNC + final static String StandardVendor = "STDV", TridiaVncVendor = "TRDV", + TightVncVendor = "TGHT"; + + // Security types + final static int SecTypeInvalid = 0, SecTypeNone = 1, SecTypeVncAuth = 2, + SecTypeTight = 16; + + // Supported tunneling types + final static int NoTunneling = 0; + final static String SigNoTunneling = "NOTUNNEL"; + + // Supported authentication types + final static int AuthNone = 1, AuthVNC = 2, AuthUnixLogin = 129; + final static String SigAuthNone = "NOAUTH__", SigAuthVNC = "VNCAUTH_", + SigAuthUnixLogin = "ULGNAUTH"; + + // VNC authentication results + final static int VncAuthOK = 0, VncAuthFailed = 1, VncAuthTooMany = 2; + + // Standard server-to-client messages + final static int FramebufferUpdate = 0, SetColourMapEntries = 1, Bell = 2, + ServerCutText = 3; + + // Non-standard server-to-client messages + final static int EndOfContinuousUpdates = 150; + final static String SigEndOfContinuousUpdates = "CUS_EOCU"; + + // Standard client-to-server messages + final static int SetPixelFormat = 0, FixColourMapEntries = 1, + SetEncodings = 2, FramebufferUpdateRequest = 3, KeyboardEvent = 4, + PointerEvent = 5, ClientCutText = 6; + + // Non-standard client-to-server messages + final static int EnableContinuousUpdates = 150; + final static String SigEnableContinuousUpdates = "CUC_ENCU"; + + // Supported encodings and pseudo-encodings + final static int EncodingRaw = 0, EncodingCopyRect = 1, EncodingRRE = 2, + EncodingCoRRE = 4, EncodingHextile = 5, EncodingZlib = 6, + EncodingTight = 7, EncodingZRLE = 16, + EncodingCompressLevel0 = 0xFFFFFF00, + EncodingQualityLevel0 = 0xFFFFFFE0, EncodingXCursor = 0xFFFFFF10, + EncodingRichCursor = 0xFFFFFF11, EncodingPointerPos = 0xFFFFFF18, + EncodingLastRect = 0xFFFFFF20, EncodingNewFBSize = 0xFFFFFF21; + final static String SigEncodingRaw = "RAW_____", + SigEncodingCopyRect = "COPYRECT", SigEncodingRRE = "RRE_____", + SigEncodingCoRRE = "CORRE___", SigEncodingHextile = "HEXTILE_", + SigEncodingZlib = "ZLIB____", SigEncodingTight = "TIGHT___", + SigEncodingZRLE = "ZRLE____", + SigEncodingCompressLevel0 = "COMPRLVL", + SigEncodingQualityLevel0 = "JPEGQLVL", + SigEncodingXCursor = "X11CURSR", + SigEncodingRichCursor = "RCHCURSR", + SigEncodingPointerPos = "POINTPOS", + SigEncodingLastRect = "LASTRECT", + SigEncodingNewFBSize = "NEWFBSIZ"; + + final static int MaxNormalEncoding = 255; + + // Contstants used in the Hextile decoder + final static int HextileRaw = 1, HextileBackgroundSpecified = 2, + HextileForegroundSpecified = 4, HextileAnySubrects = 8, + HextileSubrectsColoured = 16; + + // Contstants used in the Tight decoder + final static int TightMinToCompress = 12; + final static int TightExplicitFilter = 0x04, TightFill = 0x08, + TightJpeg = 0x09, TightMaxSubencoding = 0x09, + TightFilterCopy = 0x00, TightFilterPalette = 0x01, + TightFilterGradient = 0x02; + + String host; + int port; + Socket sock; + OutputStream os; + SessionRecorder rec; + boolean inNormalProtocol = false; + VncViewer viewer; + + // Input stream is declared private to make sure it can be accessed + // only via RfbProto methods. We have to do this because we want to + // count how many bytes were read. +// private DataInputStream is; + protected DataInputStream is; +// private long numBytesRead = 0; + protected long numBytesRead = 0; + + public long getNumBytesRead() { + return numBytesRead; + } + + + // Java on UNIX does not call keyPressed() on some keys, for example + // swedish keys To prevent our workaround to produce duplicate + // keypresses on JVMs that actually works, keep track of if + // keyPressed() for a "broken" key was called or not. + boolean brokenKeyPressed = false; + + // This will be set to true on the first framebuffer update + // containing Zlib-, ZRLE- or Tight-encoded data. + boolean wereZlibUpdates = false; + + // This will be set to false if the startSession() was called after + // we have received at least one Zlib-, ZRLE- or Tight-encoded + // framebuffer update. + boolean recordFromBeginning = true; + + // This fields are needed to show warnings about inefficiently saved + // sessions only once per each saved session file. + boolean zlibWarningShown; + boolean tightWarningShown; + + // Before starting to record each saved session, we set this field + // to 0, and increment on each framebuffer update. We don't flush + // the SessionRecorder data into the file before the second update. + // This allows us to write initial framebuffer update with zero + // timestamp, to let the player show initial desktop before + // playback. + int numUpdatesInSession; + + // Measuring network throughput. + boolean timing; + long timeWaitedIn100us; + long timedKbits; + + // Protocol version and TightVNC-specific protocol options. + int serverMajor, serverMinor; + int clientMajor, clientMinor; + boolean protocolTightVNC; + CapsContainer tunnelCaps, authCaps; + CapsContainer serverMsgCaps, clientMsgCaps; + CapsContainer encodingCaps; + + // If true, informs that the RFB socket was closed. +// private boolean closed; + protected boolean closed; + + // + // Constructor. Make TCP connection to RFB server. + // + RfbProto(String h, int p, VncViewer v) throws IOException { + viewer = v; + host = h; + port = p; + + if (viewer.socketFactory == null) { + sock = new Socket(host, port); + } else { + try { + Class factoryClass = Class.forName(viewer.socketFactory); + SocketFactory factory = (SocketFactory) factoryClass + .newInstance(); + if (viewer.inAnApplet) + sock = factory.createSocket(host, port, viewer); + else + sock = factory.createSocket(host, port, viewer.mainArgs); + } catch (Exception e) { + e.printStackTrace(); + throw new IOException(e.getMessage()); + } + } + is = new DataInputStream(new BufferedInputStream(sock.getInputStream(), + 16384)); + os = sock.getOutputStream(); + + timing = false; + timeWaitedIn100us = 5; + timedKbits = 0; + } + + RfbProto(String h, int p) throws IOException { + host = h; + port = p; + + sock = new Socket(host, port); + is = new DataInputStream(new BufferedInputStream(sock.getInputStream(), + 16384)); + os = sock.getOutputStream(); + + timing = false; + timeWaitedIn100us = 5; + timedKbits = 0; + } + + + + synchronized void close() { + try { + sock.close(); + closed = true; + System.out.println("RFB socket closed"); + if (rec != null) { + rec.close(); + rec = null; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + synchronized boolean closed() { + return closed; + } + + // + // Read server's protocol version message + // + + void readVersionMsg() throws Exception { + + byte[] b = new byte[12]; + + readFully(b); + + if ((b[0] != 'R') || (b[1] != 'F') || (b[2] != 'B') || (b[3] != ' ') + || (b[4] < '0') || (b[4] > '9') || (b[5] < '0') || (b[5] > '9') + || (b[6] < '0') || (b[6] > '9') || (b[7] != '.') + || (b[8] < '0') || (b[8] > '9') || (b[9] < '0') || (b[9] > '9') + || (b[10] < '0') || (b[10] > '9') || (b[11] != '\n')) { + throw new Exception("Host " + host + " port " + port + + " is not an RFB server"); + } + + serverMajor = (b[4] - '0') * 100 + (b[5] - '0') * 10 + (b[6] - '0'); + serverMinor = (b[8] - '0') * 100 + (b[9] - '0') * 10 + (b[10] - '0'); + + if (serverMajor < 3) { + throw new Exception( + "RFB server does not support protocol version 3"); + } + } + + // + // Write our protocol version message + // + + void writeVersionMsg() throws IOException { + clientMajor = 3; + if (serverMajor > 3 || serverMinor >= 8) { + clientMinor = 8; + os.write(versionMsg_3_8.getBytes()); + } else if (serverMinor >= 7) { + clientMinor = 7; + os.write(versionMsg_3_7.getBytes()); + } else { + clientMinor = 3; + os.write(versionMsg_3_3.getBytes()); + } + protocolTightVNC = false; + initCapabilities(); + } + + // + // Negotiate the authentication scheme. + // + + int negotiateSecurity() throws Exception { + return (clientMinor >= 7) ? selectSecurityType() : readSecurityType(); + } + + // + // Read security type from the server (protocol version 3.3). + // + + int readSecurityType() throws Exception { + int secType = readU32(); + + switch (secType) { + case SecTypeInvalid: + readConnFailedReason(); + return SecTypeInvalid; // should never be executed + case SecTypeNone: + case SecTypeVncAuth: + return secType; + default: + throw new Exception("Unknown security type from RFB server: " + + secType); + } + } + + // + // Select security type from the server's list (protocol versions 3.7/3.8). + // + + int selectSecurityType() throws Exception { + int secType = SecTypeInvalid; + + // Read the list of secutiry types. + int nSecTypes = readU8(); + if (nSecTypes == 0) { + readConnFailedReason(); + return SecTypeInvalid; // should never be executed + } + byte[] secTypes = new byte[nSecTypes]; + readFully(secTypes); + + // Find out if the server supports TightVNC protocol extensions + for (int i = 0; i < nSecTypes; i++) { + if (secTypes[i] == SecTypeTight) { + protocolTightVNC = true; + os.write(SecTypeTight); + return SecTypeTight; + } + } + + // Find first supported security type. + for (int i = 0; i < nSecTypes; i++) { + if (secTypes[i] == SecTypeNone || secTypes[i] == SecTypeVncAuth) { + secType = secTypes[i]; + break; + } + } + + if (secType == SecTypeInvalid) { + throw new Exception("Server did not offer supported security type"); + } else { + os.write(secType); + } + + return secType; + } + + // + // Perform "no authentication". + // + + void authenticateNone() throws Exception { + if (clientMinor >= 8) + readSecurityResult("No authentication"); + } + + // + // Perform standard VNC Authentication. + // + + void authenticateVNC(String pw) throws Exception { + byte[] challenge = new byte[16]; + readFully(challenge); + + if (pw.length() > 8) + pw = pw.substring(0, 8); // Truncate to 8 chars + + // Truncate password on the first zero byte. + int firstZero = pw.indexOf(0); + if (firstZero != -1) + pw = pw.substring(0, firstZero); + + byte[] key = { 0, 0, 0, 0, 0, 0, 0, 0 }; + System.arraycopy(pw.getBytes(), 0, key, 0, pw.length()); + + DesCipher des = new DesCipher(key); + + des.encrypt(challenge, 0, challenge, 0); + des.encrypt(challenge, 8, challenge, 8); + + os.write(challenge); + + readSecurityResult("VNC authentication"); + } + + // + // Read security result. + // Throws an exception on authentication failure. + // + + void readSecurityResult(String authType) throws Exception { + int securityResult = readU32(); + + switch (securityResult) { + case VncAuthOK: + System.out.println(authType + ": success"); + break; + case VncAuthFailed: + if (clientMinor >= 8) + readConnFailedReason(); + throw new Exception(authType + ": failed"); + case VncAuthTooMany: + throw new Exception(authType + ": failed, too many tries"); + default: + throw new Exception(authType + ": unknown result " + securityResult); + } + } + + // + // Read the string describing the reason for a connection failure, + // and throw an exception. + // + + void readConnFailedReason() throws Exception { + int reasonLen = readU32(); + byte[] reason = new byte[reasonLen]; + readFully(reason); + throw new Exception(new String(reason)); + } + + // + // Initialize capability lists (TightVNC protocol extensions). + // + + void initCapabilities() { + tunnelCaps = new CapsContainer(); + authCaps = new CapsContainer(); + serverMsgCaps = new CapsContainer(); + clientMsgCaps = new CapsContainer(); + encodingCaps = new CapsContainer(); + + // Supported authentication methods + authCaps.add(AuthNone, StandardVendor, SigAuthNone, "No authentication"); + authCaps.add(AuthVNC, StandardVendor, SigAuthVNC, + "Standard VNC password authentication"); + + // Supported non-standard server-to-client messages + // [NONE] + + // Supported non-standard client-to-server messages + // [NONE] + + // Supported encoding types + encodingCaps.add(EncodingCopyRect, StandardVendor, SigEncodingCopyRect, + "Standard CopyRect encoding"); + encodingCaps.add(EncodingRRE, StandardVendor, SigEncodingRRE, + "Standard RRE encoding"); + encodingCaps.add(EncodingCoRRE, StandardVendor, SigEncodingCoRRE, + "Standard CoRRE encoding"); + encodingCaps.add(EncodingHextile, StandardVendor, SigEncodingHextile, + "Standard Hextile encoding"); + encodingCaps.add(EncodingZRLE, StandardVendor, SigEncodingZRLE, + "Standard ZRLE encoding"); + encodingCaps.add(EncodingZlib, TridiaVncVendor, SigEncodingZlib, + "Zlib encoding"); + encodingCaps.add(EncodingTight, TightVncVendor, SigEncodingTight, + "Tight encoding"); + + // Supported pseudo-encoding types + encodingCaps.add(EncodingCompressLevel0, TightVncVendor, + SigEncodingCompressLevel0, "Compression level"); + encodingCaps.add(EncodingQualityLevel0, TightVncVendor, + SigEncodingQualityLevel0, "JPEG quality level"); + encodingCaps.add(EncodingXCursor, TightVncVendor, SigEncodingXCursor, + "X-style cursor shape update"); + encodingCaps.add(EncodingRichCursor, TightVncVendor, + SigEncodingRichCursor, "Rich-color cursor shape update"); + encodingCaps.add(EncodingPointerPos, TightVncVendor, + SigEncodingPointerPos, "Pointer position update"); + encodingCaps.add(EncodingLastRect, TightVncVendor, SigEncodingLastRect, + "LastRect protocol extension"); + encodingCaps.add(EncodingNewFBSize, TightVncVendor, + SigEncodingNewFBSize, "Framebuffer size change"); + } + + // + // Setup tunneling (TightVNC protocol extensions) + // + + void setupTunneling() throws IOException { + int nTunnelTypes = readU32(); + if (nTunnelTypes != 0) { + readCapabilityList(tunnelCaps, nTunnelTypes); + + // We don't support tunneling yet. + writeInt(NoTunneling); + } + } + + // + // Negotiate authentication scheme (TightVNC protocol extensions) + // + + int negotiateAuthenticationTight() throws Exception { + int nAuthTypes = readU32(); + if (nAuthTypes == 0) + return AuthNone; + + readCapabilityList(authCaps, nAuthTypes); + for (int i = 0; i < authCaps.numEnabled(); i++) { + int authType = authCaps.getByOrder(i); + if (authType == AuthNone || authType == AuthVNC) { + writeInt(authType); + return authType; + } + } + throw new Exception("No suitable authentication scheme found"); + } + + // + // Read a capability list (TightVNC protocol extensions) + // + + void readCapabilityList(CapsContainer caps, int count) throws IOException { + int code; + byte[] vendor = new byte[4]; + byte[] name = new byte[8]; + for (int i = 0; i < count; i++) { + code = readU32(); + readFully(vendor); + readFully(name); + caps.enable(new CapabilityInfo(code, vendor, name)); + } + } + + // + // Write a 32-bit integer into the output stream. + // + + void writeInt(int value) throws IOException { + byte[] b = new byte[4]; + b[0] = (byte) ((value >> 24) & 0xff); + b[1] = (byte) ((value >> 16) & 0xff); + b[2] = (byte) ((value >> 8) & 0xff); + b[3] = (byte) (value & 0xff); + os.write(b); + } + + // + // Write the client initialisation message + // + + void writeClientInit() throws IOException { +/* + if (viewer.options.shareDesktop) { + os.write(1); +*/ + os.write(0); + +// viewer.options.disableShareDesktop(); + } + + // + // Read the server initialisation message + // + + String desktopName; + int framebufferWidth, framebufferHeight; + int bitsPerPixel, depth; + boolean bigEndian, trueColour; + int redMax, greenMax, blueMax, redShift, greenShift, blueShift; + + void readServerInit() throws IOException { + + framebufferWidth = readU16(); + framebufferHeight = readU16(); + bitsPerPixel = readU8(); + depth = readU8(); + bigEndian = (readU8() != 0); + trueColour = (readU8() != 0); + redMax = readU16(); + greenMax = readU16(); + blueMax = readU16(); + redShift = readU8(); + greenShift = readU8(); + blueShift = readU8(); + byte[] pad = new byte[3]; + readFully(pad); + int nameLength = readU32(); + byte[] name = new byte[nameLength]; + readFully(name); + desktopName = new String(name); + + // Read interaction capabilities (TightVNC protocol extensions) + if (protocolTightVNC) { + int nServerMessageTypes = readU16(); + int nClientMessageTypes = readU16(); + int nEncodingTypes = readU16(); + readU16(); + readCapabilityList(serverMsgCaps, nServerMessageTypes); + readCapabilityList(clientMsgCaps, nClientMessageTypes); + readCapabilityList(encodingCaps, nEncodingTypes); + } + + inNormalProtocol = true; + } + + // + // Create session file and write initial protocol messages into it. + // + + void startSession(String fname) throws IOException { + rec = new SessionRecorder(fname); + rec.writeHeader(); + rec.write(versionMsg_3_3.getBytes()); + rec.writeIntBE(SecTypeNone); + rec.writeShortBE(framebufferWidth); + rec.writeShortBE(framebufferHeight); + byte[] fbsServerInitMsg = { 32, 24, 0, 1, 0, (byte) 0xFF, 0, + (byte) 0xFF, 0, (byte) 0xFF, 16, 8, 0, 0, 0, 0 }; + rec.write(fbsServerInitMsg); + rec.writeIntBE(desktopName.length()); + rec.write(desktopName.getBytes()); + numUpdatesInSession = 0; + + // FIXME: If there were e.g. ZRLE updates only, that should not + // affect recording of Zlib and Tight updates. So, actually + // we should maintain separate flags for Zlib, ZRLE and + // Tight, instead of one ``wereZlibUpdates'' variable. + // + if (wereZlibUpdates) + recordFromBeginning = false; + + zlibWarningShown = false; + tightWarningShown = false; + } + + // + // Close session file. + // + + void closeSession() throws IOException { + if (rec != null) { + rec.close(); + rec = null; + } + } + + // + // Set new framebuffer size + // + + void setFramebufferSize(int width, int height) { + framebufferWidth = width; + framebufferHeight = height; + } + + // + // Read the server message type + // + + int readServerMessageType() throws IOException { + int msgType = readU8(); + + // If the session is being recorded: + if (rec != null) { + if (msgType == Bell) { // Save Bell messages in session files. + rec.writeByte(msgType); + if (numUpdatesInSession > 0) + rec.flush(); + } + } + + return msgType; + } + + // + // Read a FramebufferUpdate message + // + + int updateNRects; + + void readFramebufferUpdate() throws IOException { + skipBytes(1); + updateNRects = readU16(); + // If the session is being recorded: + if (rec != null) { + rec.writeByte(FramebufferUpdate); + rec.writeByte(0); + rec.writeShortBE(updateNRects); + } + + numUpdatesInSession++; + } + + // Read a FramebufferUpdate rectangle header + + int updateRectX, updateRectY, updateRectW, updateRectH, updateRectEncoding; + + void readFramebufferUpdateRectHdr() throws Exception { + updateRectX = readU16(); + updateRectY = readU16(); + updateRectW = readU16(); + updateRectH = readU16(); + updateRectEncoding = readU32(); + // System.out.println("readU16&32"); + + if (updateRectEncoding == EncodingZlib + || updateRectEncoding == EncodingZRLE + || updateRectEncoding == EncodingTight) + wereZlibUpdates = true; + + // If the session is being recorded: + if (rec != null) { + if (numUpdatesInSession > 1) + rec.flush(); // Flush the output on each rectangle. + rec.writeShortBE(updateRectX); + rec.writeShortBE(updateRectY); + rec.writeShortBE(updateRectW); + rec.writeShortBE(updateRectH); + if (updateRectEncoding == EncodingZlib && !recordFromBeginning) { + // Here we cannot write Zlib-encoded rectangles because the + // decoder won't be able to reproduce zlib stream state. + if (!zlibWarningShown) { + System.out.println("Warning: Raw encoding will be used " + + "instead of Zlib in recorded session."); + zlibWarningShown = true; + } + rec.writeIntBE(EncodingRaw); + } else { + rec.writeIntBE(updateRectEncoding); + if (updateRectEncoding == EncodingTight && !recordFromBeginning + && !tightWarningShown) { + System.out.println("Warning: Re-compressing Tight-encoded " + + "updates for session recording."); + tightWarningShown = true; + } + } + } + + if (updateRectEncoding < 0 || updateRectEncoding > MaxNormalEncoding) + return; + + if (updateRectX + updateRectW > framebufferWidth + || updateRectY + updateRectH > framebufferHeight) { + throw new Exception("Framebuffer update rectangle too large: " + + updateRectW + "x" + updateRectH + " at (" + updateRectX + + "," + updateRectY + ")"); + } + } + + // Read CopyRect source X and Y. + + int copyRectSrcX, copyRectSrcY; + + void readCopyRect() throws IOException { + copyRectSrcX = readU16(); + copyRectSrcY = readU16(); + + // If the session is being recorded: + if (rec != null) { + rec.writeShortBE(copyRectSrcX); + rec.writeShortBE(copyRectSrcY); + } + } + + // + // Read a ServerCutText message + // + + String readServerCutText() throws IOException { + skipBytes(3); + int len = readU32(); + byte[] text = new byte[len]; + readFully(text); + return new String(text); + } + + // + // Read an integer in compact representation (1..3 bytes). + // Such format is used as a part of the Tight encoding. + // Also, this method records data if session recording is active and + // the viewer's recordFromBeginning variable is set to true. + // + + int readCompactLen() throws IOException { + int[] portion = new int[3]; + portion[0] = readU8(); + int byteCount = 1; + int len = portion[0] & 0x7F; + if ((portion[0] & 0x80) != 0) { + portion[1] = readU8(); + byteCount++; + len |= (portion[1] & 0x7F) << 7; + if ((portion[1] & 0x80) != 0) { + portion[2] = readU8(); + byteCount++; + len |= (portion[2] & 0xFF) << 14; + } + } + + if (rec != null && recordFromBeginning) + for (int i = 0; i < byteCount; i++) + rec.writeByte(portion[i]); + + return len; + } + + // + // Write a FramebufferUpdateRequest message + // + + void writeFramebufferUpdateRequest(int x, int y, int w, int h, + boolean incremental) throws IOException { + byte[] b = new byte[10]; + + b[0] = (byte) FramebufferUpdateRequest; + b[1] = (byte) (incremental ? 1 : 0); + b[2] = (byte) ((x >> 8) & 0xff); + b[3] = (byte) (x & 0xff); + b[4] = (byte) ((y >> 8) & 0xff); + b[5] = (byte) (y & 0xff); + b[6] = (byte) ((w >> 8) & 0xff); + b[7] = (byte) (w & 0xff); + b[8] = (byte) ((h >> 8) & 0xff); + b[9] = (byte) (h & 0xff); + + os.write(b); + } + + // + // Write a SetPixelFormat message + // + + void writeSetPixelFormat(int bitsPerPixel, int depth, boolean bigEndian, + boolean trueColour, int redMax, int greenMax, int blueMax, + int redShift, int greenShift, int blueShift) throws IOException { + byte[] b = new byte[20]; + + b[0] = (byte) SetPixelFormat; + b[4] = (byte) bitsPerPixel; + b[5] = (byte) depth; + b[6] = (byte) (bigEndian ? 1 : 0); + b[7] = (byte) (trueColour ? 1 : 0); + b[8] = (byte) ((redMax >> 8) & 0xff); + b[9] = (byte) (redMax & 0xff); + b[10] = (byte) ((greenMax >> 8) & 0xff); + b[11] = (byte) (greenMax & 0xff); + b[12] = (byte) ((blueMax >> 8) & 0xff); + b[13] = (byte) (blueMax & 0xff); + b[14] = (byte) redShift; + b[15] = (byte) greenShift; + b[16] = (byte) blueShift; + + os.write(b); + } + + // + // Write a FixColourMapEntries message. The values in the red, green and + // blue arrays are from 0 to 65535. + // + + void writeFixColourMapEntries(int firstColour, int nColours, int[] red, + int[] green, int[] blue) throws IOException { + byte[] b = new byte[6 + nColours * 6]; + + b[0] = (byte) FixColourMapEntries; + b[2] = (byte) ((firstColour >> 8) & 0xff); + b[3] = (byte) (firstColour & 0xff); + b[4] = (byte) ((nColours >> 8) & 0xff); + b[5] = (byte) (nColours & 0xff); + + for (int i = 0; i < nColours; i++) { + b[6 + i * 6] = (byte) ((red[i] >> 8) & 0xff); + b[6 + i * 6 + 1] = (byte) (red[i] & 0xff); + b[6 + i * 6 + 2] = (byte) ((green[i] >> 8) & 0xff); + b[6 + i * 6 + 3] = (byte) (green[i] & 0xff); + b[6 + i * 6 + 4] = (byte) ((blue[i] >> 8) & 0xff); + b[6 + i * 6 + 5] = (byte) (blue[i] & 0xff); + } + + os.write(b); + } + + // + // Write a SetEncodings message + // + + void writeSetEncodings(int[] encs, int len) throws IOException { + byte[] b = new byte[4 + 4 * len]; + + b[0] = (byte) SetEncodings; + b[2] = (byte) ((len >> 8) & 0xff); + b[3] = (byte) (len & 0xff); + + for (int i = 0; i < len; i++) { + b[4 + 4 * i] = (byte) ((encs[i] >> 24) & 0xff); + b[5 + 4 * i] = (byte) ((encs[i] >> 16) & 0xff); + b[6 + 4 * i] = (byte) ((encs[i] >> 8) & 0xff); + b[7 + 4 * i] = (byte) (encs[i] & 0xff); + } + + os.write(b); + } + + // + // Write a ClientCutText message + // + + void writeClientCutText(String text) throws IOException { + byte[] b = new byte[8 + text.length()]; + + b[0] = (byte) ClientCutText; + b[4] = (byte) ((text.length() >> 24) & 0xff); + b[5] = (byte) ((text.length() >> 16) & 0xff); + b[6] = (byte) ((text.length() >> 8) & 0xff); + b[7] = (byte) (text.length() & 0xff); + + System.arraycopy(text.getBytes(), 0, b, 8, text.length()); + + os.write(b); + } + + // + // A buffer for putting pointer and keyboard events before being sent. This + // is to ensure that multiple RFB events generated from a single Java Event + // will all be sent in a single network packet. The maximum possible + // length is 4 modifier down events, a single key event followed by 4 + // modifier up events i.e. 9 key events or 72 bytes. + // + + byte[] eventBuf = new byte[72]; + int eventBufLen; + + // Useful shortcuts for modifier masks. + + final static int CTRL_MASK = InputEvent.CTRL_MASK; + final static int SHIFT_MASK = InputEvent.SHIFT_MASK; + final static int META_MASK = InputEvent.META_MASK; + final static int ALT_MASK = InputEvent.ALT_MASK; + + // + // Write a pointer event message. We may need to send modifier key events + // around it to set the correct modifier state. + // + + int pointerMask = 0; + + void writePointerEvent(MouseEvent evt) throws IOException { + int modifiers = evt.getModifiers(); + + int mask2 = 2; + int mask3 = 4; + if (viewer.options.reverseMouseButtons2And3) { + mask2 = 4; + mask3 = 2; + } + + // Note: For some reason, AWT does not set BUTTON1_MASK on left + // button presses. Here we think that it was the left button if + // modifiers do not include BUTTON2_MASK or BUTTON3_MASK. + + if (evt.getID() == MouseEvent.MOUSE_PRESSED) { + if ((modifiers & InputEvent.BUTTON2_MASK) != 0) { + pointerMask = mask2; + modifiers &= ~ALT_MASK; + } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) { + pointerMask = mask3; + modifiers &= ~META_MASK; + } else { + pointerMask = 1; + } + } else if (evt.getID() == MouseEvent.MOUSE_RELEASED) { + pointerMask = 0; + if ((modifiers & InputEvent.BUTTON2_MASK) != 0) { + modifiers &= ~ALT_MASK; + } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) { + modifiers &= ~META_MASK; + } + } + + eventBufLen = 0; + writeModifierKeyEvents(modifiers); + + int x = evt.getX(); + int y = evt.getY(); + + if (x < 0) + x = 0; + if (y < 0) + y = 0; + + eventBuf[eventBufLen++] = (byte) PointerEvent; + eventBuf[eventBufLen++] = (byte) pointerMask; + eventBuf[eventBufLen++] = (byte) ((x >> 8) & 0xff); + eventBuf[eventBufLen++] = (byte) (x & 0xff); + eventBuf[eventBufLen++] = (byte) ((y >> 8) & 0xff); + eventBuf[eventBufLen++] = (byte) (y & 0xff); + + // + // Always release all modifiers after an "up" event + // + + if (pointerMask == 0) { + writeModifierKeyEvents(0); + } + + os.write(eventBuf, 0, eventBufLen); + } + + // + // Write a key event message. We may need to send modifier key events + // around it to set the correct modifier state. Also we need to translate + // from the Java key values to the X keysym values used by the RFB protocol. + // + + void writeKeyEvent(KeyEvent evt) throws IOException { + + int keyChar = evt.getKeyChar(); + + // + // Ignore event if only modifiers were pressed. + // + + // Some JVMs return 0 instead of CHAR_UNDEFINED in getKeyChar(). + if (keyChar == 0) + keyChar = KeyEvent.CHAR_UNDEFINED; + + if (keyChar == KeyEvent.CHAR_UNDEFINED) { + int code = evt.getKeyCode(); + if (code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_SHIFT + || code == KeyEvent.VK_META || code == KeyEvent.VK_ALT) + return; + } + + // + // Key press or key release? + // + + boolean down = (evt.getID() == KeyEvent.KEY_PRESSED); + + int key; + if (evt.isActionKey()) { + + // + // An action key should be one of the following. + // If not then just ignore the event. + // + + switch (evt.getKeyCode()) { + case KeyEvent.VK_HOME: + key = 0xff50; + break; + case KeyEvent.VK_LEFT: + key = 0xff51; + break; + case KeyEvent.VK_UP: + key = 0xff52; + break; + case KeyEvent.VK_RIGHT: + key = 0xff53; + break; + case KeyEvent.VK_DOWN: + key = 0xff54; + break; + case KeyEvent.VK_PAGE_UP: + key = 0xff55; + break; + case KeyEvent.VK_PAGE_DOWN: + key = 0xff56; + break; + case KeyEvent.VK_END: + key = 0xff57; + break; + case KeyEvent.VK_INSERT: + key = 0xff63; + break; + case KeyEvent.VK_F1: + key = 0xffbe; + break; + case KeyEvent.VK_F2: + key = 0xffbf; + break; + case KeyEvent.VK_F3: + key = 0xffc0; + break; + case KeyEvent.VK_F4: + key = 0xffc1; + break; + case KeyEvent.VK_F5: + key = 0xffc2; + break; + case KeyEvent.VK_F6: + key = 0xffc3; + break; + case KeyEvent.VK_F7: + key = 0xffc4; + break; + case KeyEvent.VK_F8: + key = 0xffc5; + break; + case KeyEvent.VK_F9: + key = 0xffc6; + break; + case KeyEvent.VK_F10: + key = 0xffc7; + break; + case KeyEvent.VK_F11: + key = 0xffc8; + break; + case KeyEvent.VK_F12: + key = 0xffc9; + break; + default: + return; + } + + } else { + + // + // A "normal" key press. Ordinary ASCII characters go straight + // through. + // For CTRL-<letter>, CTRL is sent separately so just send <letter>. + // Backspace, tab, return, escape and delete have special keysyms. + // Anything else we ignore. + // + + key = keyChar; + + if (key < 0x20) { + if (evt.isControlDown()) { + key += 0x60; + } else { + switch (key) { + case KeyEvent.VK_BACK_SPACE: + key = 0xff08; + break; + case KeyEvent.VK_TAB: + key = 0xff09; + break; + case KeyEvent.VK_ENTER: + key = 0xff0d; + break; + case KeyEvent.VK_ESCAPE: + key = 0xff1b; + break; + } + } + } else if (key == 0x7f) { + // Delete + key = 0xffff; + } else if (key > 0xff) { + // JDK1.1 on X incorrectly passes some keysyms straight through, + // so we do too. JDK1.1.4 seems to have fixed this. + // The keysyms passed are 0xff00 .. XK_BackSpace .. XK_Delete + // Also, we pass through foreign currency keysyms + // (0x20a0..0x20af). + if ((key < 0xff00 || key > 0xffff) + && !(key >= 0x20a0 && key <= 0x20af)) + return; + } + } + + // Fake keyPresses for keys that only generates keyRelease events + if ((key == 0xe5) || (key == 0xc5) || // XK_aring / XK_Aring + (key == 0xe4) || (key == 0xc4) || // XK_adiaeresis / + // XK_Adiaeresis + (key == 0xf6) || (key == 0xd6) || // XK_odiaeresis / + // XK_Odiaeresis + (key == 0xa7) || (key == 0xbd) || // XK_section / XK_onehalf + (key == 0xa3)) { // XK_sterling + // Make sure we do not send keypress events twice on platforms + // with correct JVMs (those that actually report KeyPress for all + // keys) + if (down) + brokenKeyPressed = true; + + if (!down && !brokenKeyPressed) { + // We've got a release event for this key, but haven't received + // a press. Fake it. + eventBufLen = 0; + writeModifierKeyEvents(evt.getModifiers()); + writeKeyEvent(key, true); + os.write(eventBuf, 0, eventBufLen); + } + + if (!down) + brokenKeyPressed = false; + } + + eventBufLen = 0; + writeModifierKeyEvents(evt.getModifiers()); + writeKeyEvent(key, down); + + // Always release all modifiers after an "up" event + if (!down) + writeModifierKeyEvents(0); + + //os.write(eventBuf, 0, eventBufLen); + } + + // + // Add a raw key event with the given X keysym to eventBuf. + // + + void writeKeyEvent(int keysym, boolean down) { + eventBuf[eventBufLen++] = (byte) KeyboardEvent; + eventBuf[eventBufLen++] = (byte) (down ? 1 : 0); + eventBuf[eventBufLen++] = (byte) 0; + eventBuf[eventBufLen++] = (byte) 0; + eventBuf[eventBufLen++] = (byte) ((keysym >> 24) & 0xff); + eventBuf[eventBufLen++] = (byte) ((keysym >> 16) & 0xff); + eventBuf[eventBufLen++] = (byte) ((keysym >> 8) & 0xff); + eventBuf[eventBufLen++] = (byte) (keysym & 0xff); + } + + // + // Write key events to set the correct modifier state. + // + + int oldModifiers = 0; + + void writeModifierKeyEvents(int newModifiers) { + if ((newModifiers & CTRL_MASK) != (oldModifiers & CTRL_MASK)) + writeKeyEvent(0xffe3, (newModifiers & CTRL_MASK) != 0); + + if ((newModifiers & SHIFT_MASK) != (oldModifiers & SHIFT_MASK)) + writeKeyEvent(0xffe1, (newModifiers & SHIFT_MASK) != 0); + + if ((newModifiers & META_MASK) != (oldModifiers & META_MASK)) + writeKeyEvent(0xffe7, (newModifiers & META_MASK) != 0); + + if ((newModifiers & ALT_MASK) != (oldModifiers & ALT_MASK)) + writeKeyEvent(0xffe9, (newModifiers & ALT_MASK) != 0); + + oldModifiers = newModifiers; + } + + // + // Compress and write the data into the recorded session file. This + // method assumes the recording is on (rec != null). + // + + void recordCompressedData(byte[] data, int off, int len) throws IOException { + Deflater deflater = new Deflater(); + deflater.setInput(data, off, len); + int bufSize = len + len / 100 + 12; + byte[] buf = new byte[bufSize]; + deflater.finish(); + int compressedSize = deflater.deflate(buf); + recordCompactLen(compressedSize); + rec.write(buf, 0, compressedSize); + } + + void recordCompressedData(byte[] data) throws IOException { + recordCompressedData(data, 0, data.length); + } + + // + // Write an integer in compact representation (1..3 bytes) into the + // recorded session file. This method assumes the recording is on + // (rec != null). + // + + void recordCompactLen(int len) throws IOException { + byte[] buf = new byte[3]; + int bytes = 0; + buf[bytes++] = (byte) (len & 0x7F); + if (len > 0x7F) { + buf[bytes - 1] |= 0x80; + buf[bytes++] = (byte) (len >> 7 & 0x7F); + if (len > 0x3FFF) { + buf[bytes - 1] |= 0x80; + buf[bytes++] = (byte) (len >> 14 & 0xFF); + } + } + rec.write(buf, 0, bytes); + } + + public void startTiming() { + timing = true; + + // Carry over up to 1s worth of previous rate for smoothing. + + if (timeWaitedIn100us > 10000) { + timedKbits = timedKbits * 10000 / timeWaitedIn100us; + timeWaitedIn100us = 10000; + } + } + + public void stopTiming() { + timing = false; + if (timeWaitedIn100us < timedKbits / 2) + timeWaitedIn100us = timedKbits / 2; // upper limit 20Mbit/s + } + + public long kbitsPerSecond() { + return timedKbits * 10000 / timeWaitedIn100us; + } + + public long timeWaited() { + return timeWaitedIn100us; + } + + // + // Methods for reading data via our DataInputStream member variable (is). + // + // In addition to reading data, the readFully() methods updates variables + // used to estimate data throughput. + // + + public void readFully(byte b[]) throws IOException { + readFully(b, 0, b.length); + } + + public void readFully(byte b[], int off, int len) throws IOException { + long before = 0; + if (timing) + before = System.currentTimeMillis(); + + is.readFully(b, off, len); + + if (timing) { + long after = System.currentTimeMillis(); + long newTimeWaited = (after - before) * 10; + int newKbits = len * 8 / 1000; + + // limit rate to between 10kbit/s and 40Mbit/s + + if (newTimeWaited > newKbits * 1000) + newTimeWaited = newKbits * 1000; + if (newTimeWaited < newKbits / 4) + newTimeWaited = newKbits / 4; + + timeWaitedIn100us += newTimeWaited; + timedKbits += newKbits; + } + + numBytesRead += len; + } + + final int available() throws IOException { + return is.available(); + } + + // FIXME: DataInputStream::skipBytes() is not guaranteed to skip + // exactly n bytes. Probably we don't want to use this method. + final int skipBytes(int n) throws IOException { + int r = is.skipBytes(n); + numBytesRead += r; + return r; + } + + final int readU8() throws IOException { + int r = is.readUnsignedByte(); + numBytesRead++; + + return r; + } + + final int readU16() throws IOException { + int r = is.readUnsignedShort(); + numBytesRead += 2; + return r; + } + + final int readU32() throws IOException { + int r = is.readInt(); + numBytesRead += 4; + return r; + } + +}
--- a/src/myVncClient/MyVncClient.java Wed Jul 06 11:31:30 2011 +0900 +++ b/src/myVncClient/MyVncClient.java Wed Jul 06 11:56:15 2011 +0900 @@ -174,6 +174,12 @@ fatalError("Network error: could not connect to server: " + host + ":" + port, e); } catch (EOFException e) { + + //insert + echo = new echoClient(); + echo.openport(); + echo.losthost(); + if (showOfflineDesktop) { e.printStackTrace(); System.out @@ -637,22 +643,20 @@ // void readParameters() { -// host = readParameter("HOST", !inAnApplet); - if (mainArgs.length > 0) - host = mainArgs[0]; - else - host = "hades.cr.ie.u-ryukyu.ac.jp"; - /* - * if (host == null) { host = getCodeBase().getHost(); if - * (host.equals("")) { fatalError("HOST parameter not specified"); } } - */ + if (mainArgs.length > 0){ + host = mainArgs[0]; + echo = new echoClient(host); + }else{ + host = "cls080.ie.u-ryukyu.ac.jp"; + echo = new echoClient(); + } -// port = readIntParameter("PORT", 5550); + if (mainArgs.length > 1) port = Integer.parseInt(mainArgs[1]); else port = 5550; - // Read "ENCPASSWORD" or "PASSWORD" parameter if specified. + readPasswordParameters(); String str;
--- a/src/myVncClient/VncViewer.java Wed Jul 06 11:31:30 2011 +0900 +++ b/src/myVncClient/VncViewer.java Wed Jul 06 11:56:15 2011 +0900 @@ -31,7 +31,7 @@ MyRfbProto rfb; Thread rfbThread; Thread accThread; - + echoClient echo; Frame vncFrame; Container vncContainer; @@ -206,6 +206,8 @@ fatalError("Network error: could not connect to server: " + host + ":" + port, e); } catch (EOFException e) { + + if (showOfflineDesktop) { e.printStackTrace(); System.out
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/myVncClient/echoClient.java Wed Jul 06 11:56:15 2011 +0900 @@ -0,0 +1,217 @@ +package myVncClient; + +import java.io.*; +import java.net.*; + + +public class echoClient { + Revalue value = new Revalue(); + waitreply wr;// = new waitreply(); + String responseLine,treenum,parent,line; + Socket echoSocket = null; + BufferedReader lostis = null; + DataOutputStream os = null; + PrintStream lostos; + BufferedReader is = null; + Socket clientSocket = null; + ServerSocket echoServer=null; + + String host; + + echoClient(){ + wr = new waitreply(this); + } + echoClient(String _host){ + wr = new waitreply(this); + host = _host; + } + + // Revalue hostn(String args){ + void openport(){ + // ソケットや入出力用のストリームの宣言 + + // ポート9999番を開く + try { + if(host == null){ + echoSocket = new Socket("133.13.48.18", 9999); + System.out.println(); + } + else{ + echoSocket = new Socket(host, 9999); + } + os = new DataOutputStream(echoSocket.getOutputStream()); + is = new BufferedReader(new InputStreamReader(echoSocket.getInputStream())); + } catch (UnknownHostException e) { + System.err.println("Don't know about host: localhost"); + } catch (IOException e) { + System.err.println("Couldn't get I/O for the connection to: localhost"); + } + } + + + + /* + try { + echoServer = new ServerSocket(9998); + } + catch (IOException e) { + System.out.println(e); + } + + try { + //clientSocket = echoServer.accept(); + lostis = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + lostos = new PrintStream(clientSocket.getOutputStream()); + while (true){ + line = is.readLine(); + } + } + catch (IOException e){ + System.out.println(e); + } + */ + +/* + catch(InterruptedException e){ + e.printStackTrace(); + } +*/ + + + + Revalue hostn(String args){ + + // サーバーにメッセージを送る + if (echoSocket != null && os != null && is != null) { + try { + //ip情報を取得する + InetAddress addr = InetAddress.getLocalHost(); + //System.out.println(addr.getHostAddress()); + String add = new String(addr.getHostAddress()); + + // メッセージを送ります + os.writeBytes(add + "\n"); + os.writeBytes(args + "\n"); + + if ((value.responseLine = is.readLine()) != null) { + System.out.println("Server: " + value.responseLine); + } + if ((value.parent = is.readLine()) != null) { + System.out.println("parent: " + value.parent); + } + if ((value.treenum = is.readLine()) != null) { + System.out.println("treenum: " + value.treenum); + } + + // 開いたソケットなどをクローズ + os.close(); + is.close(); + echoSocket.close(); + } catch (UnknownHostException e) { + System.err.println("Trying to connect to unknown host: " + e); + } catch (IOException e) { + System.err.println("IOException: " + e); + + } + wr.start(); + } + return value; + } + + Revalue losthost(){ + if (echoSocket != null && os != null && is != null) { + try { + + //落ちた番号を報告 + os.writeBytes("1\n"); + os.writeBytes(value.parent + "\n"); + + + + if ((value.responseLine = is.readLine()) != null) { + System.out.println("Server: " + value.responseLine); + } + if ((value.parent = is.readLine()) != null) { + System.out.println("parent: " + value.parent); + } + if ((value.treenum = is.readLine()) != null) { + System.out.println("treenum: " + value.treenum); + } + + // 開いたソケットなどをクローズ + os.close(); + is.close(); + echoSocket.close(); + + } catch (UnknownHostException e) { + System.err.println("Trying to connect to unknown host: " + e); + } catch (IOException e) { + System.err.println("IOException: " + e); + } + } + return value; + } + + void lostchild(){ + if (echoSocket != null && os != null && is != null) { + try { + + //自分の番号を報告 + os.writeBytes("2\n"); + os.writeBytes(value.treenum + "\n"); + + os.close(); + is.close(); + echoSocket.close(); + + } catch (UnknownHostException e) { + System.err.println("Trying to connect to unknown host: " + e); + } catch (IOException e) { + System.err.println("IOException: " + e); + } + } + } + + Revalue Interruption(Socket clientSocket){ + + try { + clientSocket = echoServer.accept(); + lostis = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + lostos = new PrintStream(clientSocket.getOutputStream()); + while (true){ + line = lostis.readLine(); + } + }catch (IOException e){ + System.out.println(e); + } + try{ + echoServer.close(); + } + catch (IOException e){ + System.out.println(e); + } + try{ + if ((value.responseLine = lostis.readLine()) != null) { + System.out.println("Server: " + value.responseLine); + } + if ((value.parent = lostis.readLine()) != null) { + System.out.println("parent: " + value.parent); + } + if ((value.treenum = lostis.readLine()) != null) { + System.out.println("treenum: " + value.treenum); + } + } + catch (UnknownHostException e) { + System.err.println("Trying to connect to unknown host: " + e); + } catch (IOException e) { + System.err.println("IOException: " + e); + } + return value; + } +} + +class Revalue{ + public String responseLine; + public String parent; + public String treenum; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/myVncClient/waitreply.java Wed Jul 06 11:56:15 2011 +0900 @@ -0,0 +1,33 @@ +package myVncClient; + +import java.net.*; +import java.io.*; + + +public class waitreply extends Thread{ + echoClient echo; + Socket clientSocket = null; + ServerSocket echoServer=null; + + + waitreply(echoClient _echo){ + echo = _echo; + } + public void run(){ + try { + echoServer = new ServerSocket(9998); + } + catch (IOException e) { + System.out.println(e); + } + while(true){ + try { + clientSocket = echoServer.accept(); + echo.Interruption(clientSocket); + }catch (IOException e){ + System.out.println(e); + } + } + } +} +