|
@@ -10,1972 +10,1874 @@
|
|
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
|
|
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
|
|
*/
|
|
*/
|
|
|
|
|
|
-/*jslint white: false, browser: true, bitwise: false, plusplus: false */
|
|
|
|
|
|
+/*jslint white: false, browser: true */
|
|
/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
|
|
/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
|
|
|
|
|
|
|
|
+var RFB;
|
|
|
|
|
|
-function RFB(defaults) {
|
|
|
|
-"use strict";
|
|
|
|
-
|
|
|
|
-var that = {}, // Public API methods
|
|
|
|
- conf = {}, // Configuration attributes
|
|
|
|
-
|
|
|
|
- // Pre-declare private functions used before definitions (jslint)
|
|
|
|
- init_vars, updateState, fail, handle_message,
|
|
|
|
- init_msg, normal_msg, framebufferUpdate, print_stats,
|
|
|
|
-
|
|
|
|
- pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests,
|
|
|
|
- keyEvent, pointerEvent, clientCutText,
|
|
|
|
-
|
|
|
|
- getTightCLength, extract_data_uri,
|
|
|
|
- keyPress, mouseButton, mouseMove,
|
|
|
|
-
|
|
|
|
- checkEvents, // Overridable for testing
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- //
|
|
|
|
- // Private RFB namespace variables
|
|
|
|
- //
|
|
|
|
- rfb_host = '',
|
|
|
|
- rfb_port = 5900,
|
|
|
|
- rfb_password = '',
|
|
|
|
- rfb_path = '',
|
|
|
|
-
|
|
|
|
- rfb_state = 'disconnected',
|
|
|
|
- rfb_version = 0,
|
|
|
|
- rfb_max_version= 3.8,
|
|
|
|
- rfb_auth_scheme= '',
|
|
|
|
- rfb_tightvnc = false,
|
|
|
|
-
|
|
|
|
- rfb_xvp_ver = 0,
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- // In preference order
|
|
|
|
- encodings = [
|
|
|
|
- ['COPYRECT', 0x01 ],
|
|
|
|
- ['TIGHT', 0x07 ],
|
|
|
|
- ['TIGHT_PNG', -260 ],
|
|
|
|
- ['HEXTILE', 0x05 ],
|
|
|
|
- ['RRE', 0x02 ],
|
|
|
|
- ['RAW', 0x00 ],
|
|
|
|
- ['DesktopSize', -223 ],
|
|
|
|
- ['Cursor', -239 ],
|
|
|
|
-
|
|
|
|
- // Psuedo-encoding settings
|
|
|
|
- //['JPEG_quality_lo', -32 ],
|
|
|
|
- ['JPEG_quality_med', -26 ],
|
|
|
|
- //['JPEG_quality_hi', -23 ],
|
|
|
|
- //['compress_lo', -255 ],
|
|
|
|
- ['compress_hi', -247 ],
|
|
|
|
- ['last_rect', -224 ],
|
|
|
|
- ['xvp', -309 ]
|
|
|
|
- ],
|
|
|
|
-
|
|
|
|
- encHandlers = {},
|
|
|
|
- encNames = {},
|
|
|
|
- encStats = {}, // [rectCnt, rectCntTot]
|
|
|
|
-
|
|
|
|
- ws = null, // Websock object
|
|
|
|
- display = null, // Display object
|
|
|
|
- keyboard = null, // Keyboard input handler object
|
|
|
|
- mouse = null, // Mouse input handler object
|
|
|
|
- sendTimer = null, // Send Queue check timer
|
|
|
|
- disconnTimer = null, // disconnection timer
|
|
|
|
- msgTimer = null, // queued handle_message timer
|
|
|
|
-
|
|
|
|
- // Frame buffer update state
|
|
|
|
- FBU = {
|
|
|
|
- rects : 0,
|
|
|
|
- subrects : 0, // RRE
|
|
|
|
- lines : 0, // RAW
|
|
|
|
- tiles : 0, // HEXTILE
|
|
|
|
- bytes : 0,
|
|
|
|
- x : 0,
|
|
|
|
- y : 0,
|
|
|
|
- width : 0,
|
|
|
|
- height : 0,
|
|
|
|
- encoding : 0,
|
|
|
|
- subencoding : -1,
|
|
|
|
- background : null,
|
|
|
|
- zlibs : [] // TIGHT zlib streams
|
|
|
|
- },
|
|
|
|
-
|
|
|
|
- fb_Bpp = 4,
|
|
|
|
- fb_depth = 3,
|
|
|
|
- fb_width = 0,
|
|
|
|
- fb_height = 0,
|
|
|
|
- fb_name = "",
|
|
|
|
-
|
|
|
|
- rre_chunk_sz = 100,
|
|
|
|
-
|
|
|
|
- timing = {
|
|
|
|
- last_fbu : 0,
|
|
|
|
- fbu_total : 0,
|
|
|
|
- fbu_total_cnt : 0,
|
|
|
|
- full_fbu_total : 0,
|
|
|
|
- full_fbu_cnt : 0,
|
|
|
|
-
|
|
|
|
- fbu_rt_start : 0,
|
|
|
|
- fbu_rt_total : 0,
|
|
|
|
- fbu_rt_cnt : 0,
|
|
|
|
- pixels : 0
|
|
|
|
- },
|
|
|
|
-
|
|
|
|
- test_mode = false,
|
|
|
|
-
|
|
|
|
- /* Mouse state */
|
|
|
|
- mouse_buttonMask = 0,
|
|
|
|
- mouse_arr = [],
|
|
|
|
- viewportDragging = false,
|
|
|
|
- viewportDragPos = {};
|
|
|
|
-
|
|
|
|
-// Configuration attributes
|
|
|
|
-Util.conf_defaults(conf, that, defaults, [
|
|
|
|
- ['target', 'wo', 'dom', null, 'VNC display rendering Canvas object'],
|
|
|
|
- ['focusContainer', 'wo', 'dom', document, 'DOM element that captures keyboard input'],
|
|
|
|
-
|
|
|
|
- ['encrypt', 'rw', 'bool', false, 'Use TLS/SSL/wss encryption'],
|
|
|
|
- ['true_color', 'rw', 'bool', true, 'Request true color pixel data'],
|
|
|
|
- ['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'],
|
|
|
|
- ['shared', 'rw', 'bool', true, 'Request shared mode'],
|
|
|
|
- ['view_only', 'rw', 'bool', false, 'Disable client mouse/keyboard'],
|
|
|
|
- ['xvp_password_sep', 'rw', 'str', '@', 'Separator for XVP password fields'],
|
|
|
|
- ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'],
|
|
|
|
-
|
|
|
|
- ['wsProtocols', 'rw', 'arr', ['binary', 'base64'],
|
|
|
|
- 'Protocols to use in the WebSocket connection'],
|
|
|
|
-
|
|
|
|
- // UltraVNC repeater ID to connect to
|
|
|
|
- ['repeaterID', 'rw', 'str', '', 'RepeaterID to connect to'],
|
|
|
|
-
|
|
|
|
- ['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'],
|
|
|
|
-
|
|
|
|
- // Callback functions
|
|
|
|
- ['onUpdateState', 'rw', 'func', function() { },
|
|
|
|
- 'onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change '],
|
|
|
|
- ['onPasswordRequired', 'rw', 'func', function() { },
|
|
|
|
- 'onPasswordRequired(rfb): VNC password is required '],
|
|
|
|
- ['onClipboard', 'rw', 'func', function() { },
|
|
|
|
- 'onClipboard(rfb, text): RFB clipboard contents received'],
|
|
|
|
- ['onBell', 'rw', 'func', function() { },
|
|
|
|
- 'onBell(rfb): RFB Bell message received '],
|
|
|
|
- ['onFBUReceive', 'rw', 'func', function() { },
|
|
|
|
- 'onFBUReceive(rfb, fbu): RFB FBU received but not yet processed '],
|
|
|
|
- ['onFBUComplete', 'rw', 'func', function() { },
|
|
|
|
- 'onFBUComplete(rfb, fbu): RFB FBU received and processed '],
|
|
|
|
- ['onFBResize', 'rw', 'func', function() { },
|
|
|
|
- 'onFBResize(rfb, width, height): frame buffer resized'],
|
|
|
|
- ['onDesktopName', 'rw', 'func', function() { },
|
|
|
|
- 'onDesktopName(rfb, name): desktop name received'],
|
|
|
|
- ['onXvpInit', 'rw', 'func', function() { },
|
|
|
|
- 'onXvpInit(version): XVP extensions active for this connection'],
|
|
|
|
-
|
|
|
|
- // These callback names are deprecated
|
|
|
|
- ['updateState', 'rw', 'func', function() { },
|
|
|
|
- 'obsolete, use onUpdateState'],
|
|
|
|
- ['clipboardReceive', 'rw', 'func', function() { },
|
|
|
|
- 'obsolete, use onClipboard']
|
|
|
|
- ]);
|
|
|
|
|
|
+(function () {
|
|
|
|
+ "use strict";
|
|
|
|
+ RFB = function (defaults) {
|
|
|
|
+ if (!defaults) {
|
|
|
|
+ defaults = {};
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ this._rfb_host = '';
|
|
|
|
+ this._rfb_port = 5900;
|
|
|
|
+ this._rfb_password = '';
|
|
|
|
+ this._rfb_path = '';
|
|
|
|
+
|
|
|
|
+ this._rfb_state = 'disconnected';
|
|
|
|
+ this._rfb_version = 0;
|
|
|
|
+ this._rfb_max_version = 3.8;
|
|
|
|
+ this._rfb_auth_scheme = '';
|
|
|
|
+
|
|
|
|
+ this._rfb_tightvnc = false;
|
|
|
|
+ this._rfb_xvp_ver = 0;
|
|
|
|
+
|
|
|
|
+ // In preference order
|
|
|
|
+ this._encodings = [
|
|
|
|
+ ['COPYRECT', 0x01 ],
|
|
|
|
+ ['TIGHT', 0x07 ],
|
|
|
|
+ ['TIGHT_PNG', -260 ],
|
|
|
|
+ ['HEXTILE', 0x05 ],
|
|
|
|
+ ['RRE', 0x02 ],
|
|
|
|
+ ['RAW', 0x00 ],
|
|
|
|
+ ['DesktopSize', -223 ],
|
|
|
|
+ ['Cursor', -239 ],
|
|
|
|
+
|
|
|
|
+ // Psuedo-encoding settings
|
|
|
|
+ //['JPEG_quality_lo', -32 ],
|
|
|
|
+ ['JPEG_quality_med', -26 ],
|
|
|
|
+ //['JPEG_quality_hi', -23 ],
|
|
|
|
+ //['compress_lo', -255 ],
|
|
|
|
+ ['compress_hi', -247 ],
|
|
|
|
+ ['last_rect', -224 ],
|
|
|
|
+ ['xvp', -309 ]
|
|
|
|
+ ];
|
|
|
|
+
|
|
|
|
+ this._encHandlers = {};
|
|
|
|
+ this._encNames = {};
|
|
|
|
+ this._encStats = {};
|
|
|
|
+
|
|
|
|
+ this._sock = null; // Websock object
|
|
|
|
+ this._display = null; // Display object
|
|
|
|
+ this._keyboard = null; // Keyboard input handler object
|
|
|
|
+ this._mouse = null; // Mouse input handler object
|
|
|
|
+ this._sendTimer = null; // Send Queue check timer
|
|
|
|
+ this._disconnTimer = null; // disconnection timer
|
|
|
|
+ this._msgTimer = null; // queued handle_msg timer
|
|
|
|
+
|
|
|
|
+ // Frame buffer update state
|
|
|
|
+ this._FBU = {
|
|
|
|
+ rects: 0,
|
|
|
|
+ subrects: 0, // RRE
|
|
|
|
+ lines: 0, // RAW
|
|
|
|
+ tiles: 0, // HEXTILE
|
|
|
|
+ bytes: 0,
|
|
|
|
+ x: 0,
|
|
|
|
+ y: 0,
|
|
|
|
+ width: 0,
|
|
|
|
+ height: 0,
|
|
|
|
+ encoding: 0,
|
|
|
|
+ subencoding: -1,
|
|
|
|
+ background: null,
|
|
|
|
+ zlib: [] // TIGHT zlib streams
|
|
|
|
+ };
|
|
|
|
|
|
-// Override/add some specific configuration getters/setters
|
|
|
|
-that.set_local_cursor = function(cursor) {
|
|
|
|
- if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) {
|
|
|
|
- conf.local_cursor = false;
|
|
|
|
- } else {
|
|
|
|
- if (display.get_cursor_uri()) {
|
|
|
|
- conf.local_cursor = true;
|
|
|
|
- } else {
|
|
|
|
- Util.Warn("Browser does not support local cursor");
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-// These are fake configuration getters
|
|
|
|
-that.get_display = function() { return display; };
|
|
|
|
-
|
|
|
|
-that.get_keyboard = function() { return keyboard; };
|
|
|
|
-
|
|
|
|
-that.get_mouse = function() { return mouse; };
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-//
|
|
|
|
-// Setup routines
|
|
|
|
-//
|
|
|
|
-
|
|
|
|
-// Create the public API interface and initialize values that stay
|
|
|
|
-// constant across connect/disconnect
|
|
|
|
-function constructor() {
|
|
|
|
- var i, rmode;
|
|
|
|
- Util.Debug(">> RFB.constructor");
|
|
|
|
-
|
|
|
|
- // Create lookup tables based encoding number
|
|
|
|
- for (i=0; i < encodings.length; i+=1) {
|
|
|
|
- encHandlers[encodings[i][1]] = encHandlers[encodings[i][0]];
|
|
|
|
- encNames[encodings[i][1]] = encodings[i][0];
|
|
|
|
- encStats[encodings[i][1]] = [0, 0];
|
|
|
|
- }
|
|
|
|
- // Initialize display, mouse, keyboard, and websock
|
|
|
|
- try {
|
|
|
|
- display = new Display({'target': conf.target});
|
|
|
|
- } catch (exc) {
|
|
|
|
- Util.Error("Display exception: " + exc);
|
|
|
|
- updateState('fatal', "No working Display");
|
|
|
|
- }
|
|
|
|
- keyboard = new Keyboard({'target': conf.focusContainer,
|
|
|
|
- 'onKeyPress': keyPress});
|
|
|
|
- mouse = new Mouse({'target': conf.target,
|
|
|
|
- 'onMouseButton': mouseButton,
|
|
|
|
- 'onMouseMove': mouseMove,
|
|
|
|
- 'notify': keyboard.sync});
|
|
|
|
-
|
|
|
|
- rmode = display.get_render_mode();
|
|
|
|
-
|
|
|
|
- ws = new Websock();
|
|
|
|
- ws.on('message', handle_message);
|
|
|
|
- ws.on('open', function() {
|
|
|
|
- if (rfb_state === "connect") {
|
|
|
|
- updateState('ProtocolVersion', "Starting VNC handshake");
|
|
|
|
- } else {
|
|
|
|
- fail("Got unexpected WebSockets connection");
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- ws.on('close', function(e) {
|
|
|
|
- Util.Warn("WebSocket on-close event");
|
|
|
|
- var msg = "";
|
|
|
|
- if (e.code) {
|
|
|
|
- msg = " (code: " + e.code;
|
|
|
|
- if (e.reason) {
|
|
|
|
- msg += ", reason: " + e.reason;
|
|
|
|
- }
|
|
|
|
- msg += ")";
|
|
|
|
|
|
+ this._fb_Bpp = 4;
|
|
|
|
+ this._fb_depth = 3;
|
|
|
|
+ this._fb_width = 0;
|
|
|
|
+ this._fb_height = 0;
|
|
|
|
+ this._fb_name = "";
|
|
|
|
+
|
|
|
|
+ this._rre_chunk_sz = 100;
|
|
|
|
+
|
|
|
|
+ this._timing = {
|
|
|
|
+ last_fbu: 0,
|
|
|
|
+ fbu_total: 0,
|
|
|
|
+ fbu_total_cnt: 0,
|
|
|
|
+ full_fbu_total: 0,
|
|
|
|
+ full_fbu_cnt: 0,
|
|
|
|
+
|
|
|
|
+ fbu_rt_start: 0,
|
|
|
|
+ fbu_rt_total: 0,
|
|
|
|
+ fbu_rt_cnt: 0,
|
|
|
|
+ pixels: 0
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // Mouse state
|
|
|
|
+ this._mouse_buttonMask = 0;
|
|
|
|
+ this._mouse_arr = [];
|
|
|
|
+ this._viewportDragging = false;
|
|
|
|
+ this._viewportDragPos = {};
|
|
|
|
+
|
|
|
|
+ // set the default value on user-facing properties
|
|
|
|
+ Util.set_defaults(this, defaults, {
|
|
|
|
+ 'target': 'null', // VNC display rendering Canvas object
|
|
|
|
+ 'focusContainer': document, // DOM element that captures keyboard input
|
|
|
|
+ 'encrypt': false, // Use TLS/SSL/wss encryption
|
|
|
|
+ 'true_color': true, // Request true color pixel data
|
|
|
|
+ 'local_cursor': false, // Request locally rendered cursor
|
|
|
|
+ 'shared': true, // Request shared mode
|
|
|
|
+ 'view_only': false, // Disable client mouse/keyboard
|
|
|
|
+ 'xvp_password_sep': '@', // Separator for XVP password fields
|
|
|
|
+ 'disconnectTimeout': 3, // Time (s) to wait for disconnection
|
|
|
|
+ 'wsProtocols': ['binary', 'base64'], // Protocols to use in the WebSocket connection
|
|
|
|
+ 'repeaterID': '', // [UltraVNC] RepeaterID to connect to
|
|
|
|
+ 'viewportDrag': false, // Move the viewport on mouse drags
|
|
|
|
+
|
|
|
|
+ // Callback functions
|
|
|
|
+ 'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate, statusMsg): state update/change
|
|
|
|
+ 'onPasswordRequired': function () { }, // onPasswordRequired(rfb): VNC password is required
|
|
|
|
+ 'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received
|
|
|
|
+ 'onBell': function () { }, // onBell(rfb): RFB Bell message received
|
|
|
|
+ 'onFBUReceive': function () { }, // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
|
|
|
|
+ 'onFBUComplete': function () { }, // onFBUComplete(rfb, fbu): RFB FBU received and processed
|
|
|
|
+ 'onFBResize': function () { }, // onFBResize(rfb, width, height): frame buffer resized
|
|
|
|
+ 'onDesktopName': function () { }, // onDesktopName(rfb, name): desktop name received
|
|
|
|
+ 'onXvpInit': function () { }, // onXvpInit(version): XVP extensions active for this connection
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // main setup
|
|
|
|
+ Util.Debug(">> RFB.constructor");
|
|
|
|
+
|
|
|
|
+ // populate encHandlers with bound versions
|
|
|
|
+ Object.keys(RFB.encodingHandlers).forEach(function (encName) {
|
|
|
|
+ this._encHandlers[encName] = RFB.encodingHandlers[encName].bind(this);
|
|
|
|
+ }.bind(this));
|
|
|
|
+
|
|
|
|
+ // Create lookup tables based on encoding number
|
|
|
|
+ for (var i = 0; i < this._encodings.length; i++) {
|
|
|
|
+ this._encHandlers[this._encodings[i][1]] = this._encHandlers[this._encodings[i][0]];
|
|
|
|
+ this._encNames[this._encodings[i][1]] = this._encodings[i][0];
|
|
|
|
+ this._encStats[this._encodings[i][1]] = [0, 0];
|
|
}
|
|
}
|
|
- if (rfb_state === 'disconnect') {
|
|
|
|
- updateState('disconnected', 'VNC disconnected' + msg);
|
|
|
|
- } else if (rfb_state === 'ProtocolVersion') {
|
|
|
|
- fail('Failed to connect to server' + msg);
|
|
|
|
- } else if (rfb_state in {'failed':1, 'disconnected':1}) {
|
|
|
|
- Util.Error("Received onclose while disconnected" + msg);
|
|
|
|
- } else {
|
|
|
|
- fail('Server disconnected' + msg);
|
|
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ this._display = new Display({target: this._target});
|
|
|
|
+ } catch (exc) {
|
|
|
|
+ Util.Error("Display exception: " + exc);
|
|
|
|
+ this._updateState('fatal', "No working Display");
|
|
}
|
|
}
|
|
- });
|
|
|
|
- ws.on('error', function(e) {
|
|
|
|
- Util.Warn("WebSocket on-error event");
|
|
|
|
- //fail("WebSock reported an error");
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- init_vars();
|
|
|
|
-
|
|
|
|
- /* Check web-socket-js if no builtin WebSocket support */
|
|
|
|
- if (Websock_native) {
|
|
|
|
- Util.Info("Using native WebSockets");
|
|
|
|
- updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
|
|
|
|
- } else {
|
|
|
|
- Util.Warn("Using web-socket-js bridge. Flash version: " +
|
|
|
|
- Util.Flash.version);
|
|
|
|
- if ((! Util.Flash) ||
|
|
|
|
- (Util.Flash.version < 9)) {
|
|
|
|
- updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash<\/a> is required");
|
|
|
|
- } else if (document.location.href.substr(0, 7) === "file://") {
|
|
|
|
- updateState('fatal',
|
|
|
|
- "'file://' URL is incompatible with Adobe Flash");
|
|
|
|
|
|
+
|
|
|
|
+ this._keyboard = new Keyboard({target: this._focusContainer,
|
|
|
|
+ onKeyPress: this._handleKeyPress.bind(this)});
|
|
|
|
+
|
|
|
|
+ this._mouse = new Mouse({target: this._target,
|
|
|
|
+ onMouseButton: this._handleMouseButton.bind(this),
|
|
|
|
+ onMouseMove: this._handleMouseMove.bind(this),
|
|
|
|
+ notify: this._keyboard.sync.bind(this._keyboard)});
|
|
|
|
+
|
|
|
|
+ this._sock = new Websock();
|
|
|
|
+ this._sock.on('message', this._handle_message.bind(this));
|
|
|
|
+ this._sock.on('open', function () {
|
|
|
|
+ if (this._rfb_state === 'connect') {
|
|
|
|
+ this._updateState('ProtocolVersion', "Starting VNC handshake");
|
|
|
|
+ } else {
|
|
|
|
+ this._fail("Got unexpected WebSocket connection");
|
|
|
|
+ }
|
|
|
|
+ }.bind(this));
|
|
|
|
+ this._sock.on('close', function (e) {
|
|
|
|
+ Util.Warn("WebSocket on-close event");
|
|
|
|
+ var msg = "";
|
|
|
|
+ if (e.code) {
|
|
|
|
+ msg = " (code: " + e.code;
|
|
|
|
+ if (e.reason) {
|
|
|
|
+ msg += ", reason: " + e.reason;
|
|
|
|
+ }
|
|
|
|
+ msg += ")";
|
|
|
|
+ }
|
|
|
|
+ if (this._rfb_state === 'disconnect') {
|
|
|
|
+ this._updateState('disconnected', 'VNC disconnected' + msg);
|
|
|
|
+ } else if (this._rfb_state === 'ProtocolVersion') {
|
|
|
|
+ this._fail('Failed to connect to server' + msg);
|
|
|
|
+ } else if (this._rfb_state in {'failed': 1, 'disconnected': 1}) {
|
|
|
|
+ Util.Error("Received onclose while disconnected" + msg);
|
|
|
|
+ } else {
|
|
|
|
+ this._fail("Server disconnected" + msg);
|
|
|
|
+ }
|
|
|
|
+ }.bind(this));
|
|
|
|
+ this._sock.on('error', function (e) {
|
|
|
|
+ Util.Warn("WebSocket on-error event");
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ this._init_vars();
|
|
|
|
+
|
|
|
|
+ var rmode = this._display.get_render_mode();
|
|
|
|
+ if (Websock_native) {
|
|
|
|
+ Util.Info("Using native WebSockets");
|
|
|
|
+ this._updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
|
|
} else {
|
|
} else {
|
|
- updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- Util.Debug("<< RFB.constructor");
|
|
|
|
- return that; // Return the public API interface
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-function connect() {
|
|
|
|
- Util.Debug(">> RFB.connect");
|
|
|
|
- var uri;
|
|
|
|
-
|
|
|
|
- if (typeof UsingSocketIO !== "undefined") {
|
|
|
|
- uri = "http";
|
|
|
|
- } else {
|
|
|
|
- uri = conf.encrypt ? "wss" : "ws";
|
|
|
|
- }
|
|
|
|
- uri += "://" + rfb_host + ":" + rfb_port + "/" + rfb_path;
|
|
|
|
- Util.Info("connecting to " + uri);
|
|
|
|
-
|
|
|
|
- ws.open(uri, conf.wsProtocols);
|
|
|
|
-
|
|
|
|
- Util.Debug("<< RFB.connect");
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-// Initialize variables that are reset before each connection
|
|
|
|
-init_vars = function() {
|
|
|
|
- var i;
|
|
|
|
-
|
|
|
|
- /* Reset state */
|
|
|
|
- ws.init();
|
|
|
|
-
|
|
|
|
- FBU.rects = 0;
|
|
|
|
- FBU.subrects = 0; // RRE and HEXTILE
|
|
|
|
- FBU.lines = 0; // RAW
|
|
|
|
- FBU.tiles = 0; // HEXTILE
|
|
|
|
- FBU.zlibs = []; // TIGHT zlib encoders
|
|
|
|
- mouse_buttonMask = 0;
|
|
|
|
- mouse_arr = [];
|
|
|
|
-
|
|
|
|
- // Clear the per connection encoding stats
|
|
|
|
- for (i=0; i < encodings.length; i+=1) {
|
|
|
|
- encStats[encodings[i][1]][0] = 0;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- for (i=0; i < 4; i++) {
|
|
|
|
- //FBU.zlibs[i] = new InflateStream();
|
|
|
|
- FBU.zlibs[i] = new TINF();
|
|
|
|
- FBU.zlibs[i].init();
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-// Print statistics
|
|
|
|
-print_stats = function() {
|
|
|
|
- var i, s;
|
|
|
|
- Util.Info("Encoding stats for this connection:");
|
|
|
|
- for (i=0; i < encodings.length; i+=1) {
|
|
|
|
- s = encStats[encodings[i][1]];
|
|
|
|
- if ((s[0] + s[1]) > 0) {
|
|
|
|
- Util.Info(" " + encodings[i][0] + ": " +
|
|
|
|
- s[0] + " rects");
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- Util.Info("Encoding stats since page load:");
|
|
|
|
- for (i=0; i < encodings.length; i+=1) {
|
|
|
|
- s = encStats[encodings[i][1]];
|
|
|
|
- if ((s[0] + s[1]) > 0) {
|
|
|
|
- Util.Info(" " + encodings[i][0] + ": " +
|
|
|
|
- s[1] + " rects");
|
|
|
|
|
|
+ Util.Warn("Using web-socket-js bridge. Flash version: " + Util.Flash.version);
|
|
|
|
+ if (!Util.Flash || Util.Flash.version < 9) {
|
|
|
|
+ this._updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash</a> is required");
|
|
|
|
+ } else if (document.location.href.substr(0, 7) === 'file://') {
|
|
|
|
+ this._updateState('fatal', "'file://' URL is incompatible with Adobe Flash");
|
|
|
|
+ } else {
|
|
|
|
+ this._updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
|
|
-//
|
|
|
|
-// Utility routines
|
|
|
|
-//
|
|
|
|
|
|
+ Util.Debug("<< RFB.constructor");
|
|
|
|
+ };
|
|
|
|
|
|
|
|
+ RFB.prototype = {
|
|
|
|
+ // Public methods
|
|
|
|
+ connect: function (host, port, password, path) {
|
|
|
|
+ this._rfb_host = host;
|
|
|
|
+ this._rfb_port = port;
|
|
|
|
+ this._rfb_password = (password !== undefined) ? password : "";
|
|
|
|
+ this._rfb_path = (path !== undefined) ? path : "";
|
|
|
|
|
|
-/*
|
|
|
|
- * Page states:
|
|
|
|
- * loaded - page load, equivalent to disconnected
|
|
|
|
- * disconnected - idle state
|
|
|
|
- * connect - starting to connect (to ProtocolVersion)
|
|
|
|
- * normal - connected
|
|
|
|
- * disconnect - starting to disconnect
|
|
|
|
- * failed - abnormal disconnect
|
|
|
|
- * fatal - failed to load page, or fatal error
|
|
|
|
- *
|
|
|
|
- * RFB protocol initialization states:
|
|
|
|
- * ProtocolVersion
|
|
|
|
- * Security
|
|
|
|
- * Authentication
|
|
|
|
- * password - waiting for password, not part of RFB
|
|
|
|
- * SecurityResult
|
|
|
|
- * ClientInitialization - not triggered by server message
|
|
|
|
- * ServerInitialization (to normal)
|
|
|
|
- */
|
|
|
|
-updateState = function(state, statusMsg) {
|
|
|
|
- var func, cmsg, oldstate = rfb_state;
|
|
|
|
-
|
|
|
|
- if (state === oldstate) {
|
|
|
|
- /* Already here, ignore */
|
|
|
|
- Util.Debug("Already in state '" + state + "', ignoring.");
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /*
|
|
|
|
- * These are disconnected states. A previous connect may
|
|
|
|
- * asynchronously cause a connection so make sure we are closed.
|
|
|
|
- */
|
|
|
|
- if (state in {'disconnected':1, 'loaded':1, 'connect':1,
|
|
|
|
- 'disconnect':1, 'failed':1, 'fatal':1}) {
|
|
|
|
- if (sendTimer) {
|
|
|
|
- clearInterval(sendTimer);
|
|
|
|
- sendTimer = null;
|
|
|
|
- }
|
|
|
|
|
|
+ if (!this._rfb_host || !this._rfb_port) {
|
|
|
|
+ return this._fail("Must set host and port");
|
|
|
|
+ }
|
|
|
|
|
|
- if (msgTimer) {
|
|
|
|
- clearTimeout(msgTimer);
|
|
|
|
- msgTimer = null;
|
|
|
|
- }
|
|
|
|
|
|
+ this._updateState('connect');
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ disconnect: function () {
|
|
|
|
+ this._updateState('disconnect', 'Disconnecting');
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ sendPassword: function (passwd) {
|
|
|
|
+ this._rfb_password = passwd;
|
|
|
|
+ this._rfb_state = 'Authentication';
|
|
|
|
+ setTimeout(this._init_msg.bind(this), 1);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ sendCtrlAltDel: function () {
|
|
|
|
+ if (this._rfb_state !== 'normal' || this._view_only) { return false; }
|
|
|
|
+ Util.Info("Sending Ctrl-Alt-Del");
|
|
|
|
+
|
|
|
|
+ var arr = [];
|
|
|
|
+ arr = arr.concat(RFB.messages.keyEvent(0xFFE3, 1)); // Control
|
|
|
|
+ arr = arr.concat(RFB.messages.keyEvent(0xFFE9, 1)); // Alt
|
|
|
|
+ arr = arr.concat(RFB.messages.keyEvent(0xFFFF, 1)); // Delete
|
|
|
|
+ arr = arr.concat(RFB.messages.keyEvent(0xFFFF, 0)); // Delete
|
|
|
|
+ arr = arr.concat(RFB.messages.keyEvent(0xFFE9, 0)); // Alt
|
|
|
|
+ arr = arr.concat(RFB.messages.keyEvent(0xFFE3, 0)); // Control
|
|
|
|
+ this._sock.send(arr);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ xvpOp: function (ver, op) {
|
|
|
|
+ if (this._rfb_xvp_ver < ver) { return false; }
|
|
|
|
+ Util.Info("Sending XVP operation " + op + " (version " + ver + ")");
|
|
|
|
+ this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op));
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ xvpShutdown: function () {
|
|
|
|
+ return this.xvpOp(1, 2);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ xvpReboot: function () {
|
|
|
|
+ return this.xvpOp(1, 3);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ xvpReset: function () {
|
|
|
|
+ return this.xvpOp(1, 4);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // Send a key press. If 'down' is not specified then send a down key
|
|
|
|
+ // followed by an up key.
|
|
|
|
+ sendKey: function (code, down) {
|
|
|
|
+ if (this._rfb_state !== "normal" || this._view_only) { return false; }
|
|
|
|
+ var arr = [];
|
|
|
|
+ if (typeof down !== 'undefined') {
|
|
|
|
+ Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
|
|
|
|
+ arr = arr.concat(RFB.messages.keyEvent(code, down ? 1 : 0));
|
|
|
|
+ } else {
|
|
|
|
+ Util.Info("Sending key code (down + up): " + code);
|
|
|
|
+ arr = arr.concat(RFB.messages.keyEvent(code, 1));
|
|
|
|
+ arr = arr.concat(RFB.messages.keyEvent(code, 0));
|
|
|
|
+ }
|
|
|
|
+ this._sock.send(arr);
|
|
|
|
+ },
|
|
|
|
|
|
- if (display && display.get_context()) {
|
|
|
|
- keyboard.ungrab();
|
|
|
|
- mouse.ungrab();
|
|
|
|
- display.defaultCursor();
|
|
|
|
- if ((Util.get_logging() !== 'debug') ||
|
|
|
|
- (state === 'loaded')) {
|
|
|
|
- // Show noVNC logo on load and when disconnected if
|
|
|
|
- // debug is off
|
|
|
|
- display.clear();
|
|
|
|
|
|
+ clipboardPasteFrom: function (text) {
|
|
|
|
+ if (this._rfb_state !== 'normal') { return; }
|
|
|
|
+ this._sock.send(RFB.messages.clientCutText(text));
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // Private methods
|
|
|
|
+
|
|
|
|
+ _connect: function () {
|
|
|
|
+ Util.Debug(">> RFB.connect");
|
|
|
|
+
|
|
|
|
+ var uri;
|
|
|
|
+ if (typeof UsingSocketIO !== 'undefined') {
|
|
|
|
+ uri = 'http';
|
|
|
|
+ } else {
|
|
|
|
+ uri = this._encrypt ? 'wss' : 'ws';
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- ws.close();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (oldstate === 'fatal') {
|
|
|
|
- Util.Error("Fatal error, cannot continue");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ((state === 'failed') || (state === 'fatal')) {
|
|
|
|
- func = Util.Error;
|
|
|
|
- } else {
|
|
|
|
- func = Util.Warn;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
|
|
|
|
- func("New state '" + state + "', was '" + oldstate + "'." + cmsg);
|
|
|
|
-
|
|
|
|
- if ((oldstate === 'failed') && (state === 'disconnected')) {
|
|
|
|
- // Do disconnect action, but stay in failed state
|
|
|
|
- rfb_state = 'failed';
|
|
|
|
- } else {
|
|
|
|
- rfb_state = state;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (disconnTimer && (rfb_state !== 'disconnect')) {
|
|
|
|
- Util.Debug("Clearing disconnect timer");
|
|
|
|
- clearTimeout(disconnTimer);
|
|
|
|
- disconnTimer = null;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- switch (state) {
|
|
|
|
- case 'normal':
|
|
|
|
- if ((oldstate === 'disconnected') || (oldstate === 'failed')) {
|
|
|
|
- Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
|
|
|
|
- }
|
|
|
|
|
|
+ uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path;
|
|
|
|
+ Util.Info("connecting to " + uri);
|
|
|
|
|
|
- break;
|
|
|
|
|
|
+ this._sock.open(uri, this._sockProtocols);
|
|
|
|
+
|
|
|
|
+ Util.Debug("<< RFB.connect");
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _init_vars: function () {
|
|
|
|
+ // reset state
|
|
|
|
+ this._sock.init();
|
|
|
|
+
|
|
|
|
+ this._FBU.rects = 0;
|
|
|
|
+ this._FBU.subrects = 0; // RRE and HEXTILE
|
|
|
|
+ this._FBU.lines = 0; // RAW
|
|
|
|
+ this._FBU.tiles = 0; // HEXTILE
|
|
|
|
+ this._FBU.zlibs = []; // TIGHT zlib encoders
|
|
|
|
+ this._mouse_buttonMask = 0;
|
|
|
|
+ this._mouse_arr = [];
|
|
|
|
+ this._rfb_tightvnc = false;
|
|
|
|
+
|
|
|
|
+ // Clear the per connection encoding stats
|
|
|
|
+ var i;
|
|
|
|
+ for (i = 0; i < this._encodings.length; i++) {
|
|
|
|
+ this._encStats[this._encodings[i][1]][0] = 0;
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ for (i = 0; i < 4; i++) {
|
|
|
|
+ this._FBU.zlibs[i] = new TINF();
|
|
|
|
+ this._FBU.zlibs[i].init();
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _print_stats: function () {
|
|
|
|
+ Util.Info("Encoding stats for this connection:");
|
|
|
|
+ var i, s;
|
|
|
|
+ for (i = 0; i < this._encodings.length; i++) {
|
|
|
|
+ s = this._encStats[this._encodings[i][1]];
|
|
|
|
+ if (s[0] + s[1] > 0) {
|
|
|
|
+ Util.Info(" " + this._encodings[i][0] + ": " + s[0] + " rects");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- case 'connect':
|
|
|
|
|
|
+ Util.Info("Encoding stats since page load:");
|
|
|
|
+ for (i = 0; i < this._encodings.length; i++) {
|
|
|
|
+ s = this._encStats[this._encodings[i][1]];
|
|
|
|
+ Util.Info(" " + this._encodings[i][0] + ": " + s[1] + " rects");
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- init_vars();
|
|
|
|
- connect();
|
|
|
|
|
|
|
|
- // WebSocket.onopen transitions to 'ProtocolVersion'
|
|
|
|
- break;
|
|
|
|
|
|
+ /*
|
|
|
|
+ * Page states:
|
|
|
|
+ * loaded - page load, equivalent to disconnected
|
|
|
|
+ * disconnected - idle state
|
|
|
|
+ * connect - starting to connect (to ProtocolVersion)
|
|
|
|
+ * normal - connected
|
|
|
|
+ * disconnect - starting to disconnect
|
|
|
|
+ * failed - abnormal disconnect
|
|
|
|
+ * fatal - failed to load page, or fatal error
|
|
|
|
+ *
|
|
|
|
+ * RFB protocol initialization states:
|
|
|
|
+ * ProtocolVersion
|
|
|
|
+ * Security
|
|
|
|
+ * Authentication
|
|
|
|
+ * password - waiting for password, not part of RFB
|
|
|
|
+ * SecurityResult
|
|
|
|
+ * ClientInitialization - not triggered by server message
|
|
|
|
+ * ServerInitialization (to normal)
|
|
|
|
+ */
|
|
|
|
+ _updateState: function (state, statusMsg) {
|
|
|
|
+ var oldstate = this._rfb_state;
|
|
|
|
+
|
|
|
|
+ if (state === oldstate) {
|
|
|
|
+ // Already here, ignore
|
|
|
|
+ Util.Debug("Already in state '" + state + "', ignoring");
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ /*
|
|
|
|
+ * These are disconnected states. A previous connect may
|
|
|
|
+ * asynchronously cause a connection so make sure we are closed.
|
|
|
|
+ */
|
|
|
|
+ if (state in {'disconnected': 1, 'loaded': 1, 'connect': 1,
|
|
|
|
+ 'disconnect': 1, 'failed': 1, 'fatal': 1}) {
|
|
|
|
|
|
- case 'disconnect':
|
|
|
|
|
|
+ if (this._sendTimer) {
|
|
|
|
+ clearInterval(this._sendTimer);
|
|
|
|
+ this._sendTimer = null;
|
|
|
|
+ }
|
|
|
|
|
|
- if (! test_mode) {
|
|
|
|
- disconnTimer = setTimeout(function () {
|
|
|
|
- fail("Disconnect timeout");
|
|
|
|
- }, conf.disconnectTimeout * 1000);
|
|
|
|
- }
|
|
|
|
|
|
+ if (this._msgTimer) {
|
|
|
|
+ clearInterval(this._msgTimer);
|
|
|
|
+ this._msgTimer = null;
|
|
|
|
+ }
|
|
|
|
|
|
- print_stats();
|
|
|
|
|
|
+ if (this._display && this._display.get_context()) {
|
|
|
|
+ this._keyboard.ungrab();
|
|
|
|
+ this._mouse.ungrab();
|
|
|
|
+ this._display.defaultCursor();
|
|
|
|
+ if (Util.get_logging() !== 'debug' || state === 'loaded') {
|
|
|
|
+ // Show noVNC logo on load and when disconnected, unless in
|
|
|
|
+ // debug mode
|
|
|
|
+ this._display.clear();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- // WebSocket.onclose transitions to 'disconnected'
|
|
|
|
- break;
|
|
|
|
|
|
+ this._sock.close();
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ if (oldstate === 'fatal') {
|
|
|
|
+ Util.Error('Fatal error, cannot continue');
|
|
|
|
+ }
|
|
|
|
|
|
- case 'failed':
|
|
|
|
- if (oldstate === 'disconnected') {
|
|
|
|
- Util.Error("Invalid transition from 'disconnected' to 'failed'");
|
|
|
|
- }
|
|
|
|
- if (oldstate === 'normal') {
|
|
|
|
- Util.Error("Error while connected.");
|
|
|
|
- }
|
|
|
|
- if (oldstate === 'init') {
|
|
|
|
- Util.Error("Error while initializing.");
|
|
|
|
- }
|
|
|
|
|
|
+ var cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
|
|
|
|
+ var fullmsg = "New state '" + state + "', was '" + oldstate + "'." + cmsg;
|
|
|
|
+ if (state === 'failed' || state === 'fatal') {
|
|
|
|
+ Util.Error(cmsg);
|
|
|
|
+ } else {
|
|
|
|
+ Util.Warn(cmsg);
|
|
|
|
+ }
|
|
|
|
|
|
- // Make sure we transition to disconnected
|
|
|
|
- setTimeout(function() { updateState('disconnected'); }, 50);
|
|
|
|
-
|
|
|
|
- break;
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- default:
|
|
|
|
- // No state change action to take
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ((oldstate === 'failed') && (state === 'disconnected')) {
|
|
|
|
- // Leave the failed message
|
|
|
|
- conf.updateState(that, state, oldstate); // Obsolete
|
|
|
|
- conf.onUpdateState(that, state, oldstate);
|
|
|
|
- } else {
|
|
|
|
- conf.updateState(that, state, oldstate, statusMsg); // Obsolete
|
|
|
|
- conf.onUpdateState(that, state, oldstate, statusMsg);
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-fail = function(msg) {
|
|
|
|
- updateState('failed', msg);
|
|
|
|
- return false;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-handle_message = function() {
|
|
|
|
- //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen());
|
|
|
|
- //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
|
|
|
|
- if (ws.rQlen() === 0) {
|
|
|
|
- Util.Warn("handle_message called on empty receive queue");
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- switch (rfb_state) {
|
|
|
|
- case 'disconnected':
|
|
|
|
- case 'failed':
|
|
|
|
- Util.Error("Got data while disconnected");
|
|
|
|
- break;
|
|
|
|
- case 'normal':
|
|
|
|
- if (normal_msg() && ws.rQlen() > 0) {
|
|
|
|
- // true means we can continue processing
|
|
|
|
- // Give other events a chance to run
|
|
|
|
- if (msgTimer === null) {
|
|
|
|
- Util.Debug("More data to process, creating timer");
|
|
|
|
- msgTimer = setTimeout(function () {
|
|
|
|
- msgTimer = null;
|
|
|
|
- handle_message();
|
|
|
|
- }, 10);
|
|
|
|
|
|
+ if (oldstate === 'failed' && state === 'disconnected') {
|
|
|
|
+ // do disconnect action, but stay in failed state
|
|
|
|
+ this._rfb_state = 'failed';
|
|
} else {
|
|
} else {
|
|
- Util.Debug("More data to process, existing timer");
|
|
|
|
|
|
+ this._rfb_state = state;
|
|
}
|
|
}
|
|
- }
|
|
|
|
- break;
|
|
|
|
- default:
|
|
|
|
- init_msg();
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-function genDES(password, challenge) {
|
|
|
|
- var i, passwd = [];
|
|
|
|
- for (i=0; i < password.length; i += 1) {
|
|
|
|
- passwd.push(password.charCodeAt(i));
|
|
|
|
- }
|
|
|
|
- return (new DES(passwd)).encrypt(challenge);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-// overridable for testing
|
|
|
|
-checkEvents = function() {
|
|
|
|
- if (rfb_state === 'normal' && !viewportDragging && mouse_arr.length > 0) {
|
|
|
|
- ws.send(mouse_arr);
|
|
|
|
- mouse_arr = [];
|
|
|
|
- }
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-keyPress = function(keysym, down) {
|
|
|
|
- if (conf.view_only) { return; } // View only, skip keyboard events
|
|
|
|
-
|
|
|
|
- ws.send(keyEvent(keysym, down));
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-mouseButton = function(x, y, down, bmask) {
|
|
|
|
- if (down) {
|
|
|
|
- mouse_buttonMask |= bmask;
|
|
|
|
- } else {
|
|
|
|
- mouse_buttonMask ^= bmask;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (conf.viewportDrag) {
|
|
|
|
- if (down && !viewportDragging) {
|
|
|
|
- viewportDragging = true;
|
|
|
|
- viewportDragPos = {'x': x, 'y': y};
|
|
|
|
-
|
|
|
|
- // Skip sending mouse events
|
|
|
|
- return;
|
|
|
|
- } else {
|
|
|
|
- viewportDragging = false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
|
|
- if (conf.view_only) { return; } // View only, skip mouse events
|
|
|
|
|
|
+ if (this._disconnTimer && this._rfb_state !== 'disconnect') {
|
|
|
|
+ Util.Debug("Clearing disconnect timer");
|
|
|
|
+ clearTimeout(this._disconnTimer);
|
|
|
|
+ this._disconnTimer = null;
|
|
|
|
+ }
|
|
|
|
|
|
- mouse_arr = mouse_arr.concat(
|
|
|
|
- pointerEvent(display.absX(x), display.absY(y)) );
|
|
|
|
- ws.send(mouse_arr);
|
|
|
|
- mouse_arr = [];
|
|
|
|
-};
|
|
|
|
|
|
+ switch (state) {
|
|
|
|
+ case 'normal':
|
|
|
|
+ if (oldstate === 'disconnected' || oldstate === 'failed') {
|
|
|
|
+ Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case 'connect':
|
|
|
|
+ this._init_vars();
|
|
|
|
+ this._connect();
|
|
|
|
+ // WebSocket.onopen transitions to 'ProtocolVersion'
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case 'disconnect':
|
|
|
|
+ this._disconnTimer = setTimeout(function () {
|
|
|
|
+ this._fail("Disconnect timeout");
|
|
|
|
+ }.bind(this), this._disconnectTimeout * 1000);
|
|
|
|
+
|
|
|
|
+ this._print_stats();
|
|
|
|
+
|
|
|
|
+ // WebSocket.onclose transitions to 'disconnected'
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case 'failed':
|
|
|
|
+ if (oldstate === 'disconnected') {
|
|
|
|
+ Util.Error("Invalid transition from 'disconnected' to 'failed'");
|
|
|
|
+ } else if (oldstate === 'normal') {
|
|
|
|
+ Util.Error("Error while connected.");
|
|
|
|
+ } else if (oldstate === 'init') {
|
|
|
|
+ Util.Error("Error while initializing.");
|
|
|
|
+ }
|
|
|
|
|
|
-mouseMove = function(x, y) {
|
|
|
|
- //Util.Debug('>> mouseMove ' + x + "," + y);
|
|
|
|
- var deltaX, deltaY;
|
|
|
|
|
|
+ // Make sure we transition to disconnected
|
|
|
|
+ setTimeout(function () {
|
|
|
|
+ this._updateState('disconnected');
|
|
|
|
+ }.bind(this), 50);
|
|
|
|
|
|
- if (viewportDragging) {
|
|
|
|
- //deltaX = x - viewportDragPos.x; // drag viewport
|
|
|
|
- deltaX = viewportDragPos.x - x; // drag frame buffer
|
|
|
|
- //deltaY = y - viewportDragPos.y; // drag viewport
|
|
|
|
- deltaY = viewportDragPos.y - y; // drag frame buffer
|
|
|
|
- viewportDragPos = {'x': x, 'y': y};
|
|
|
|
|
|
+ break;
|
|
|
|
|
|
- display.viewportChange(deltaX, deltaY);
|
|
|
|
|
|
+ default:
|
|
|
|
+ // No state change action to take
|
|
|
|
+ }
|
|
|
|
|
|
- // Skip sending mouse events
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ if (oldstate === 'failed' && state === 'disconnected') {
|
|
|
|
+ this._onUpdateState(this, state, oldstate);
|
|
|
|
+ } else {
|
|
|
|
+ this._onUpdateState(this, state, oldstate, statusMsg);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- if (conf.view_only) { return; } // View only, skip mouse events
|
|
|
|
|
|
+ _fail: function (msg) {
|
|
|
|
+ this._updateState('failed', msg);
|
|
|
|
+ return false;
|
|
|
|
+ },
|
|
|
|
|
|
- mouse_arr = mouse_arr.concat(
|
|
|
|
- pointerEvent(display.absX(x), display.absY(y)));
|
|
|
|
-
|
|
|
|
- checkEvents();
|
|
|
|
-};
|
|
|
|
|
|
+ _handle_message: function () {
|
|
|
|
+ if (this._sock.rQlen() === 0) {
|
|
|
|
+ Util.Warn("handle_message called on an empty receive queue");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ switch (this._rfb_state) {
|
|
|
|
+ case 'disconnected':
|
|
|
|
+ case 'failed':
|
|
|
|
+ Util.Error("Got data while disconnected");
|
|
|
|
+ break;
|
|
|
|
+ case 'normal':
|
|
|
|
+ if (this._normal_msg() && this._sock.rQlen() > 0) {
|
|
|
|
+ // true means we can continue processing
|
|
|
|
+ // Give other events a chance to run
|
|
|
|
+ if (this._msgTimer === null) {
|
|
|
|
+ Util.Debug("More data to process, creating timer");
|
|
|
|
+ this._msgTimer = setTimeout(function () {
|
|
|
|
+ this._msgTimer = null;
|
|
|
|
+ this._handle_message();
|
|
|
|
+ }.bind(this), 10);
|
|
|
|
+ } else {
|
|
|
|
+ Util.Debug("More data to process, existing timer");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ this._init_msg();
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
-//
|
|
|
|
-// Server message handlers
|
|
|
|
-//
|
|
|
|
|
|
+ _checkEvents: function () {
|
|
|
|
+ if (this._rfb_state === 'normal' && !this._viewportDragging && this._mouse_arr.length > 0) {
|
|
|
|
+ this._sock.send(this._mouse_arr);
|
|
|
|
+ this._mouse_arr = [];
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
-// RFB/VNC initialisation message handler
|
|
|
|
-init_msg = function() {
|
|
|
|
- //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
|
|
|
|
|
|
+ _handleKeyPress: function (keysym, down) {
|
|
|
|
+ if (this._view_only) { return; } // View only, skip keyboard, events
|
|
|
|
+ this._sock.send(RFB.messages.keyEvent(keysym, down));
|
|
|
|
+ },
|
|
|
|
|
|
- var strlen, reason, length, sversion, cversion, repeaterID,
|
|
|
|
- i, types, num_types, challenge, response, bpp, depth,
|
|
|
|
- big_endian, red_max, green_max, blue_max, red_shift,
|
|
|
|
- green_shift, blue_shift, true_color, name_length, is_repeater,
|
|
|
|
- xvp_sep, xvp_auth, xvp_auth_str;
|
|
|
|
|
|
+ _handleMouseButton: function (x, y, down, bmask) {
|
|
|
|
+ if (down) {
|
|
|
|
+ this._mouse_buttonMask |= bmask;
|
|
|
|
+ } else {
|
|
|
|
+ this._mouse_buttonMaks ^= bmask;
|
|
|
|
+ }
|
|
|
|
|
|
- //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));
|
|
|
|
- switch (rfb_state) {
|
|
|
|
|
|
+ if (this._viewportDrag) {
|
|
|
|
+ if (down && !this._viewportDragging) {
|
|
|
|
+ this._viewportDragging = true;
|
|
|
|
+ this._viewportDragPos = {'x': x, 'y': y};
|
|
|
|
|
|
- case 'ProtocolVersion' :
|
|
|
|
- if (ws.rQlen() < 12) {
|
|
|
|
- return fail("Incomplete protocol version");
|
|
|
|
- }
|
|
|
|
- sversion = ws.rQshiftStr(12).substr(4,7);
|
|
|
|
- Util.Info("Server ProtocolVersion: " + sversion);
|
|
|
|
- is_repeater = 0;
|
|
|
|
- switch (sversion) {
|
|
|
|
- case "000.000": is_repeater = 1; break; // UltraVNC repeater
|
|
|
|
- case "003.003": rfb_version = 3.3; break;
|
|
|
|
- case "003.006": rfb_version = 3.3; break; // UltraVNC
|
|
|
|
- case "003.889": rfb_version = 3.3; break; // Apple Remote Desktop
|
|
|
|
- case "003.007": rfb_version = 3.7; break;
|
|
|
|
- case "003.008": rfb_version = 3.8; break;
|
|
|
|
- case "004.000": rfb_version = 3.8; break; // Intel AMT KVM
|
|
|
|
- case "004.001": rfb_version = 3.8; break; // RealVNC 4.6
|
|
|
|
- default:
|
|
|
|
- return fail("Invalid server version " + sversion);
|
|
|
|
- }
|
|
|
|
- if (is_repeater) {
|
|
|
|
- repeaterID = conf.repeaterID;
|
|
|
|
- while (repeaterID.length < 250) {
|
|
|
|
- repeaterID += "\0";
|
|
|
|
|
|
+ // Skip sending mouse events
|
|
|
|
+ return;
|
|
|
|
+ } else {
|
|
|
|
+ this._viewportDragging = false;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- ws.send_string(repeaterID);
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- if (rfb_version > rfb_max_version) {
|
|
|
|
- rfb_version = rfb_max_version;
|
|
|
|
- }
|
|
|
|
|
|
|
|
- if (! test_mode) {
|
|
|
|
- sendTimer = setInterval(function() {
|
|
|
|
- // Send updates either at a rate of one update
|
|
|
|
- // every 50ms, or whatever slower rate the network
|
|
|
|
- // can handle.
|
|
|
|
- ws.flush();
|
|
|
|
- }, 50);
|
|
|
|
- }
|
|
|
|
|
|
+ if (this._view_only) { return; } // View only, skip mouse events
|
|
|
|
+
|
|
|
|
+ this._mouse_arr = this._mouse_arr.concat(
|
|
|
|
+ RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask));
|
|
|
|
+ this._sock.send(this._mouse_arr);
|
|
|
|
+ this._mouse_arr = [];
|
|
|
|
+ },
|
|
|
|
|
|
- cversion = "00" + parseInt(rfb_version,10) +
|
|
|
|
- ".00" + ((rfb_version * 10) % 10);
|
|
|
|
- ws.send_string("RFB " + cversion + "\n");
|
|
|
|
- updateState('Security', "Sent ProtocolVersion: " + cversion);
|
|
|
|
- break;
|
|
|
|
-
|
|
|
|
- case 'Security' :
|
|
|
|
- if (rfb_version >= 3.7) {
|
|
|
|
- // Server sends supported list, client decides
|
|
|
|
- num_types = ws.rQshift8();
|
|
|
|
- if (ws.rQwait("security type", num_types, 1)) { return false; }
|
|
|
|
- if (num_types === 0) {
|
|
|
|
- strlen = ws.rQshift32();
|
|
|
|
- reason = ws.rQshiftStr(strlen);
|
|
|
|
- return fail("Security failure: " + reason);
|
|
|
|
- }
|
|
|
|
- rfb_auth_scheme = 0;
|
|
|
|
- types = ws.rQshiftBytes(num_types);
|
|
|
|
- Util.Debug("Server security types: " + types);
|
|
|
|
- for (i=0; i < types.length; i+=1) {
|
|
|
|
- if ((types[i] > rfb_auth_scheme) && (types[i] <= 16 || types[i] == 22)) {
|
|
|
|
- rfb_auth_scheme = types[i];
|
|
|
|
|
|
+ _handleMouseMove: function (x, y) {
|
|
|
|
+ if (this._viewportDragging) {
|
|
|
|
+ var deltaX = this._viewportDragPos.x - x;
|
|
|
|
+ var deltaY = this._viewportDragPos.y - y;
|
|
|
|
+ this._viewportDragPos = {'x': x, 'y': y};
|
|
|
|
+
|
|
|
|
+ this._display.viewportChange(deltaX, deltaY);
|
|
|
|
+
|
|
|
|
+ // Skip sending mouse events
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this._view_only) { return; } // View only, skip mouse events
|
|
|
|
+
|
|
|
|
+ this._mouse_arr = this._mouse_arr.concat(
|
|
|
|
+ RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask));
|
|
|
|
+
|
|
|
|
+ this._checkEvents();
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // Message Handlers
|
|
|
|
+
|
|
|
|
+ _negotiate_protocol_version: function () {
|
|
|
|
+ if (this._sock.rQlen() < 12) {
|
|
|
|
+ return this._fail("Incomplete protocol version");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var sversion = this._sock.rQshiftStr(12).substr(4, 7);
|
|
|
|
+ Util.Info("Server ProtocolVersion: " + sversion);
|
|
|
|
+ var is_repeater = 0;
|
|
|
|
+ switch (sversion) {
|
|
|
|
+ case "000.000": // UltraVNC repeater
|
|
|
|
+ is_repeater = 1;
|
|
|
|
+ break;
|
|
|
|
+ case "003.003":
|
|
|
|
+ case "003.006": // UltraVNC
|
|
|
|
+ case "003.889": // Apple Remote Desktop
|
|
|
|
+ this._rfb_version = 3.3;
|
|
|
|
+ break;
|
|
|
|
+ case "003.007":
|
|
|
|
+ this._rfb_version = 3.7;
|
|
|
|
+ break;
|
|
|
|
+ case "003.008":
|
|
|
|
+ case "004.000": // Intel AMT KVM
|
|
|
|
+ case "004.001": // RealVNC 4.6
|
|
|
|
+ this._rfb_version = 3.8;
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ return this._fail("Invalid server version " + sversion);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (is_repeater) {
|
|
|
|
+ var repeaterID = this._repeaterID;
|
|
|
|
+ while (repeaterID.length < 250) {
|
|
|
|
+ repeaterID += "\0";
|
|
}
|
|
}
|
|
|
|
+ this._sock.send_string(repeaterID);
|
|
|
|
+ return true;
|
|
}
|
|
}
|
|
- if (rfb_auth_scheme === 0) {
|
|
|
|
- return fail("Unsupported security types: " + types);
|
|
|
|
|
|
+
|
|
|
|
+ if (this._rfb_version > this._rfb_max_version) {
|
|
|
|
+ this._rfb_version = this._rfb_max_version;
|
|
}
|
|
}
|
|
-
|
|
|
|
- ws.send([rfb_auth_scheme]);
|
|
|
|
- } else {
|
|
|
|
- // Server decides
|
|
|
|
- if (ws.rQwait("security scheme", 4)) { return false; }
|
|
|
|
- rfb_auth_scheme = ws.rQshift32();
|
|
|
|
- }
|
|
|
|
- updateState('Authentication',
|
|
|
|
- "Authenticating using scheme: " + rfb_auth_scheme);
|
|
|
|
- init_msg(); // Recursive fallthrough (workaround JSLint complaint)
|
|
|
|
- break;
|
|
|
|
-
|
|
|
|
- // Triggered by fallthough, not by server message
|
|
|
|
- case 'Authentication' :
|
|
|
|
- //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
|
|
|
|
- switch (rfb_auth_scheme) {
|
|
|
|
- case 0: // connection failed
|
|
|
|
- if (ws.rQwait("auth reason", 4)) { return false; }
|
|
|
|
- strlen = ws.rQshift32();
|
|
|
|
- reason = ws.rQshiftStr(strlen);
|
|
|
|
- return fail("Auth failure: " + reason);
|
|
|
|
- case 1: // no authentication
|
|
|
|
- if (rfb_version >= 3.8) {
|
|
|
|
- updateState('SecurityResult');
|
|
|
|
- return;
|
|
|
|
|
|
+
|
|
|
|
+ // Send updates either at a rate of 1 update per 50ms, or
|
|
|
|
+ // whatever slower rate the network can handle
|
|
|
|
+ this._sendTimer = setInterval(this._sock.flush.bind(this._sock), 50);
|
|
|
|
+
|
|
|
|
+ var cversion = "00" + parseInt(this._rfb_version, 10) +
|
|
|
|
+ ".00" + ((this._rfb_version * 10) % 10);
|
|
|
|
+ this._sock.send_string("RFB " + cversion + "\n");
|
|
|
|
+ this._updateState('Security', 'Sent ProtocolVersion: ' + cversion);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _negotiate_security: function () {
|
|
|
|
+ if (this._rfb_version >= 3.7) {
|
|
|
|
+ // Server sends supported list, client decides
|
|
|
|
+ var num_types = this._sock.rQshift8();
|
|
|
|
+ if (this._sock.rQwait("security type", num_types, 1)) { return false; }
|
|
|
|
+
|
|
|
|
+ if (num_types === 0) {
|
|
|
|
+ var strlen = this._sock.rQshift32();
|
|
|
|
+ var reason = this._sock.rQshiftStr(strlen);
|
|
|
|
+ return this._fail("Security failure: " + reason);
|
|
}
|
|
}
|
|
- // Fall through to ClientInitialisation
|
|
|
|
- break;
|
|
|
|
- case 22: // XVP authentication
|
|
|
|
- xvp_sep = conf.xvp_password_sep;
|
|
|
|
- xvp_auth = rfb_password.split(xvp_sep);
|
|
|
|
- if (xvp_auth.length < 3) {
|
|
|
|
- updateState('password', "XVP credentials required (user" + xvp_sep +
|
|
|
|
- "target" + xvp_sep + "password) -- got only " + rfb_password);
|
|
|
|
- conf.onPasswordRequired(that);
|
|
|
|
- return;
|
|
|
|
|
|
+
|
|
|
|
+ this._rfb_auth_scheme = 0;
|
|
|
|
+ var types = this._sock.rQshiftBytes(num_types);
|
|
|
|
+ Util.Debug("Server security types: " + types);
|
|
|
|
+ for (var i = 0; i < types.length; i++) {
|
|
|
|
+ if (types[i] > this._rfb_auth_scheme && (types[i] <= 16 || types[i] == 22)) {
|
|
|
|
+ this._rfb_auth_scheme = types[i];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this._rfb_auth_scheme === 0) {
|
|
|
|
+ return this._fail("Unsupported security types: " + types);
|
|
}
|
|
}
|
|
- xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
|
|
|
|
|
|
+
|
|
|
|
+ this._sock.send([this._rfb_auth_scheme]);
|
|
|
|
+ } else {
|
|
|
|
+ // Server decides
|
|
|
|
+ if (this._sock.rQwait("security scheme", 4)) { return false; }
|
|
|
|
+ this._rfb_auth_scheme = this._sock.rQshift32();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this._updateState('Authentication', 'Authenticating using scheme: ' + this._rfb_auth_scheme);
|
|
|
|
+ return this._init_msg(); // jump to authentication
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // authentication
|
|
|
|
+ _negotiate_xvp_auth: function () {
|
|
|
|
+ var xvp_sep = this._xvp_password_sep;
|
|
|
|
+ var xvp_auth = this._rfb_password.split(xvp_sep);
|
|
|
|
+ if (xvp_auth.length < 3) {
|
|
|
|
+ this._updateState('password', 'XVP credentials required (user' + xvp_sep +
|
|
|
|
+ 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password);
|
|
|
|
+ this._onPasswordRequired(this);
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
|
|
String.fromCharCode(xvp_auth[1].length) +
|
|
String.fromCharCode(xvp_auth[1].length) +
|
|
xvp_auth[0] +
|
|
xvp_auth[0] +
|
|
xvp_auth[1];
|
|
xvp_auth[1];
|
|
- ws.send_string(xvp_auth_str);
|
|
|
|
- rfb_password = xvp_auth.slice(2).join(xvp_sep);
|
|
|
|
- rfb_auth_scheme = 2;
|
|
|
|
- // Fall through to standard VNC authentication with remaining part of password
|
|
|
|
- case 2: // VNC authentication
|
|
|
|
- if (rfb_password.length === 0) {
|
|
|
|
- // Notify via both callbacks since it is kind of
|
|
|
|
- // a RFB state change and a UI interface issue.
|
|
|
|
- updateState('password', "Password Required");
|
|
|
|
- conf.onPasswordRequired(that);
|
|
|
|
- return;
|
|
|
|
|
|
+ this._sock.send_string(xvp_auth_str);
|
|
|
|
+ this._rfb_password = xvp_auth.slice(2).join(xvp_sep);
|
|
|
|
+ this._rfb_auth_scheme = 2;
|
|
|
|
+ return this._negotiate_authentication();
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _negotiate_std_vnc_auth: function () {
|
|
|
|
+ if (this._rfb_password.length === 0) {
|
|
|
|
+ // Notify via both callbacks since it's kind of
|
|
|
|
+ // an RFB state change and a UI interface issue
|
|
|
|
+ this._updateState('password', "Password Required");
|
|
|
|
+ this._onPasswordRequired(this);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this._sock.rQwait("auth challenge", 16)) { return false; }
|
|
|
|
+
|
|
|
|
+ var challenge = this._sock.rQshiftBytes(16);
|
|
|
|
+ var response = RFB.genDES(this._rfb_password, challenge);
|
|
|
|
+ this._sock.send(response);
|
|
|
|
+ this._updateState("SecurityResult");
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _negotiate_tight_tunnels: function (numTunnels) {
|
|
|
|
+ var clientSupportedTunnelTypes = {
|
|
|
|
+ 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
|
|
|
|
+ };
|
|
|
|
+ var serverSupportedTunnelTypes = {};
|
|
|
|
+ // receive tunnel capabilities
|
|
|
|
+ for (var i = 0; i < numTunnels; i++) {
|
|
|
|
+ var cap_code = this._sock.rQshift32();
|
|
|
|
+ var cap_vendor = this._sock.rQshiftStr(4);
|
|
|
|
+ var cap_signature = this._sock.rQshiftStr(8);
|
|
|
|
+ serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // choose the notunnel type
|
|
|
|
+ if (serverSupportedTunnelTypes[0]) {
|
|
|
|
+ if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
|
|
|
|
+ serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
|
|
|
|
+ return this._fail("Client's tunnel type had the incorrect vendor or signature");
|
|
}
|
|
}
|
|
- if (ws.rQwait("auth challenge", 16)) { return false; }
|
|
|
|
- challenge = ws.rQshiftBytes(16);
|
|
|
|
- //Util.Debug("Password: " + rfb_password);
|
|
|
|
- //Util.Debug("Challenge: " + challenge +
|
|
|
|
- // " (" + challenge.length + ")");
|
|
|
|
- response = genDES(rfb_password, challenge);
|
|
|
|
- //Util.Debug("Response: " + response +
|
|
|
|
- // " (" + response.length + ")");
|
|
|
|
-
|
|
|
|
- //Util.Debug("Sending DES encrypted auth response");
|
|
|
|
- ws.send(response);
|
|
|
|
- updateState('SecurityResult');
|
|
|
|
- return;
|
|
|
|
- case 16: // TightVNC Security Type
|
|
|
|
- if (ws.rQwait("num tunnels", 4)) { return false; }
|
|
|
|
- var numTunnels = ws.rQshift32();
|
|
|
|
- //console.log("Number of tunnels: "+numTunnels);
|
|
|
|
|
|
+ this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
|
|
|
|
+ return false; // wait until we receive the sub auth count to continue
|
|
|
|
+ } else {
|
|
|
|
+ return this._fail("Server wanted tunnels, but doesn't support the notunnel type");
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
|
|
- rfb_tightvnc = true;
|
|
|
|
|
|
+ _negotiate_tight_auth: function () {
|
|
|
|
+ if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
|
|
|
|
+ if (this._sock.rQwait("num tunnels", 4)) { return false; }
|
|
|
|
+ var numTunnels = this._sock.rQshift32();
|
|
|
|
+ if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
|
|
|
|
|
|
- if (numTunnels != 0)
|
|
|
|
- {
|
|
|
|
- fail("Protocol requested tunnels, not currently supported. numTunnels: " + numTunnels);
|
|
|
|
- return;
|
|
|
|
|
|
+ this._rfb_tightvnc = true;
|
|
|
|
+
|
|
|
|
+ if (numTunnels > 0) {
|
|
|
|
+ this._negotiate_tight_tunnels(numTunnels);
|
|
|
|
+ return false; // wait until we receive the sub auth to continue
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- var clientSupportedTypes = {
|
|
|
|
- 'STDVNOAUTH__': 1,
|
|
|
|
- 'STDVVNCAUTH_': 2
|
|
|
|
- };
|
|
|
|
|
|
+ // second pass, do the sub-auth negotiation
|
|
|
|
+ if (this._sock.rQwait("sub auth count", 4)) { return false; }
|
|
|
|
+ var subAuthCount = this._sock.rQshift32();
|
|
|
|
+ if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
|
|
|
|
|
|
- var serverSupportedTypes = [];
|
|
|
|
|
|
+ var clientSupportedTypes = {
|
|
|
|
+ 'STDVNOAUTH__': 1,
|
|
|
|
+ 'STDVVNCAUTH_': 2
|
|
|
|
+ };
|
|
|
|
|
|
- if (ws.rQwait("sub auth count", 4)) { return false; }
|
|
|
|
- var subAuthCount = ws.rQshift32();
|
|
|
|
- //console.log("Sub auth count: "+subAuthCount);
|
|
|
|
- for (var i=0;i<subAuthCount;i++)
|
|
|
|
- {
|
|
|
|
|
|
+ var serverSupportedTypes = [];
|
|
|
|
|
|
- if (ws.rQwait("sub auth capabilities "+i, 16)) { return false; }
|
|
|
|
- var capNum = ws.rQshift32();
|
|
|
|
- var capabilities = ws.rQshiftStr(12);
|
|
|
|
- //console.log("queue: "+ws.rQlen());
|
|
|
|
- //console.log("auth type: "+capNum+": "+capabilities);
|
|
|
|
|
|
+ for (var i = 0; i < subAuthCount; i++) {
|
|
|
|
+ var capNum = this._sock.rQshift32();
|
|
|
|
+ var capabilities = this._sock.rQshiftStr(12);
|
|
|
|
+ serverSupportedTypes.push(capabilities);
|
|
|
|
+ }
|
|
|
|
|
|
- serverSupportedTypes.push(capabilities);
|
|
|
|
|
|
+ for (var authType in clientSupportedTypes) {
|
|
|
|
+ if (serverSupportedTypes.indexOf(authType) != -1) {
|
|
|
|
+ this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
|
|
|
|
+
|
|
|
|
+ switch (authType) {
|
|
|
|
+ case 'STDVNOAUTH__': // no auth
|
|
|
|
+ this._updateState('SecurityResult');
|
|
|
|
+ return true;
|
|
|
|
+ case 'STDVVNCAUTH_': // VNC auth
|
|
|
|
+ this._rfb_auth_scheme = 2;
|
|
|
|
+ return this._init_msg();
|
|
|
|
+ default:
|
|
|
|
+ return this._fail("Unsupported tiny auth scheme: " + authType);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- for (var authType in clientSupportedTypes)
|
|
|
|
- {
|
|
|
|
- if (serverSupportedTypes.indexOf(authType) != -1)
|
|
|
|
- {
|
|
|
|
- //console.log("selected authType "+authType);
|
|
|
|
- ws.send([0,0,0,clientSupportedTypes[authType]]);
|
|
|
|
-
|
|
|
|
- switch (authType)
|
|
|
|
- {
|
|
|
|
- case 'STDVNOAUTH__':
|
|
|
|
- // No authentication
|
|
|
|
- updateState('SecurityResult');
|
|
|
|
- return;
|
|
|
|
- case 'STDVVNCAUTH_':
|
|
|
|
- // VNC Authentication. Reenter auth handler to complete auth
|
|
|
|
- rfb_auth_scheme = 2;
|
|
|
|
- init_msg();
|
|
|
|
- return;
|
|
|
|
- default:
|
|
|
|
- fail("Unsupported tiny auth scheme: " + authType);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ this._fail("No supported sub-auth types!");
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _negotiate_authentication: function () {
|
|
|
|
+ switch (this._rfb_auth_scheme) {
|
|
|
|
+ case 0: // connection failed
|
|
|
|
+ if (this._sock.rQwait("auth reason", 4)) { return false; }
|
|
|
|
+ var strlen = this._sock.rQshift32();
|
|
|
|
+ var reason = this._sock.rQshiftStr(strlen);
|
|
|
|
+ return this._fail("Auth failure: " + reason);
|
|
|
|
+
|
|
|
|
+ case 1: // no auth
|
|
|
|
+ if (this._rfb_version >= 3.8) {
|
|
|
|
+ this._updateState('SecurityResult');
|
|
|
|
+ return true;
|
|
}
|
|
}
|
|
|
|
+ this._updateState('ClientInitialisation', "No auth required");
|
|
|
|
+ return this._init_msg();
|
|
|
|
+
|
|
|
|
+ case 22: // XVP auth
|
|
|
|
+ return this._negotiate_xvp_auth();
|
|
|
|
+
|
|
|
|
+ case 2: // VNC authentication
|
|
|
|
+ return this._negotiate_std_vnc_auth();
|
|
|
|
+
|
|
|
|
+ case 16: // TightVNC Security Type
|
|
|
|
+ return this._negotiate_tight_auth();
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ return this._fail("Unsupported auth scheme: " + this._rfb_auth_scheme);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _handle_security_result: function () {
|
|
|
|
+ if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
|
|
|
|
+ switch (this._sock.rQshift32()) {
|
|
|
|
+ case 0: // OK
|
|
|
|
+ this._updateState('ClientInitialisation', 'Authentication OK');
|
|
|
|
+ return this._init_msg();
|
|
|
|
+ case 1: // failed
|
|
|
|
+ if (this._rfb_version >= 3.8) {
|
|
|
|
+ var length = this._sock.rQshift32();
|
|
|
|
+ if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
|
|
|
|
+ var reason = this._sock.rQshiftStr(length);
|
|
|
|
+ return this._fail(reason);
|
|
|
|
+ } else {
|
|
|
|
+ return this._fail("Authentication failure");
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+ case 2:
|
|
|
|
+ return this._fail("Too many auth attempts");
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _negotiate_server_init: function () {
|
|
|
|
+ if (this._sock.rQwait("server initialization", 24)) { return false; }
|
|
|
|
+
|
|
|
|
+ /* Screen size */
|
|
|
|
+ this._fb_width = this._sock.rQshift16();
|
|
|
|
+ this._fb_height = this._sock.rQshift16();
|
|
|
|
+
|
|
|
|
+ /* PIXEL_FORMAT */
|
|
|
|
+ var bpp = this._sock.rQshift8();
|
|
|
|
+ var depth = this._sock.rQshift8();
|
|
|
|
+ var big_endian = this._sock.rQshift8();
|
|
|
|
+ var true_color = this._sock.rQshift8();
|
|
|
|
+
|
|
|
|
+ var red_max = this._sock.rQshift16();
|
|
|
|
+ var green_max = this._sock.rQshift16();
|
|
|
|
+ var blue_max = this._sock.rQshift16();
|
|
|
|
+ var red_shift = this._sock.rQshift8();
|
|
|
|
+ var green_shift = this._sock.rQshift8();
|
|
|
|
+ var blue_shift = this._sock.rQshift8();
|
|
|
|
+ this._sock.rQskipBytes(3); // padding
|
|
|
|
+
|
|
|
|
+ // NB(directxman12): we don't want to call any callbacks or print messages until
|
|
|
|
+ // *after* we're past the point where we could backtrack
|
|
|
|
+
|
|
|
|
+ /* Connection name/title */
|
|
|
|
+ var name_length = this._sock.rQshift32();
|
|
|
|
+ if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
|
|
|
|
+ this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length));
|
|
|
|
+
|
|
|
|
+ if (this._rfb_tightvnc) {
|
|
|
|
+ if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
|
|
|
|
+ // In TightVNC mode, ServerInit message is extended
|
|
|
|
+ var numServerMessages = this._sock.rQshift16();
|
|
|
|
+ var numClientMessages = this._sock.rQshift16();
|
|
|
|
+ var numEncodings = this._sock.rQshift16();
|
|
|
|
+ this._sock.rQskipBytes(2); // padding
|
|
|
|
+
|
|
|
|
+ var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
|
|
|
|
+ if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
|
|
|
|
+
|
|
|
|
+ var i;
|
|
|
|
+ for (i = 0; i < numServerMessages; i++) {
|
|
|
|
+ var srvMsg = this._sock.rQshiftStr(16);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ for (i = 0; i < numClientMessages; i++) {
|
|
|
|
+ var clientMsg = this._sock.rQshiftStr(16);
|
|
|
|
+ }
|
|
|
|
|
|
- return;
|
|
|
|
- default:
|
|
|
|
- fail("Unsupported auth scheme: " + rfb_auth_scheme);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- updateState('ClientInitialisation', "No auth required");
|
|
|
|
- init_msg(); // Recursive fallthrough (workaround JSLint complaint)
|
|
|
|
- break;
|
|
|
|
-
|
|
|
|
- case 'SecurityResult' :
|
|
|
|
- if (ws.rQwait("VNC auth response ", 4)) { return false; }
|
|
|
|
- switch (ws.rQshift32()) {
|
|
|
|
- case 0: // OK
|
|
|
|
- // Fall through to ClientInitialisation
|
|
|
|
- break;
|
|
|
|
- case 1: // failed
|
|
|
|
- if (rfb_version >= 3.8) {
|
|
|
|
- length = ws.rQshift32();
|
|
|
|
- if (ws.rQwait("SecurityResult reason", length, 8)) {
|
|
|
|
|
|
+ for (i = 0; i < numEncodings; i++) {
|
|
|
|
+ var encoding = this._sock.rQshiftStr(16);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // NB(directxman12): these are down here so that we don't run them multiple times
|
|
|
|
+ // if we backtrack
|
|
|
|
+ Util.Info("Screen: " + this._fb_width + "x" + this._fb_height +
|
|
|
|
+ ", bpp: " + bpp + ", depth: " + depth +
|
|
|
|
+ ", big_endian: " + big_endian +
|
|
|
|
+ ", true_color: " + true_color +
|
|
|
|
+ ", red_max: " + red_max +
|
|
|
|
+ ", green_max: " + green_max +
|
|
|
|
+ ", blue_max: " + blue_max +
|
|
|
|
+ ", red_shift: " + red_shift +
|
|
|
|
+ ", green_shift: " + green_shift +
|
|
|
|
+ ", blue_shift: " + blue_shift);
|
|
|
|
+
|
|
|
|
+ if (big_endian !== 0) {
|
|
|
|
+ Util.Warn("Server native endian is not little endian");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (red_shift !== 16) {
|
|
|
|
+ Util.Warn("Server native red-shift is not 16");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (blue_shift !== 0) {
|
|
|
|
+ Util.Warn("Server native blue-shift is not 0");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // we're past the point where we could backtrack, so it's safe to call this
|
|
|
|
+ this._onDesktopName(this, this._fb_name);
|
|
|
|
+
|
|
|
|
+ if (this._true_color && this._fb_name === "Intel(r) AMT KVM") {
|
|
|
|
+ Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color");
|
|
|
|
+ this._true_color = false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this._display.set_true_color(this._true_color);
|
|
|
|
+ this._onFBResize(this, this._fb_width, this._fb_height);
|
|
|
|
+ this._display.resize(this._fb_width, this._fb_height);
|
|
|
|
+ this._keyboard.grab();
|
|
|
|
+ this._mouse.grab();
|
|
|
|
+
|
|
|
|
+ if (this._true_color) {
|
|
|
|
+ this._fb_Bpp = 4;
|
|
|
|
+ this._fb_depth = 3;
|
|
|
|
+ } else {
|
|
|
|
+ this._fb_Bpp = 1;
|
|
|
|
+ this._fb_depth = 1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var response = RFB.messages.pixelFormat(this._fb_Bpp, this._fb_depth, this._true_color);
|
|
|
|
+ response = response.concat(
|
|
|
|
+ RFB.messages.clientEncodings(this._encodings, this._local_cursor, this._true_color));
|
|
|
|
+ response = response.concat(
|
|
|
|
+ RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
|
|
|
|
+ this._fb_width, this._fb_height));
|
|
|
|
+
|
|
|
|
+ this._timing.fbu_rt_start = (new Date()).getTime();
|
|
|
|
+ this._timing.pixels = 0;
|
|
|
|
+ this._sock.send(response);
|
|
|
|
+
|
|
|
|
+ this._checkEvents();
|
|
|
|
+
|
|
|
|
+ if (this._encrypt) {
|
|
|
|
+ this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name);
|
|
|
|
+ } else {
|
|
|
|
+ this._updateState('normal', 'Connected (unencrypted) to: ' + this._fb_name);
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _init_msg: function () {
|
|
|
|
+ switch (this._rfb_state) {
|
|
|
|
+ case 'ProtocolVersion':
|
|
|
|
+ return this._negotiate_protocol_version();
|
|
|
|
+
|
|
|
|
+ case 'Security':
|
|
|
|
+ return this._negotiate_security();
|
|
|
|
+
|
|
|
|
+ case 'Authentication':
|
|
|
|
+ return this._negotiate_authentication();
|
|
|
|
+
|
|
|
|
+ case 'SecurityResult':
|
|
|
|
+ return this._handle_security_result();
|
|
|
|
+
|
|
|
|
+ case 'ClientInitialisation':
|
|
|
|
+ this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
|
|
|
|
+ this._updateState('ServerInitialisation', "Authentication OK");
|
|
|
|
+ return true;
|
|
|
|
+
|
|
|
|
+ case 'ServerInitialisation':
|
|
|
|
+ return this._negotiate_server_init();
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _handle_set_colour_map_msg: function () {
|
|
|
|
+ Util.Debug("SetColorMapEntries");
|
|
|
|
+ this._sock.rQskip8(); // Padding
|
|
|
|
+
|
|
|
|
+ var first_colour = this._sock.rQshift16();
|
|
|
|
+ var num_colours = this._sock.rQshift16();
|
|
|
|
+ if (this._sock.rQwait('SetColorMapEntries', num_colours * 6, 6)) { return false; }
|
|
|
|
+
|
|
|
|
+ for (var c = 0; c < num_colours; c++) {
|
|
|
|
+ var red = parseInt(this._sock.rQshift16() / 256, 10);
|
|
|
|
+ var green = parseInt(this._sock.rQshift16() / 256, 10);
|
|
|
|
+ var blue = parseInt(this._sock.rQshift16() / 256, 10);
|
|
|
|
+ this._display.set_colourMap([blue, green, red], first_colour + c);
|
|
|
|
+ }
|
|
|
|
+ Util.Debug("colourMap: " + this._display.get_colourMap());
|
|
|
|
+ Util.Info("Registered " + num_colours + " colourMap entries");
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _handle_server_cut_text: function () {
|
|
|
|
+ Util.Debug("ServerCutText");
|
|
|
|
+ if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
|
|
|
|
+ this._sock.rQskipBytes(3); // Padding
|
|
|
|
+ var length = this._sock.rQshift32();
|
|
|
|
+ if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
|
|
|
|
+
|
|
|
|
+ var text = this._sock.rQshiftStr(length);
|
|
|
|
+ this._onClipboard(this, text);
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _handle_xvp_msg: function () {
|
|
|
|
+ if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
|
|
|
|
+ this._sock.rQskip8(); // Padding
|
|
|
|
+ var xvp_ver = this._sock.rQshift8();
|
|
|
|
+ var xvp_msg = this._sock.rQshift8();
|
|
|
|
+
|
|
|
|
+ switch (xvp_msg) {
|
|
|
|
+ case 0: // XVP_FAIL
|
|
|
|
+ this._updateState(this._rfb_state, "Operation Failed");
|
|
|
|
+ break;
|
|
|
|
+ case 1: // XVP_INIT
|
|
|
|
+ this._rfb_xvp_ver = xvp_ver;
|
|
|
|
+ Util.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
|
|
|
|
+ this._onXvpInit(this._rfb_xvp_ver);
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ this._fail("Disconnected: illegal server XVP message " + xvp_msg);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _normal_msg: function () {
|
|
|
|
+ var msg_type;
|
|
|
|
+
|
|
|
|
+ if (this._FBU.rects > 0) {
|
|
|
|
+ msg_type = 0;
|
|
|
|
+ } else {
|
|
|
|
+ msg_type = this._sock.rQshift8();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ switch (msg_type) {
|
|
|
|
+ case 0: // FramebufferUpdate
|
|
|
|
+ var ret = this._framebufferUpdate();
|
|
|
|
+ if (ret) {
|
|
|
|
+ this._sock.send(RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
|
|
|
|
+ this._fb_width, this._fb_height));
|
|
|
|
+ }
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ case 1: // SetColorMapEntries
|
|
|
|
+ return this._handle_set_colour_map_msg();
|
|
|
|
+
|
|
|
|
+ case 2: // Bell
|
|
|
|
+ Util.Debug("Bell");
|
|
|
|
+ this._onBell(this);
|
|
|
|
+ return true;
|
|
|
|
+
|
|
|
|
+ case 3: // ServerCutText
|
|
|
|
+ return this._handle_server_cut_text();
|
|
|
|
+
|
|
|
|
+ case 250: // XVP
|
|
|
|
+ return this._handle_xvp_msg();
|
|
|
|
+
|
|
|
|
+ default:
|
|
|
|
+ this._fail("Disconnected: illegal server message type " + msg_type);
|
|
|
|
+ Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ _framebufferUpdate: function () {
|
|
|
|
+ var ret = true;
|
|
|
|
+ var now;
|
|
|
|
+
|
|
|
|
+ if (this._FBU.rects === 0) {
|
|
|
|
+ if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
|
|
|
|
+ this._sock.rQskip8(); // Padding
|
|
|
|
+ this._FBU.rects = this._sock.rQshift16();
|
|
|
|
+ this._FBU.bytes = 0;
|
|
|
|
+ this._timing.cur_fbu = 0;
|
|
|
|
+ if (this._timing.fbu_rt_start > 0) {
|
|
|
|
+ now = (new Date()).getTime();
|
|
|
|
+ Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ while (this._FBU.rects > 0) {
|
|
|
|
+ if (this._rfb_state !== "normal") { return false; }
|
|
|
|
+
|
|
|
|
+ if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; }
|
|
|
|
+ if (this._FBU.bytes === 0) {
|
|
|
|
+ if (this._sock.rQwait("rect header", 12)) { return false; }
|
|
|
|
+ /* New FramebufferUpdate */
|
|
|
|
+
|
|
|
|
+ var hdr = this._sock.rQshiftBytes(12);
|
|
|
|
+ this._FBU.x = (hdr[0] << 8) + hdr[1];
|
|
|
|
+ this._FBU.y = (hdr[2] << 8) + hdr[3];
|
|
|
|
+ this._FBU.width = (hdr[4] << 8) + hdr[5];
|
|
|
|
+ this._FBU.height = (hdr[6] << 8) + hdr[7];
|
|
|
|
+ this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
|
|
|
|
+ (hdr[10] << 8) + hdr[11], 10);
|
|
|
|
+
|
|
|
|
+ this._onFBUReceive(this,
|
|
|
|
+ {'x': this._FBU.x, 'y': this._FBU.y,
|
|
|
|
+ 'width': this._FBU.width, 'height': this._FBU.height,
|
|
|
|
+ 'encoding': this._FBU.encoding,
|
|
|
|
+ 'encodingName': this._encNames[this._FBU.encoding]});
|
|
|
|
+
|
|
|
|
+ if (!this._encNames[this._FBU.encoding]) {
|
|
|
|
+ this._fail("Disconnected: unsupported encoding " +
|
|
|
|
+ this._FBU.encoding);
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
- reason = ws.rQshiftStr(length);
|
|
|
|
- fail(reason);
|
|
|
|
- } else {
|
|
|
|
- fail("Authentication failed");
|
|
|
|
}
|
|
}
|
|
- return;
|
|
|
|
- case 2: // too-many
|
|
|
|
- return fail("Too many auth attempts");
|
|
|
|
- }
|
|
|
|
- updateState('ClientInitialisation', "Authentication OK");
|
|
|
|
- init_msg(); // Recursive fallthrough (workaround JSLint complaint)
|
|
|
|
- break;
|
|
|
|
-
|
|
|
|
- // Triggered by fallthough, not by server message
|
|
|
|
- case 'ClientInitialisation' :
|
|
|
|
- ws.send([conf.shared ? 1 : 0]); // ClientInitialisation
|
|
|
|
- updateState('ServerInitialisation', "Authentication OK");
|
|
|
|
- break;
|
|
|
|
-
|
|
|
|
- case 'ServerInitialisation' :
|
|
|
|
- if (ws.rQwait("server initialization", 24)) { return false; }
|
|
|
|
-
|
|
|
|
- /* Screen size */
|
|
|
|
- fb_width = ws.rQshift16();
|
|
|
|
- fb_height = ws.rQshift16();
|
|
|
|
-
|
|
|
|
- /* PIXEL_FORMAT */
|
|
|
|
- bpp = ws.rQshift8();
|
|
|
|
- depth = ws.rQshift8();
|
|
|
|
- big_endian = ws.rQshift8();
|
|
|
|
- true_color = ws.rQshift8();
|
|
|
|
-
|
|
|
|
- red_max = ws.rQshift16();
|
|
|
|
- green_max = ws.rQshift16();
|
|
|
|
- blue_max = ws.rQshift16();
|
|
|
|
- red_shift = ws.rQshift8();
|
|
|
|
- green_shift = ws.rQshift8();
|
|
|
|
- blue_shift = ws.rQshift8();
|
|
|
|
- ws.rQshiftStr(3); // padding
|
|
|
|
-
|
|
|
|
- Util.Info("Screen: " + fb_width + "x" + fb_height +
|
|
|
|
- ", bpp: " + bpp + ", depth: " + depth +
|
|
|
|
- ", big_endian: " + big_endian +
|
|
|
|
- ", true_color: " + true_color +
|
|
|
|
- ", red_max: " + red_max +
|
|
|
|
- ", green_max: " + green_max +
|
|
|
|
- ", blue_max: " + blue_max +
|
|
|
|
- ", red_shift: " + red_shift +
|
|
|
|
- ", green_shift: " + green_shift +
|
|
|
|
- ", blue_shift: " + blue_shift);
|
|
|
|
-
|
|
|
|
- if (big_endian !== 0) {
|
|
|
|
- Util.Warn("Server native endian is not little endian");
|
|
|
|
- }
|
|
|
|
- if (red_shift !== 16) {
|
|
|
|
- Util.Warn("Server native red-shift is not 16");
|
|
|
|
- }
|
|
|
|
- if (blue_shift !== 0) {
|
|
|
|
- Util.Warn("Server native blue-shift is not 0");
|
|
|
|
- }
|
|
|
|
|
|
|
|
- /* Connection name/title */
|
|
|
|
- name_length = ws.rQshift32();
|
|
|
|
- fb_name = Util.decodeUTF8(ws.rQshiftStr(name_length));
|
|
|
|
- conf.onDesktopName(that, fb_name);
|
|
|
|
-
|
|
|
|
- if (conf.true_color && fb_name === "Intel(r) AMT KVM")
|
|
|
|
- {
|
|
|
|
- Util.Warn("Intel AMT KVM only support 8/16 bit depths. Disabling true color");
|
|
|
|
- conf.true_color = false;
|
|
|
|
- }
|
|
|
|
|
|
+ this._timing.last_fbu = (new Date()).getTime();
|
|
|
|
+
|
|
|
|
+ ret = this._encHandlers[this._FBU.encoding]();
|
|
|
|
+
|
|
|
|
+ now = (new Date()).getTime();
|
|
|
|
+ this._timing.cur_fbu += (now - this._timing.last_fbu);
|
|
|
|
|
|
- if (rfb_tightvnc)
|
|
|
|
- {
|
|
|
|
- // In TightVNC mode, ServerInit message is extended
|
|
|
|
- var numServerMessages = ws.rQshift16();
|
|
|
|
- var numClientMessages = ws.rQshift16();
|
|
|
|
- var numEncodings = ws.rQshift16();
|
|
|
|
- ws.rQshift16(); // padding
|
|
|
|
- //console.log("numServerMessages "+numServerMessages);
|
|
|
|
- //console.log("numClientMessages "+numClientMessages);
|
|
|
|
- //console.log("numEncodings "+numEncodings);
|
|
|
|
-
|
|
|
|
- for (var i=0;i<numServerMessages;i++)
|
|
|
|
- {
|
|
|
|
- var srvMsg = ws.rQshiftStr(16);
|
|
|
|
- //console.log("server message: "+srvMsg);
|
|
|
|
- }
|
|
|
|
- for (var i=0;i<numClientMessages;i++)
|
|
|
|
- {
|
|
|
|
- var clientMsg = ws.rQshiftStr(16);
|
|
|
|
- //console.log("client message: "+clientMsg);
|
|
|
|
- }
|
|
|
|
- for (var i=0;i<numEncodings;i++)
|
|
|
|
- {
|
|
|
|
- var encoding = ws.rQshiftStr(16);
|
|
|
|
- //console.log("encoding: "+encoding);
|
|
|
|
|
|
+ if (ret) {
|
|
|
|
+ this._encStats[this._FBU.encoding][0]++;
|
|
|
|
+ this._encStats[this._FBU.encoding][1]++;
|
|
|
|
+ this._timing.pixels += this._FBU.width * this._FBU.height;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this._timing.pixels >= (this._fb_width * this._fb_height)) {
|
|
|
|
+ if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) ||
|
|
|
|
+ this._timing.fbu_rt_start > 0) {
|
|
|
|
+ this._timing.full_fbu_total += this._timing.cur_fbu;
|
|
|
|
+ this._timing.full_fbu_cnt++;
|
|
|
|
+ Util.Info("Timing of full FBU, curr: " +
|
|
|
|
+ this._timing.cur_fbu + ", total: " +
|
|
|
|
+ this._timing.full_fbu_total + ", cnt: " +
|
|
|
|
+ this._timing.full_fbu_cnt + ", avg: " +
|
|
|
|
+ (this._timing.full_fbu_total / this._timing.full_fbu_cnt));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this._timing.fbu_rt_start > 0) {
|
|
|
|
+ var fbu_rt_diff = now - this._timing.fbu_rt_start;
|
|
|
|
+ this._timing.fbu_rt_total += fbu_rt_diff;
|
|
|
|
+ this._timing.fbu_rt_cnt++;
|
|
|
|
+ Util.Info("full FBU round-trip, cur: " +
|
|
|
|
+ fbu_rt_diff + ", total: " +
|
|
|
|
+ this._timing.fbu_rt_total + ", cnt: " +
|
|
|
|
+ this._timing.fbu_rt_cnt + ", avg: " +
|
|
|
|
+ (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt));
|
|
|
|
+ this._timing.fbu_rt_start = 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!ret) { return ret; } // need more data
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- display.set_true_color(conf.true_color);
|
|
|
|
- conf.onFBResize(that, fb_width, fb_height);
|
|
|
|
- display.resize(fb_width, fb_height);
|
|
|
|
- keyboard.grab();
|
|
|
|
- mouse.grab();
|
|
|
|
|
|
+ this._onFBUComplete(this,
|
|
|
|
+ {'x': this._FBU.x, 'y': this._FBU.y,
|
|
|
|
+ 'width': this._FBU.width, 'height': this._FBU.height,
|
|
|
|
+ 'encoding': this._FBU.encoding,
|
|
|
|
+ 'encodingName': this._encNames[this._FBU.encoding]});
|
|
|
|
|
|
- if (conf.true_color) {
|
|
|
|
- fb_Bpp = 4;
|
|
|
|
- fb_depth = 3;
|
|
|
|
- } else {
|
|
|
|
- fb_Bpp = 1;
|
|
|
|
- fb_depth = 1;
|
|
|
|
- }
|
|
|
|
|
|
+ return true; // We finished this FBU
|
|
|
|
+ },
|
|
|
|
+ };
|
|
|
|
|
|
- response = pixelFormat();
|
|
|
|
- response = response.concat(clientEncodings());
|
|
|
|
- response = response.concat(fbUpdateRequests()); // initial fbu-request
|
|
|
|
- timing.fbu_rt_start = (new Date()).getTime();
|
|
|
|
- timing.pixels = 0;
|
|
|
|
- ws.send(response);
|
|
|
|
-
|
|
|
|
- checkEvents();
|
|
|
|
-
|
|
|
|
- if (conf.encrypt) {
|
|
|
|
- updateState('normal', "Connected (encrypted) to: " + fb_name);
|
|
|
|
- } else {
|
|
|
|
- updateState('normal', "Connected (unencrypted) to: " + fb_name);
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- //Util.Debug("<< init_msg");
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-/* Normal RFB/VNC server message handler */
|
|
|
|
-normal_msg = function() {
|
|
|
|
- //Util.Debug(">> normal_msg");
|
|
|
|
-
|
|
|
|
- var ret = true, msg_type, length, text,
|
|
|
|
- c, first_colour, num_colours, red, green, blue,
|
|
|
|
- xvp_ver, xvp_msg;
|
|
|
|
-
|
|
|
|
- if (FBU.rects > 0) {
|
|
|
|
- msg_type = 0;
|
|
|
|
- } else {
|
|
|
|
- msg_type = ws.rQshift8();
|
|
|
|
- }
|
|
|
|
- switch (msg_type) {
|
|
|
|
- case 0: // FramebufferUpdate
|
|
|
|
- ret = framebufferUpdate(); // false means need more data
|
|
|
|
- if (ret) {
|
|
|
|
- // only allow one outstanding fbu-request at a time
|
|
|
|
- ws.send(fbUpdateRequests());
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- case 1: // SetColourMapEntries
|
|
|
|
- Util.Debug("SetColourMapEntries");
|
|
|
|
- ws.rQshift8(); // Padding
|
|
|
|
- first_colour = ws.rQshift16(); // First colour
|
|
|
|
- num_colours = ws.rQshift16();
|
|
|
|
- if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; }
|
|
|
|
-
|
|
|
|
- for (c=0; c < num_colours; c+=1) {
|
|
|
|
- red = ws.rQshift16();
|
|
|
|
- //Util.Debug("red before: " + red);
|
|
|
|
- red = parseInt(red / 256, 10);
|
|
|
|
- //Util.Debug("red after: " + red);
|
|
|
|
- green = parseInt(ws.rQshift16() / 256, 10);
|
|
|
|
- blue = parseInt(ws.rQshift16() / 256, 10);
|
|
|
|
- display.set_colourMap([blue, green, red], first_colour + c);
|
|
|
|
- }
|
|
|
|
- Util.Debug("colourMap: " + display.get_colourMap());
|
|
|
|
- Util.Info("Registered " + num_colours + " colourMap entries");
|
|
|
|
- //Util.Debug("colourMap: " + display.get_colourMap());
|
|
|
|
- break;
|
|
|
|
- case 2: // Bell
|
|
|
|
- Util.Debug("Bell");
|
|
|
|
- conf.onBell(that);
|
|
|
|
- break;
|
|
|
|
- case 3: // ServerCutText
|
|
|
|
- Util.Debug("ServerCutText");
|
|
|
|
- if (ws.rQwait("ServerCutText header", 7, 1)) { return false; }
|
|
|
|
- ws.rQshiftBytes(3); // Padding
|
|
|
|
- length = ws.rQshift32();
|
|
|
|
- if (ws.rQwait("ServerCutText", length, 8)) { return false; }
|
|
|
|
-
|
|
|
|
- text = ws.rQshiftStr(length);
|
|
|
|
- conf.clipboardReceive(that, text); // Obsolete
|
|
|
|
- conf.onClipboard(that, text);
|
|
|
|
- break;
|
|
|
|
- case 250: // XVP
|
|
|
|
- ws.rQshift8(); // Padding
|
|
|
|
- xvp_ver = ws.rQshift8();
|
|
|
|
- xvp_msg = ws.rQshift8();
|
|
|
|
- switch (xvp_msg) {
|
|
|
|
- case 0: // XVP_FAIL
|
|
|
|
- updateState(rfb_state, "Operation failed");
|
|
|
|
- break;
|
|
|
|
- case 1: // XVP_INIT
|
|
|
|
- rfb_xvp_ver = xvp_ver;
|
|
|
|
- Util.Info("XVP extensions enabled (version " + rfb_xvp_ver + ")");
|
|
|
|
- conf.onXvpInit(rfb_xvp_ver);
|
|
|
|
- break;
|
|
|
|
- default:
|
|
|
|
- fail("Disconnected: illegal server XVP message " + xvp_msg);
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- default:
|
|
|
|
- fail("Disconnected: illegal server message type " + msg_type);
|
|
|
|
- Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- //Util.Debug("<< normal_msg");
|
|
|
|
- return ret;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-framebufferUpdate = function() {
|
|
|
|
- var now, hdr, fbu_rt_diff, ret = true;
|
|
|
|
-
|
|
|
|
- if (FBU.rects === 0) {
|
|
|
|
- //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20));
|
|
|
|
- if (ws.rQwait("FBU header", 3)) {
|
|
|
|
- ws.rQunshift8(0); // FBU msg_type
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- ws.rQshift8(); // padding
|
|
|
|
- FBU.rects = ws.rQshift16();
|
|
|
|
- //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
|
|
|
|
- FBU.bytes = 0;
|
|
|
|
- timing.cur_fbu = 0;
|
|
|
|
- if (timing.fbu_rt_start > 0) {
|
|
|
|
- now = (new Date()).getTime();
|
|
|
|
- Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ Util.make_properties(RFB, [
|
|
|
|
+ ['target', 'wo', 'dom'], // VNC display rendering Canvas object
|
|
|
|
+ ['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input
|
|
|
|
+ ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption
|
|
|
|
+ ['true_color', 'rw', 'bool'], // Request true color pixel data
|
|
|
|
+ ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor
|
|
|
|
+ ['shared', 'rw', 'bool'], // Request shared mode
|
|
|
|
+ ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard
|
|
|
|
+ ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields
|
|
|
|
+ ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection
|
|
|
|
+ ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection
|
|
|
|
+ ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to
|
|
|
|
+ ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags
|
|
|
|
+
|
|
|
|
+ // Callback functions
|
|
|
|
+ ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change
|
|
|
|
+ ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb): VNC password is required
|
|
|
|
+ ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received
|
|
|
|
+ ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received
|
|
|
|
+ ['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
|
|
|
|
+ ['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed
|
|
|
|
+ ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized
|
|
|
|
+ ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received
|
|
|
|
+ ['onXvpInit', 'rw', 'func'], // onXvpInit(version): XVP extensions active for this connection
|
|
|
|
+ ]);
|
|
|
|
|
|
- while (FBU.rects > 0) {
|
|
|
|
- if (rfb_state !== "normal") {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- if (ws.rQwait("FBU", FBU.bytes)) { return false; }
|
|
|
|
- if (FBU.bytes === 0) {
|
|
|
|
- if (ws.rQwait("rect header", 12)) { return false; }
|
|
|
|
- /* New FramebufferUpdate */
|
|
|
|
-
|
|
|
|
- hdr = ws.rQshiftBytes(12);
|
|
|
|
- FBU.x = (hdr[0] << 8) + hdr[1];
|
|
|
|
- FBU.y = (hdr[2] << 8) + hdr[3];
|
|
|
|
- FBU.width = (hdr[4] << 8) + hdr[5];
|
|
|
|
- FBU.height = (hdr[6] << 8) + hdr[7];
|
|
|
|
- FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
|
|
|
|
- (hdr[10] << 8) + hdr[11], 10);
|
|
|
|
-
|
|
|
|
- conf.onFBUReceive(that,
|
|
|
|
- {'x': FBU.x, 'y': FBU.y,
|
|
|
|
- 'width': FBU.width, 'height': FBU.height,
|
|
|
|
- 'encoding': FBU.encoding,
|
|
|
|
- 'encodingName': encNames[FBU.encoding]});
|
|
|
|
-
|
|
|
|
- if (encNames[FBU.encoding]) {
|
|
|
|
- // Debug:
|
|
|
|
- /*
|
|
|
|
- var msg = "FramebufferUpdate rects:" + FBU.rects;
|
|
|
|
- msg += " x: " + FBU.x + " y: " + FBU.y;
|
|
|
|
- msg += " width: " + FBU.width + " height: " + FBU.height;
|
|
|
|
- msg += " encoding:" + FBU.encoding;
|
|
|
|
- msg += "(" + encNames[FBU.encoding] + ")";
|
|
|
|
- msg += ", ws.rQlen(): " + ws.rQlen();
|
|
|
|
- Util.Debug(msg);
|
|
|
|
- */
|
|
|
|
|
|
+ RFB.prototype.set_local_cursor = function (cursor) {
|
|
|
|
+ if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) {
|
|
|
|
+ this._local_cursor = false;
|
|
|
|
+ } else {
|
|
|
|
+ if (this._display.get_cursor_uri()) {
|
|
|
|
+ this._local_cursor = true;
|
|
} else {
|
|
} else {
|
|
- fail("Disconnected: unsupported encoding " +
|
|
|
|
- FBU.encoding);
|
|
|
|
- return false;
|
|
|
|
|
|
+ Util.Warn("Browser does not support local cursor");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ };
|
|
|
|
|
|
- timing.last_fbu = (new Date()).getTime();
|
|
|
|
|
|
+ RFB.prototype.get_display = function () { return this._display; };
|
|
|
|
+ RFB.prototype.get_keyboard = function () { return this._keyboard; };
|
|
|
|
+ RFB.prototype.get_mouse = function () { return this._mouse; };
|
|
|
|
+
|
|
|
|
+ // Class Methods
|
|
|
|
+ RFB.messages = {
|
|
|
|
+ keyEvent: function (keysym, down) {
|
|
|
|
+ var arr = [4];
|
|
|
|
+ arr.push8(down);
|
|
|
|
+ arr.push16(0);
|
|
|
|
+ arr.push32(keysym);
|
|
|
|
+ return arr;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ pointerEvent: function (x, y, mask) {
|
|
|
|
+ var arr = [5]; // msg-type
|
|
|
|
+ arr.push8(mask);
|
|
|
|
+ arr.push16(x);
|
|
|
|
+ arr.push16(y);
|
|
|
|
+ return arr;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // TODO(directxman12): make this unicode compatible?
|
|
|
|
+ clientCutText: function (text) {
|
|
|
|
+ var arr = [6]; // msg-type
|
|
|
|
+ arr.push8(0); // padding
|
|
|
|
+ arr.push8(0); // padding
|
|
|
|
+ arr.push8(0); // padding
|
|
|
|
+ arr.push32(text.length);
|
|
|
|
+ var n = text.length;
|
|
|
|
+ for (var i = 0; i < n; i++) {
|
|
|
|
+ arr.push(text.charCodeAt(i));
|
|
|
|
+ }
|
|
|
|
|
|
- ret = encHandlers[FBU.encoding]();
|
|
|
|
|
|
+ return arr;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ pixelFormat: function (bpp, depth, true_color) {
|
|
|
|
+ var arr = [0]; // msg-type
|
|
|
|
+ arr.push8(0); // padding
|
|
|
|
+ arr.push8(0); // padding
|
|
|
|
+ arr.push8(0); // padding
|
|
|
|
+
|
|
|
|
+ arr.push8(bpp * 8); // bits-per-pixel
|
|
|
|
+ arr.push8(depth * 8); // depth
|
|
|
|
+ arr.push8(0); // little-endian
|
|
|
|
+ arr.push8(true_color ? 1 : 0); // true-color
|
|
|
|
+
|
|
|
|
+ arr.push16(255); // red-max
|
|
|
|
+ arr.push16(255); // green-max
|
|
|
|
+ arr.push16(255); // blue-max
|
|
|
|
+ arr.push8(16); // red-shift
|
|
|
|
+ arr.push8(8); // green-shift
|
|
|
|
+ arr.push8(0); // blue-shift
|
|
|
|
+
|
|
|
|
+ arr.push8(0); // padding
|
|
|
|
+ arr.push8(0); // padding
|
|
|
|
+ arr.push8(0); // padding
|
|
|
|
+ return arr;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ clientEncodings: function (encodings, local_cursor, true_color) {
|
|
|
|
+ var i, encList = [];
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < encodings.length; i++) {
|
|
|
|
+ if (encodings[i][0] === "Cursor" && !local_cursor) {
|
|
|
|
+ Util.Debug("Skipping Cursor pseudo-encoding");
|
|
|
|
+ } else if (encodings[i][0] === "TIGHT" && !true_color) {
|
|
|
|
+ // TODO: remove this when we have tight+non-true-color
|
|
|
|
+ Util.Warn("Skipping tight as it is only supported with true color");
|
|
|
|
+ } else {
|
|
|
|
+ encList.push(encodings[i][1]);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- now = (new Date()).getTime();
|
|
|
|
- timing.cur_fbu += (now - timing.last_fbu);
|
|
|
|
|
|
+ var arr = [2]; // msg-type
|
|
|
|
+ arr.push8(0); // padding
|
|
|
|
|
|
- if (ret) {
|
|
|
|
- encStats[FBU.encoding][0] += 1;
|
|
|
|
- encStats[FBU.encoding][1] += 1;
|
|
|
|
- timing.pixels += FBU.width * FBU.height;
|
|
|
|
- }
|
|
|
|
|
|
+ arr.push16(encList.length); // encoding count
|
|
|
|
+ for (i = 0; i < encList.length; i++) {
|
|
|
|
+ arr.push32(encList[i]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return arr;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ fbUpdateRequests: function (cleanDirty, fb_width, fb_height) {
|
|
|
|
+ var arr = [];
|
|
|
|
|
|
- if (timing.pixels >= (fb_width * fb_height)) {
|
|
|
|
- if (((FBU.width === fb_width) &&
|
|
|
|
- (FBU.height === fb_height)) ||
|
|
|
|
- (timing.fbu_rt_start > 0)) {
|
|
|
|
- timing.full_fbu_total += timing.cur_fbu;
|
|
|
|
- timing.full_fbu_cnt += 1;
|
|
|
|
- Util.Info("Timing of full FBU, cur: " +
|
|
|
|
- timing.cur_fbu + ", total: " +
|
|
|
|
- timing.full_fbu_total + ", cnt: " +
|
|
|
|
- timing.full_fbu_cnt + ", avg: " +
|
|
|
|
- (timing.full_fbu_total /
|
|
|
|
- timing.full_fbu_cnt));
|
|
|
|
- }
|
|
|
|
- if (timing.fbu_rt_start > 0) {
|
|
|
|
- fbu_rt_diff = now - timing.fbu_rt_start;
|
|
|
|
- timing.fbu_rt_total += fbu_rt_diff;
|
|
|
|
- timing.fbu_rt_cnt += 1;
|
|
|
|
- Util.Info("full FBU round-trip, cur: " +
|
|
|
|
- fbu_rt_diff + ", total: " +
|
|
|
|
- timing.fbu_rt_total + ", cnt: " +
|
|
|
|
- timing.fbu_rt_cnt + ", avg: " +
|
|
|
|
- (timing.fbu_rt_total /
|
|
|
|
- timing.fbu_rt_cnt));
|
|
|
|
- timing.fbu_rt_start = 0;
|
|
|
|
|
|
+ var cb = cleanDirty.cleanBox;
|
|
|
|
+ var w, h;
|
|
|
|
+ if (cb.w > 0 && cb.h > 0) {
|
|
|
|
+ w = typeof cb.w === "undefined" ? fb_width : cb.w;
|
|
|
|
+ h = typeof cb.h === "undefined" ? fb_height : cb.h;
|
|
|
|
+ // Request incremental for clean box
|
|
|
|
+ arr = arr.concat(RFB.messages.fbUpdateRequest(1, cb.x, cb.y, w, h));
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ for (var i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
|
|
|
|
+ var db = cleanDirty.dirtyBoxes[i];
|
|
|
|
+ // Force all (non-incremental) for dirty box
|
|
|
|
+ w = typeof db.w === "undefined" ? fb_width : db.w;
|
|
|
|
+ h = typeof db.h === "undefined" ? fb_height : db.h;
|
|
|
|
+ arr = arr.concat(RFB.messages.fbUpdateRequest(0, db.x, db.y, w, h));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return arr;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ fbUpdateRequest: function (incremental, x, y, w, h) {
|
|
|
|
+ if (typeof(x) === "undefined") { x = 0; }
|
|
|
|
+ if (typeof(y) === "undefined") { y = 0; }
|
|
|
|
+
|
|
|
|
+ var arr = [3]; // msg-type
|
|
|
|
+ arr.push8(incremental);
|
|
|
|
+ arr.push16(x);
|
|
|
|
+ arr.push16(y);
|
|
|
|
+ arr.push16(w);
|
|
|
|
+ arr.push16(h);
|
|
|
|
+
|
|
|
|
+ return arr;
|
|
}
|
|
}
|
|
- if (! ret) {
|
|
|
|
- return ret; // false ret means need more data
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- conf.onFBUComplete(that,
|
|
|
|
- {'x': FBU.x, 'y': FBU.y,
|
|
|
|
- 'width': FBU.width, 'height': FBU.height,
|
|
|
|
- 'encoding': FBU.encoding,
|
|
|
|
- 'encodingName': encNames[FBU.encoding]});
|
|
|
|
-
|
|
|
|
- return true; // We finished this FBU
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-//
|
|
|
|
-// FramebufferUpdate encodings
|
|
|
|
-//
|
|
|
|
-
|
|
|
|
-encHandlers.RAW = function display_raw() {
|
|
|
|
- //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)");
|
|
|
|
-
|
|
|
|
- var cur_y, cur_height;
|
|
|
|
-
|
|
|
|
- if (FBU.lines === 0) {
|
|
|
|
- FBU.lines = FBU.height;
|
|
|
|
- }
|
|
|
|
- FBU.bytes = FBU.width * fb_Bpp; // At least a line
|
|
|
|
- if (ws.rQwait("RAW", FBU.bytes)) { return false; }
|
|
|
|
- cur_y = FBU.y + (FBU.height - FBU.lines);
|
|
|
|
- cur_height = Math.min(FBU.lines,
|
|
|
|
- Math.floor(ws.rQlen()/(FBU.width * fb_Bpp)));
|
|
|
|
- display.blitImage(FBU.x, cur_y, FBU.width, cur_height,
|
|
|
|
- ws.get_rQ(), ws.get_rQi());
|
|
|
|
- ws.rQshiftBytes(FBU.width * cur_height * fb_Bpp);
|
|
|
|
- FBU.lines -= cur_height;
|
|
|
|
-
|
|
|
|
- if (FBU.lines > 0) {
|
|
|
|
- FBU.bytes = FBU.width * fb_Bpp; // At least another line
|
|
|
|
- } else {
|
|
|
|
- FBU.rects -= 1;
|
|
|
|
- FBU.bytes = 0;
|
|
|
|
- }
|
|
|
|
- //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)");
|
|
|
|
- return true;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-encHandlers.COPYRECT = function display_copy_rect() {
|
|
|
|
- //Util.Debug(">> display_copy_rect");
|
|
|
|
-
|
|
|
|
- var old_x, old_y;
|
|
|
|
-
|
|
|
|
- FBU.bytes = 4;
|
|
|
|
- if (ws.rQwait("COPYRECT", 4)) { return false; }
|
|
|
|
- display.renderQ_push({
|
|
|
|
- 'type': 'copy',
|
|
|
|
- 'old_x': ws.rQshift16(),
|
|
|
|
- 'old_y': ws.rQshift16(),
|
|
|
|
- 'x': FBU.x,
|
|
|
|
- 'y': FBU.y,
|
|
|
|
- 'width': FBU.width,
|
|
|
|
- 'height': FBU.height});
|
|
|
|
- FBU.rects -= 1;
|
|
|
|
- FBU.bytes = 0;
|
|
|
|
- return true;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-encHandlers.RRE = function display_rre() {
|
|
|
|
- //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)");
|
|
|
|
- var color, x, y, width, height, chunk;
|
|
|
|
-
|
|
|
|
- if (FBU.subrects === 0) {
|
|
|
|
- FBU.bytes = 4+fb_Bpp;
|
|
|
|
- if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; }
|
|
|
|
- FBU.subrects = ws.rQshift32();
|
|
|
|
- color = ws.rQshiftBytes(fb_Bpp); // Background
|
|
|
|
- display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
|
|
|
|
- }
|
|
|
|
- while ((FBU.subrects > 0) && (ws.rQlen() >= (fb_Bpp + 8))) {
|
|
|
|
- color = ws.rQshiftBytes(fb_Bpp);
|
|
|
|
- x = ws.rQshift16();
|
|
|
|
- y = ws.rQshift16();
|
|
|
|
- width = ws.rQshift16();
|
|
|
|
- height = ws.rQshift16();
|
|
|
|
- display.fillRect(FBU.x + x, FBU.y + y, width, height, color);
|
|
|
|
- FBU.subrects -= 1;
|
|
|
|
- }
|
|
|
|
- //Util.Debug(" display_rre: rects: " + FBU.rects +
|
|
|
|
- // ", FBU.subrects: " + FBU.subrects);
|
|
|
|
-
|
|
|
|
- if (FBU.subrects > 0) {
|
|
|
|
- chunk = Math.min(rre_chunk_sz, FBU.subrects);
|
|
|
|
- FBU.bytes = (fb_Bpp + 8) * chunk;
|
|
|
|
- } else {
|
|
|
|
- FBU.rects -= 1;
|
|
|
|
- FBU.bytes = 0;
|
|
|
|
- }
|
|
|
|
- //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);
|
|
|
|
- return true;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-encHandlers.HEXTILE = function display_hextile() {
|
|
|
|
- //Util.Debug(">> display_hextile");
|
|
|
|
- var subencoding, subrects, color, cur_tile,
|
|
|
|
- tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh,
|
|
|
|
- rQ = ws.get_rQ(), rQi = ws.get_rQi();
|
|
|
|
-
|
|
|
|
- if (FBU.tiles === 0) {
|
|
|
|
- FBU.tiles_x = Math.ceil(FBU.width/16);
|
|
|
|
- FBU.tiles_y = Math.ceil(FBU.height/16);
|
|
|
|
- FBU.total_tiles = FBU.tiles_x * FBU.tiles_y;
|
|
|
|
- FBU.tiles = FBU.total_tiles;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */
|
|
|
|
- while (FBU.tiles > 0) {
|
|
|
|
- FBU.bytes = 1;
|
|
|
|
- if (ws.rQwait("HEXTILE subencoding", FBU.bytes)) { return false; }
|
|
|
|
- subencoding = rQ[rQi]; // Peek
|
|
|
|
- if (subencoding > 30) { // Raw
|
|
|
|
- fail("Disconnected: illegal hextile subencoding " + subencoding);
|
|
|
|
- //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
|
|
|
|
- return false;
|
|
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ RFB.genDES = function (password, challenge) {
|
|
|
|
+ var passwd = [];
|
|
|
|
+ for (var i = 0; i < password.length; i++) {
|
|
|
|
+ passwd.push(password.charCodeAt(i));
|
|
}
|
|
}
|
|
- subrects = 0;
|
|
|
|
- cur_tile = FBU.total_tiles - FBU.tiles;
|
|
|
|
- tile_x = cur_tile % FBU.tiles_x;
|
|
|
|
- tile_y = Math.floor(cur_tile / FBU.tiles_x);
|
|
|
|
- x = FBU.x + tile_x * 16;
|
|
|
|
- y = FBU.y + tile_y * 16;
|
|
|
|
- w = Math.min(16, (FBU.x + FBU.width) - x);
|
|
|
|
- h = Math.min(16, (FBU.y + FBU.height) - y);
|
|
|
|
-
|
|
|
|
- /* Figure out how much we are expecting */
|
|
|
|
- if (subencoding & 0x01) { // Raw
|
|
|
|
- //Util.Debug(" Raw subencoding");
|
|
|
|
- FBU.bytes += w * h * fb_Bpp;
|
|
|
|
- } else {
|
|
|
|
- if (subencoding & 0x02) { // Background
|
|
|
|
- FBU.bytes += fb_Bpp;
|
|
|
|
- }
|
|
|
|
- if (subencoding & 0x04) { // Foreground
|
|
|
|
- FBU.bytes += fb_Bpp;
|
|
|
|
- }
|
|
|
|
- if (subencoding & 0x08) { // AnySubrects
|
|
|
|
- FBU.bytes += 1; // Since we aren't shifting it off
|
|
|
|
- if (ws.rQwait("hextile subrects header", FBU.bytes)) { return false; }
|
|
|
|
- subrects = rQ[rQi + FBU.bytes-1]; // Peek
|
|
|
|
- if (subencoding & 0x10) { // SubrectsColoured
|
|
|
|
- FBU.bytes += subrects * (fb_Bpp + 2);
|
|
|
|
- } else {
|
|
|
|
- FBU.bytes += subrects * 2;
|
|
|
|
- }
|
|
|
|
|
|
+ return (new DES(passwd)).encrypt(challenge);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ RFB.extract_data_uri = function (arr) {
|
|
|
|
+ return ";base64," + Base64.encode(arr);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ RFB.encodingHandlers = {
|
|
|
|
+ RAW: function () {
|
|
|
|
+ if (this._FBU.lines === 0) {
|
|
|
|
+ this._FBU.lines = this._FBU.height;
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- /*
|
|
|
|
- Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
|
|
|
|
- " (" + tile_x + "," + tile_y + ")" +
|
|
|
|
- " [" + x + "," + y + "]@" + w + "x" + h +
|
|
|
|
- ", subenc:" + subencoding +
|
|
|
|
- "(last: " + FBU.lastsubencoding + "), subrects:" +
|
|
|
|
- subrects +
|
|
|
|
- ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes +
|
|
|
|
- " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) +
|
|
|
|
- " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10));
|
|
|
|
- */
|
|
|
|
- if (ws.rQwait("hextile", FBU.bytes)) { return false; }
|
|
|
|
-
|
|
|
|
- /* We know the encoding and have a whole tile */
|
|
|
|
- FBU.subencoding = rQ[rQi];
|
|
|
|
- rQi += 1;
|
|
|
|
- if (FBU.subencoding === 0) {
|
|
|
|
- if (FBU.lastsubencoding & 0x01) {
|
|
|
|
- /* Weird: ignore blanks after RAW */
|
|
|
|
- Util.Debug(" Ignoring blank after RAW");
|
|
|
|
|
|
+ this._FBU.bytes = this._FBU.width * this._fb_Bpp; // at least a line
|
|
|
|
+ if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
|
|
|
|
+ var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
|
|
|
|
+ var curr_height = Math.min(this._FBU.lines,
|
|
|
|
+ Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp)));
|
|
|
|
+ this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
|
|
|
|
+ curr_height, this._sock.get_rQ(),
|
|
|
|
+ this._sock.get_rQi());
|
|
|
|
+ this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp);
|
|
|
|
+ this._FBU.lines -= curr_height;
|
|
|
|
+
|
|
|
|
+ if (this._FBU.lines > 0) {
|
|
|
|
+ this._FBU.bytes = this._FBU.width * this._fb_Bpp; // At least another line
|
|
} else {
|
|
} else {
|
|
- display.fillRect(x, y, w, h, FBU.background);
|
|
|
|
|
|
+ this._FBU.rects--;
|
|
|
|
+ this._FBU.bytes = 0;
|
|
}
|
|
}
|
|
- } else if (FBU.subencoding & 0x01) { // Raw
|
|
|
|
- display.blitImage(x, y, w, h, rQ, rQi);
|
|
|
|
- rQi += FBU.bytes - 1;
|
|
|
|
- } else {
|
|
|
|
- if (FBU.subencoding & 0x02) { // Background
|
|
|
|
- FBU.background = rQ.slice(rQi, rQi + fb_Bpp);
|
|
|
|
- rQi += fb_Bpp;
|
|
|
|
- }
|
|
|
|
- if (FBU.subencoding & 0x04) { // Foreground
|
|
|
|
- FBU.foreground = rQ.slice(rQi, rQi + fb_Bpp);
|
|
|
|
- rQi += fb_Bpp;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- display.startTile(x, y, w, h, FBU.background);
|
|
|
|
- if (FBU.subencoding & 0x08) { // AnySubrects
|
|
|
|
- subrects = rQ[rQi];
|
|
|
|
- rQi += 1;
|
|
|
|
- for (s = 0; s < subrects; s += 1) {
|
|
|
|
- if (FBU.subencoding & 0x10) { // SubrectsColoured
|
|
|
|
- color = rQ.slice(rQi, rQi + fb_Bpp);
|
|
|
|
- rQi += fb_Bpp;
|
|
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ COPYRECT: function () {
|
|
|
|
+ this._FBU.bytes = 4;
|
|
|
|
+ if (this._sock.rQwait("COPYRECT", 4)) { return false; }
|
|
|
|
+ this._display.renderQ_push({
|
|
|
|
+ 'type': 'copy',
|
|
|
|
+ 'old_x': this._sock.rQshift16(),
|
|
|
|
+ 'old_y': this._sock.rQshift16(),
|
|
|
|
+ 'x': this._FBU.x,
|
|
|
|
+ 'y': this._FBU.y,
|
|
|
|
+ 'width': this._FBU.width,
|
|
|
|
+ 'height': this._FBU.height
|
|
|
|
+ });
|
|
|
|
+ this._FBU.rects--;
|
|
|
|
+ this._FBU.bytes = 0;
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ RRE: function () {
|
|
|
|
+ var color;
|
|
|
|
+ if (this._FBU.subrects === 0) {
|
|
|
|
+ this._FBU.bytes = 4 + this._fb_Bpp;
|
|
|
|
+ if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; }
|
|
|
|
+ this._FBU.subrects = this._sock.rQshift32();
|
|
|
|
+ color = this._sock.rQshiftBytes(this._fb_Bpp); // Background
|
|
|
|
+ this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) {
|
|
|
|
+ color = this._sock.rQshiftBytes(this._fb_Bpp);
|
|
|
|
+ var x = this._sock.rQshift16();
|
|
|
|
+ var y = this._sock.rQshift16();
|
|
|
|
+ var width = this._sock.rQshift16();
|
|
|
|
+ var height = this._sock.rQshift16();
|
|
|
|
+ this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
|
|
|
|
+ this._FBU.subrects--;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this._FBU.subrects > 0) {
|
|
|
|
+ var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
|
|
|
|
+ this._FBU.bytes = (this._fb_Bpp + 8) * chunk;
|
|
|
|
+ } else {
|
|
|
|
+ this._FBU.rects--;
|
|
|
|
+ this._FBU.bytes = 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ HEXTILE: function () {
|
|
|
|
+ var rQ = this._sock.get_rQ();
|
|
|
|
+ var rQi = this._sock.get_rQi();
|
|
|
|
+
|
|
|
|
+ if (this._FBU.tiles === 0) {
|
|
|
|
+ this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
|
|
|
|
+ this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
|
|
|
|
+ this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
|
|
|
|
+ this._FBU.tiles = this._FBU.total_tiles;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ while (this._FBU.tiles > 0) {
|
|
|
|
+ this._FBU.bytes = 1;
|
|
|
|
+ if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
|
|
|
|
+ var subencoding = rQ[rQi]; // Peek
|
|
|
|
+ if (subencoding > 30) { // Raw
|
|
|
|
+ this._fail("Disconnected: illegal hextile subencoding " + subencoding);
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var subrects = 0;
|
|
|
|
+ var curr_tile = this._FBU.total_tiles - this._FBU.tiles;
|
|
|
|
+ var tile_x = curr_tile % this._FBU.tiles_x;
|
|
|
|
+ var tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
|
|
|
|
+ var x = this._FBU.x + tile_x * 16;
|
|
|
|
+ var y = this._FBU.y + tile_y * 16;
|
|
|
|
+ var w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
|
|
|
|
+ var h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
|
|
|
|
+
|
|
|
|
+ // Figure out how much we are expecting
|
|
|
|
+ if (subencoding & 0x01) { // Raw
|
|
|
|
+ this._FBU.bytes += w * h * this._fb_Bpp;
|
|
|
|
+ } else {
|
|
|
|
+ if (subencoding & 0x02) { // Background
|
|
|
|
+ this._FBU.bytes += this._fb_Bpp;
|
|
|
|
+ }
|
|
|
|
+ if (subencoding & 0x04) { // Foreground
|
|
|
|
+ this._FBU.bytes += this._fb_Bpp;
|
|
|
|
+ }
|
|
|
|
+ if (subencoding & 0x08) { // AnySubrects
|
|
|
|
+ this._FBU.bytes++; // Since we aren't shifting it off
|
|
|
|
+ if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
|
|
|
|
+ subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek
|
|
|
|
+ if (subencoding & 0x10) { // SubrectsColoured
|
|
|
|
+ this._FBU.bytes += subrects * (this._fb_Bpp + 2);
|
|
|
|
+ } else {
|
|
|
|
+ this._FBU.bytes += subrects * 2;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
|
|
|
|
+
|
|
|
|
+ // We know the encoding and have a whole tile
|
|
|
|
+ this._FBU.subencoding = rQ[rQi];
|
|
|
|
+ rQi++;
|
|
|
|
+ if (this._FBU.subencoding === 0) {
|
|
|
|
+ if (this._FBU.lastsubencoding & 0x01) {
|
|
|
|
+ // Weird: ignore blanks are RAW
|
|
|
|
+ Util.Debug(" Ignoring blank after RAW");
|
|
} else {
|
|
} else {
|
|
- color = FBU.foreground;
|
|
|
|
|
|
+ this._display.fillRect(x, y, w, h, rQ, rQi);
|
|
|
|
+ rQi += this._FBU.bytes - 1;
|
|
|
|
+ }
|
|
|
|
+ } else if (this._FBU.subencoding & 0x01) { // Raw
|
|
|
|
+ this._display.blitImage(x, y, w, h, rQ, rQi);
|
|
|
|
+ rQi += this._FBU.bytes - 1;
|
|
|
|
+ } else {
|
|
|
|
+ if (this._FBU.subencoding & 0x02) { // Background
|
|
|
|
+ this._FBU.background = rQ.slice(rQi, rQi + this._fb_Bpp);
|
|
|
|
+ rQi += this._fb_Bpp;
|
|
|
|
+ }
|
|
|
|
+ if (this._FBU.subencoding & 0x04) { // Foreground
|
|
|
|
+ this._FBU.foreground = rQ.slice(rQi, rQi + this._fb_Bpp);
|
|
|
|
+ rQi += this._fb_Bpp;
|
|
}
|
|
}
|
|
- xy = rQ[rQi];
|
|
|
|
- rQi += 1;
|
|
|
|
- sx = (xy >> 4);
|
|
|
|
- sy = (xy & 0x0f);
|
|
|
|
|
|
|
|
- wh = rQ[rQi];
|
|
|
|
- rQi += 1;
|
|
|
|
- sw = (wh >> 4) + 1;
|
|
|
|
- sh = (wh & 0x0f) + 1;
|
|
|
|
|
|
+ this._display.startTile(x, y, w, h, this._FBU.background);
|
|
|
|
+ if (this._FBU.subencoding & 0x08) { // AnySubrects
|
|
|
|
+ subrects = rQ[rQi];
|
|
|
|
+ rQi++;
|
|
|
|
+
|
|
|
|
+ for (var s = 0; s < subrects; s++) {
|
|
|
|
+ var color;
|
|
|
|
+ if (this._FBU.subencoding & 0x10) { // SubrectsColoured
|
|
|
|
+ color = rQ.slice(rQi, rQi + this._fb_Bpp);
|
|
|
|
+ rQi += this._fb_Bpp;
|
|
|
|
+ } else {
|
|
|
|
+ color = this._FBU.foreground;
|
|
|
|
+ }
|
|
|
|
+ var xy = rQ[rQi];
|
|
|
|
+ rQi++;
|
|
|
|
+ var sx = (xy >> 4);
|
|
|
|
+ var sy = (xy & 0x0f);
|
|
|
|
+
|
|
|
|
+ var wh = rQ[rQi];
|
|
|
|
+ rQi++;
|
|
|
|
+ var sw = (wh >> 4) + 1;
|
|
|
|
+ var sh = (wh & 0x0f) + 1;
|
|
|
|
+
|
|
|
|
+ this._display.subTile(sx, sy, sw, sh, color);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ this._display.finishTile();
|
|
|
|
+ }
|
|
|
|
+ this._sock.set_rQi(rQi);
|
|
|
|
+ this._FBU.lastsubencoding = this._FBU.subencoding;
|
|
|
|
+ this._FBU.bytes = 0;
|
|
|
|
+ this._FBU.tiles--;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this._FBU.tiles === 0) {
|
|
|
|
+ this._FBU.rects--;
|
|
|
|
+ }
|
|
|
|
|
|
- display.subTile(sx, sy, sw, sh, color);
|
|
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ getTightCLength: function (arr) {
|
|
|
|
+ var header = 1, data = 0;
|
|
|
|
+ data += arr[0] & 0x7f;
|
|
|
|
+ if (arr[0] & 0x80) {
|
|
|
|
+ header++;
|
|
|
|
+ data += (arr[1] & 0x7f) << 7;
|
|
|
|
+ if (arr[1] & 0x80) {
|
|
|
|
+ header++;
|
|
|
|
+ data += arr[2] << 14;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- display.finishTile();
|
|
|
|
- }
|
|
|
|
- ws.set_rQi(rQi);
|
|
|
|
- FBU.lastsubencoding = FBU.subencoding;
|
|
|
|
- FBU.bytes = 0;
|
|
|
|
- FBU.tiles -= 1;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (FBU.tiles === 0) {
|
|
|
|
- FBU.rects -= 1;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- //Util.Debug("<< display_hextile");
|
|
|
|
- return true;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-// Get 'compact length' header and data size
|
|
|
|
-getTightCLength = function (arr) {
|
|
|
|
- var header = 1, data = 0;
|
|
|
|
- data += arr[0] & 0x7f;
|
|
|
|
- if (arr[0] & 0x80) {
|
|
|
|
- header += 1;
|
|
|
|
- data += (arr[1] & 0x7f) << 7;
|
|
|
|
- if (arr[1] & 0x80) {
|
|
|
|
- header += 1;
|
|
|
|
- data += arr[2] << 14;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return [header, data];
|
|
|
|
-};
|
|
|
|
|
|
+ return [header, data];
|
|
|
|
+ },
|
|
|
|
|
|
-function display_tight(isTightPNG) {
|
|
|
|
- //Util.Debug(">> display_tight");
|
|
|
|
|
|
+ display_tight: function (isTightPNG) {
|
|
|
|
+ if (this._fb_depth === 1) {
|
|
|
|
+ this._fail("Tight protocol handler only implements true color mode");
|
|
|
|
+ }
|
|
|
|
|
|
- if (fb_depth === 1) {
|
|
|
|
- fail("Tight protocol handler only implements true color mode");
|
|
|
|
- }
|
|
|
|
|
|
+ this._FBU.bytes = 1; // compression-control byte
|
|
|
|
+ if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
|
|
|
|
|
|
- var ctl, cmode, clength, color, img, data;
|
|
|
|
- var filterId = -1, resetStreams = 0, streamId = -1;
|
|
|
|
- var rQ = ws.get_rQ(), rQi = ws.get_rQi();
|
|
|
|
|
|
+ var checksum = function (data) {
|
|
|
|
+ var sum = 0;
|
|
|
|
+ for (var i = 0; i < data.length; i++) {
|
|
|
|
+ sum += data[i];
|
|
|
|
+ if (sum > 65536) sum -= 65536;
|
|
|
|
+ }
|
|
|
|
+ return sum;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ var resetStreams = 0;
|
|
|
|
+ var streamId = -1;
|
|
|
|
+ var decompress = function (data) {
|
|
|
|
+ for (var i = 0; i < 4; i++) {
|
|
|
|
+ if ((resetStreams >> i) & 1) {
|
|
|
|
+ this._FBU.zlibs[i].reset();
|
|
|
|
+ Util.Info("Reset zlib stream " + i);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- FBU.bytes = 1; // compression-control byte
|
|
|
|
- if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }
|
|
|
|
|
|
+ var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
|
|
|
|
+ if (uncompressed.status !== 0) {
|
|
|
|
+ Util.Error("Invalid data in zlib stream");
|
|
|
|
+ }
|
|
|
|
|
|
- var checksum = function(data) {
|
|
|
|
- var sum=0, i;
|
|
|
|
- for (i=0; i<data.length;i++) {
|
|
|
|
- sum += data[i];
|
|
|
|
- if (sum > 65536) sum -= 65536;
|
|
|
|
- }
|
|
|
|
- return sum;
|
|
|
|
- }
|
|
|
|
|
|
+ return uncompressed.data;
|
|
|
|
+ }.bind(this);
|
|
|
|
+
|
|
|
|
+ var indexedToRGB = function (data, numColors, palette, width, height) {
|
|
|
|
+ // Convert indexed (palette based) image data to RGB
|
|
|
|
+ // TODO: reduce number of calculations inside loop
|
|
|
|
+ var dest = [];
|
|
|
|
+ var x, y, dp, sp;
|
|
|
|
+ if (numColors === 2) {
|
|
|
|
+ var w = Math.floor((width + 7) / 8);
|
|
|
|
+ var w1 = Math.floor(width / 8);
|
|
|
|
+
|
|
|
|
+ for (y = 0; y < height; y++) {
|
|
|
|
+ var b;
|
|
|
|
+ for (x = 0; x < w1; x++) {
|
|
|
|
+ for (b = 7; b >= 0; b--) {
|
|
|
|
+ dp = (y * width + x * 8 + 7 - b) * 3;
|
|
|
|
+ sp = (data[y * w + x] >> b & 1) * 3;
|
|
|
|
+ dest[dp] = palette[sp];
|
|
|
|
+ dest[dp + 1] = palette[sp + 1];
|
|
|
|
+ dest[dp + 2] = palette[sp + 2];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- var decompress = function(data) {
|
|
|
|
- for (var i=0; i<4; i++) {
|
|
|
|
- if ((resetStreams >> i) & 1) {
|
|
|
|
- FBU.zlibs[i].reset();
|
|
|
|
- Util.Info("Reset zlib stream " + i);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- var uncompressed = FBU.zlibs[streamId].uncompress(data, 0);
|
|
|
|
- if (uncompressed.status !== 0) {
|
|
|
|
- Util.Error("Invalid data in zlib stream");
|
|
|
|
- }
|
|
|
|
- //Util.Warn("Decompressed " + data.length + " to " +
|
|
|
|
- // uncompressed.data.length + " checksums " +
|
|
|
|
- // checksum(data) + ":" + checksum(uncompressed.data));
|
|
|
|
-
|
|
|
|
- return uncompressed.data;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- var indexedToRGB = function (data, numColors, palette, width, height) {
|
|
|
|
- // Convert indexed (palette based) image data to RGB
|
|
|
|
- // TODO: reduce number of calculations inside loop
|
|
|
|
- var dest = [];
|
|
|
|
- var x, y, b, w, w1, dp, sp;
|
|
|
|
- if (numColors === 2) {
|
|
|
|
- w = Math.floor((width + 7) / 8);
|
|
|
|
- w1 = Math.floor(width / 8);
|
|
|
|
- for (y = 0; y < height; y++) {
|
|
|
|
- for (x = 0; x < w1; x++) {
|
|
|
|
- for (b = 7; b >= 0; b--) {
|
|
|
|
- dp = (y*width + x*8 + 7-b) * 3;
|
|
|
|
- sp = (data[y*w + x] >> b & 1) * 3;
|
|
|
|
- dest[dp ] = palette[sp ];
|
|
|
|
- dest[dp+1] = palette[sp+1];
|
|
|
|
- dest[dp+2] = palette[sp+2];
|
|
|
|
|
|
+ for (b = 7; b >= 8 - width % 8; b--) {
|
|
|
|
+ dp = (y * width + x * 8 + 7 - b) * 3;
|
|
|
|
+ sp = (data[y * w + x] >> b & 1) * 3;
|
|
|
|
+ dest[dp] = palette[sp];
|
|
|
|
+ dest[dp + 1] = palette[sp + 1];
|
|
|
|
+ dest[dp + 2] = palette[sp + 2];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ for (y = 0; y < height; y++) {
|
|
|
|
+ for (x = 0; x < width; x++) {
|
|
|
|
+ dp = (y * width + x) * 3;
|
|
|
|
+ sp = data[y * width + x] * 3;
|
|
|
|
+ dest[dp] = palette[sp];
|
|
|
|
+ dest[dp + 1] = palette[sp + 1];
|
|
|
|
+ dest[dp + 2] = palette[sp + 2];
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- for (b = 7; b >= 8 - width % 8; b--) {
|
|
|
|
- dp = (y*width + x*8 + 7-b) * 3;
|
|
|
|
- sp = (data[y*w + x] >> b & 1) * 3;
|
|
|
|
- dest[dp ] = palette[sp ];
|
|
|
|
- dest[dp+1] = palette[sp+1];
|
|
|
|
- dest[dp+2] = palette[sp+2];
|
|
|
|
|
|
+
|
|
|
|
+ return dest;
|
|
|
|
+ }.bind(this);
|
|
|
|
+
|
|
|
|
+ var rQ = this._sock.get_rQ();
|
|
|
|
+ var rQi = this._sock.get_rQi();
|
|
|
|
+ var cmode, clength, data;
|
|
|
|
+
|
|
|
|
+ var handlePalette = function () {
|
|
|
|
+ var numColors = rQ[rQi + 2] + 1;
|
|
|
|
+ var paletteSize = numColors * this._fb_depth;
|
|
|
|
+ this._FBU.bytes += paletteSize;
|
|
|
|
+ if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
|
|
|
|
+
|
|
|
|
+ var bpp = (numColors <= 2) ? 1 : 8;
|
|
|
|
+ var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
|
|
|
|
+ var raw = false;
|
|
|
|
+ if (rowSize * this._FBU.height < 12) {
|
|
|
|
+ raw = true;
|
|
|
|
+ clength = [0, rowSize * this._FBU.height];
|
|
|
|
+ } else {
|
|
|
|
+ clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(3 + paletteSize,
|
|
|
|
+ 3 + paletteSize + 3));
|
|
}
|
|
}
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- for (y = 0; y < height; y++) {
|
|
|
|
- for (x = 0; x < width; x++) {
|
|
|
|
- dp = (y*width + x) * 3;
|
|
|
|
- sp = data[y*width + x] * 3;
|
|
|
|
- dest[dp ] = palette[sp ];
|
|
|
|
- dest[dp+1] = palette[sp+1];
|
|
|
|
- dest[dp+2] = palette[sp+2];
|
|
|
|
|
|
+
|
|
|
|
+ this._FBU.bytes += clength[0] + clength[1];
|
|
|
|
+ if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
|
|
|
|
+
|
|
|
|
+ // Shift ctl, filter id, num colors, palette entries, and clength off
|
|
|
|
+ this._sock.rQskipBytes(3);
|
|
|
|
+ var palette = this._sock.rQshiftBytes(paletteSize);
|
|
|
|
+ this._sock.rQskipBytes(clength[0]);
|
|
|
|
+
|
|
|
|
+ if (raw) {
|
|
|
|
+ data = this._sock.rQshiftBytes(clength[1]);
|
|
|
|
+ } else {
|
|
|
|
+ data = decompress(this._sock.rQshiftBytes(clength[1]));
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // Convert indexed (palette based) image data to RGB
|
|
|
|
+ var rgb = indexedToRGB(data, numColors, palette, this._FBU.width, this._FBU.height);
|
|
|
|
+
|
|
|
|
+ this._display.renderQ_push({
|
|
|
|
+ 'type': 'blitRgb',
|
|
|
|
+ 'data': rgb,
|
|
|
|
+ 'x': this._FBU.x,
|
|
|
|
+ 'y': this._FBU.y,
|
|
|
|
+ 'width': this._FBU.width,
|
|
|
|
+ 'height': this._FBU.height
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ }.bind(this);
|
|
|
|
+
|
|
|
|
+ var handleCopy = function () {
|
|
|
|
+ var raw = false;
|
|
|
|
+ var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth;
|
|
|
|
+ if (uncompressedSize < 12) {
|
|
|
|
+ raw = true;
|
|
|
|
+ clength = [0, uncompressedSize];
|
|
|
|
+ } else {
|
|
|
|
+ clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
|
|
|
|
+ }
|
|
|
|
+ this._FBU.bytes = 1 + clength[0] + clength[1];
|
|
|
|
+ if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
|
|
|
|
+
|
|
|
|
+ // Shift ctl, clength off
|
|
|
|
+ this._sock.rQshiftBytes(1 + clength[0]);
|
|
|
|
+
|
|
|
|
+ if (raw) {
|
|
|
|
+ data = this._sock.rQshiftBytes(clength[1]);
|
|
|
|
+ } else {
|
|
|
|
+ data = decompress(this._sock.rQshiftBytes(clength[1]));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this._display.renderQ_push({
|
|
|
|
+ 'type': 'blitRgb',
|
|
|
|
+ 'data': data,
|
|
|
|
+ 'x': this._FBU.x,
|
|
|
|
+ 'y': this._FBU.y,
|
|
|
|
+ 'width': this._FBU.width,
|
|
|
|
+ 'height': this._FBU.height
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ }.bind(this);
|
|
|
|
+
|
|
|
|
+ var ctl = this._sock.rQpeek8();
|
|
|
|
+
|
|
|
|
+ // Keep tight reset bits
|
|
|
|
+ resetStreams = ctl & 0xF;
|
|
|
|
+
|
|
|
|
+ // Figure out filter
|
|
|
|
+ ctl = ctl >> 4;
|
|
|
|
+ streamId = ctl & 0x3;
|
|
|
|
+
|
|
|
|
+ if (ctl === 0x08) cmode = "fill";
|
|
|
|
+ else if (ctl === 0x09) cmode = "jpeg";
|
|
|
|
+ else if (ctl === 0x0A) cmode = "png";
|
|
|
|
+ else if (ctl & 0x04) cmode = "filter";
|
|
|
|
+ else if (ctl < 0x04) cmode = "copy";
|
|
|
|
+ else return this._fail("Illegal tight compression received, ctl: " + ctl);
|
|
|
|
+
|
|
|
|
+ if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
|
|
|
|
+ return this._fail("filter/copy received in tightPNG mode");
|
|
}
|
|
}
|
|
- }
|
|
|
|
- return dest;
|
|
|
|
- };
|
|
|
|
- var handlePalette = function() {
|
|
|
|
- var numColors = rQ[rQi + 2] + 1;
|
|
|
|
- var paletteSize = numColors * fb_depth;
|
|
|
|
- FBU.bytes += paletteSize;
|
|
|
|
- if (ws.rQwait("TIGHT palette " + cmode, FBU.bytes)) { return false; }
|
|
|
|
-
|
|
|
|
- var bpp = (numColors <= 2) ? 1 : 8;
|
|
|
|
- var rowSize = Math.floor((FBU.width * bpp + 7) / 8);
|
|
|
|
- var raw = false;
|
|
|
|
- if (rowSize * FBU.height < 12) {
|
|
|
|
- raw = true;
|
|
|
|
- clength = [0, rowSize * FBU.height];
|
|
|
|
- } else {
|
|
|
|
- clength = getTightCLength(ws.rQslice(3 + paletteSize,
|
|
|
|
- 3 + paletteSize + 3));
|
|
|
|
- }
|
|
|
|
- FBU.bytes += clength[0] + clength[1];
|
|
|
|
- if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
|
|
|
|
|
|
|
|
- // Shift ctl, filter id, num colors, palette entries, and clength off
|
|
|
|
- ws.rQshiftBytes(3);
|
|
|
|
- var palette = ws.rQshiftBytes(paletteSize);
|
|
|
|
- ws.rQshiftBytes(clength[0]);
|
|
|
|
|
|
+ switch (cmode) {
|
|
|
|
+ // fill use fb_depth because TPIXELs drop the padding byte
|
|
|
|
+ case "fill": // TPIXEL
|
|
|
|
+ this._FBU.bytes += this._fb_depth;
|
|
|
|
+ break;
|
|
|
|
+ case "jpeg": // max clength
|
|
|
|
+ this._FBU.bytes += 3;
|
|
|
|
+ break;
|
|
|
|
+ case "png": // max clength
|
|
|
|
+ this._FBU.bytes += 3;
|
|
|
|
+ break;
|
|
|
|
+ case "filter": // filter id + num colors if palette
|
|
|
|
+ this._FBU.bytes += 2;
|
|
|
|
+ break;
|
|
|
|
+ case "copy":
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
|
|
- if (raw) {
|
|
|
|
- data = ws.rQshiftBytes(clength[1]);
|
|
|
|
- } else {
|
|
|
|
- data = decompress(ws.rQshiftBytes(clength[1]));
|
|
|
|
- }
|
|
|
|
|
|
+ if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
|
|
|
|
+
|
|
|
|
+ // Determine FBU.bytes
|
|
|
|
+ switch (cmode) {
|
|
|
|
+ case "fill":
|
|
|
|
+ this._sock.rQskip8(); // shift off ctl
|
|
|
|
+ var color = this._sock.rQshiftBytes(this._fb_depth);
|
|
|
|
+ this._display.renderQ_push({
|
|
|
|
+ 'type': 'fill',
|
|
|
|
+ 'x': this._FBU.x,
|
|
|
|
+ 'y': this._FBU.y,
|
|
|
|
+ 'width': this._FBU.width,
|
|
|
|
+ 'height': this._FBU.height,
|
|
|
|
+ 'color': [color[2], color[1], color[0]]
|
|
|
|
+ });
|
|
|
|
+ break;
|
|
|
|
+ case "png":
|
|
|
|
+ case "jpeg":
|
|
|
|
+ clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
|
|
|
|
+ this._FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
|
|
|
|
+ if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
|
|
|
|
+
|
|
|
|
+ // We have everything, render it
|
|
|
|
+ this._sock.rQskipBytes(1 + clength[0]); // shift off clt + compact length
|
|
|
|
+ var img = new Image();
|
|
|
|
+ img.src = "data: image/" + cmode +
|
|
|
|
+ RFB.extract_data_uri(this._sock.rQshiftBytes(clength[1]));
|
|
|
|
+ this._display.renderQ_push({
|
|
|
|
+ 'type': 'img',
|
|
|
|
+ 'img': img,
|
|
|
|
+ 'x': this._FBU.x,
|
|
|
|
+ 'y': this._FBU.y
|
|
|
|
+ });
|
|
|
|
+ img = null;
|
|
|
|
+ break;
|
|
|
|
+ case "filter":
|
|
|
|
+ var filterId = rQ[rQi + 1];
|
|
|
|
+ if (filterId === 1) {
|
|
|
|
+ if (!handlePalette()) { return false; }
|
|
|
|
+ } else {
|
|
|
|
+ // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
|
|
|
|
+ // Filter 2, Gradient is valid but not use if jpeg is enabled
|
|
|
|
+ // TODO(directxman12): why aren't we just calling '_fail' here
|
|
|
|
+ throw new Error("Unsupported tight subencoding received, filter: " + filterId);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case "copy":
|
|
|
|
+ if (!handleCopy()) { return false; }
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
|
|
- // Convert indexed (palette based) image data to RGB
|
|
|
|
- var rgb = indexedToRGB(data, numColors, palette, FBU.width, FBU.height);
|
|
|
|
-
|
|
|
|
- // Add it to the render queue
|
|
|
|
- display.renderQ_push({
|
|
|
|
- 'type': 'blitRgb',
|
|
|
|
- 'data': rgb,
|
|
|
|
- 'x': FBU.x,
|
|
|
|
- 'y': FBU.y,
|
|
|
|
- 'width': FBU.width,
|
|
|
|
- 'height': FBU.height});
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- var handleCopy = function() {
|
|
|
|
- var raw = false;
|
|
|
|
- var uncompressedSize = FBU.width * FBU.height * fb_depth;
|
|
|
|
- if (uncompressedSize < 12) {
|
|
|
|
- raw = true;
|
|
|
|
- clength = [0, uncompressedSize];
|
|
|
|
- } else {
|
|
|
|
- clength = getTightCLength(ws.rQslice(1, 4));
|
|
|
|
- }
|
|
|
|
- FBU.bytes = 1 + clength[0] + clength[1];
|
|
|
|
- if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
|
|
|
|
|
|
|
|
- // Shift ctl, clength off
|
|
|
|
- ws.rQshiftBytes(1 + clength[0]);
|
|
|
|
|
|
+ this._FBU.bytes = 0;
|
|
|
|
+ this._FBU.rects--;
|
|
|
|
|
|
- if (raw) {
|
|
|
|
- data = ws.rQshiftBytes(clength[1]);
|
|
|
|
- } else {
|
|
|
|
- data = decompress(ws.rQshiftBytes(clength[1]));
|
|
|
|
- }
|
|
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
|
|
- display.renderQ_push({
|
|
|
|
- 'type': 'blitRgb',
|
|
|
|
- 'data': data,
|
|
|
|
- 'x': FBU.x,
|
|
|
|
- 'y': FBU.y,
|
|
|
|
- 'width': FBU.width,
|
|
|
|
- 'height': FBU.height});
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- ctl = ws.rQpeek8();
|
|
|
|
-
|
|
|
|
- // Keep tight reset bits
|
|
|
|
- resetStreams = ctl & 0xF;
|
|
|
|
-
|
|
|
|
- // Figure out filter
|
|
|
|
- ctl = ctl >> 4;
|
|
|
|
- streamId = ctl & 0x3;
|
|
|
|
-
|
|
|
|
- if (ctl === 0x08) cmode = "fill";
|
|
|
|
- else if (ctl === 0x09) cmode = "jpeg";
|
|
|
|
- else if (ctl === 0x0A) cmode = "png";
|
|
|
|
- else if (ctl & 0x04) cmode = "filter";
|
|
|
|
- else if (ctl < 0x04) cmode = "copy";
|
|
|
|
- else return fail("Illegal tight compression received, ctl: " + ctl);
|
|
|
|
-
|
|
|
|
- if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
|
|
|
|
- return fail("filter/copy received in tightPNG mode");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- switch (cmode) {
|
|
|
|
- // fill uses fb_depth because TPIXELs drop the padding byte
|
|
|
|
- case "fill": FBU.bytes += fb_depth; break; // TPIXEL
|
|
|
|
- case "jpeg": FBU.bytes += 3; break; // max clength
|
|
|
|
- case "png": FBU.bytes += 3; break; // max clength
|
|
|
|
- case "filter": FBU.bytes += 2; break; // filter id + num colors if palette
|
|
|
|
- case "copy": break;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
|
|
|
|
-
|
|
|
|
- //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
|
|
|
|
- //Util.Debug(" cmode: " + cmode);
|
|
|
|
-
|
|
|
|
- // Determine FBU.bytes
|
|
|
|
- switch (cmode) {
|
|
|
|
- case "fill":
|
|
|
|
- ws.rQshift8(); // shift off ctl
|
|
|
|
- color = ws.rQshiftBytes(fb_depth);
|
|
|
|
- display.renderQ_push({
|
|
|
|
- 'type': 'fill',
|
|
|
|
- 'x': FBU.x,
|
|
|
|
- 'y': FBU.y,
|
|
|
|
- 'width': FBU.width,
|
|
|
|
- 'height': FBU.height,
|
|
|
|
- 'color': [color[2], color[1], color[0]] });
|
|
|
|
- break;
|
|
|
|
- case "png":
|
|
|
|
- case "jpeg":
|
|
|
|
- clength = getTightCLength(ws.rQslice(1, 4));
|
|
|
|
- FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
|
|
|
|
- if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
|
|
|
|
-
|
|
|
|
- // We have everything, render it
|
|
|
|
- //Util.Debug(" jpeg, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " +
|
|
|
|
- // clength[0] + ", clength[1]: " + clength[1]);
|
|
|
|
- ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length
|
|
|
|
- img = new Image();
|
|
|
|
- img.src = "data:image/" + cmode +
|
|
|
|
- extract_data_uri(ws.rQshiftBytes(clength[1]));
|
|
|
|
- display.renderQ_push({
|
|
|
|
- 'type': 'img',
|
|
|
|
- 'img': img,
|
|
|
|
- 'x': FBU.x,
|
|
|
|
- 'y': FBU.y});
|
|
|
|
- img = null;
|
|
|
|
- break;
|
|
|
|
- case "filter":
|
|
|
|
- filterId = rQ[rQi + 1];
|
|
|
|
- if (filterId === 1) {
|
|
|
|
- if (!handlePalette()) { return false; }
|
|
|
|
- } else {
|
|
|
|
- // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
|
|
|
|
- // Filter 2, Gradient is valid but not used if jpeg is enabled
|
|
|
|
- throw("Unsupported tight subencoding received, filter: " + filterId);
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- case "copy":
|
|
|
|
- if (!handleCopy()) { return false; }
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- FBU.bytes = 0;
|
|
|
|
- FBU.rects -= 1;
|
|
|
|
- //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
|
|
|
|
- //Util.Debug("<< display_tight_png");
|
|
|
|
- return true;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-extract_data_uri = function(arr) {
|
|
|
|
- //var i, stra = [];
|
|
|
|
- //for (i=0; i< arr.length; i += 1) {
|
|
|
|
- // stra.push(String.fromCharCode(arr[i]));
|
|
|
|
- //}
|
|
|
|
- //return "," + escape(stra.join(''));
|
|
|
|
- return ";base64," + Base64.encode(arr);
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-encHandlers.TIGHT = function () { return display_tight(false); };
|
|
|
|
-encHandlers.TIGHT_PNG = function () { return display_tight(true); };
|
|
|
|
-
|
|
|
|
-encHandlers.last_rect = function last_rect() {
|
|
|
|
- //Util.Debug(">> last_rect");
|
|
|
|
- FBU.rects = 0;
|
|
|
|
- //Util.Debug("<< last_rect");
|
|
|
|
- return true;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-encHandlers.DesktopSize = function set_desktopsize() {
|
|
|
|
- Util.Debug(">> set_desktopsize");
|
|
|
|
- fb_width = FBU.width;
|
|
|
|
- fb_height = FBU.height;
|
|
|
|
- conf.onFBResize(that, fb_width, fb_height);
|
|
|
|
- display.resize(fb_width, fb_height);
|
|
|
|
- timing.fbu_rt_start = (new Date()).getTime();
|
|
|
|
-
|
|
|
|
- FBU.bytes = 0;
|
|
|
|
- FBU.rects -= 1;
|
|
|
|
-
|
|
|
|
- Util.Debug("<< set_desktopsize");
|
|
|
|
- return true;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-encHandlers.Cursor = function set_cursor() {
|
|
|
|
- var x, y, w, h, pixelslength, masklength;
|
|
|
|
- Util.Debug(">> set_cursor");
|
|
|
|
- x = FBU.x; // hotspot-x
|
|
|
|
- y = FBU.y; // hotspot-y
|
|
|
|
- w = FBU.width;
|
|
|
|
- h = FBU.height;
|
|
|
|
-
|
|
|
|
- pixelslength = w * h * fb_Bpp;
|
|
|
|
- masklength = Math.floor((w + 7) / 8) * h;
|
|
|
|
-
|
|
|
|
- FBU.bytes = pixelslength + masklength;
|
|
|
|
- if (ws.rQwait("cursor encoding", FBU.bytes)) { return false; }
|
|
|
|
-
|
|
|
|
- //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
|
|
|
|
-
|
|
|
|
- display.changeCursor(ws.rQshiftBytes(pixelslength),
|
|
|
|
- ws.rQshiftBytes(masklength),
|
|
|
|
- x, y, w, h);
|
|
|
|
-
|
|
|
|
- FBU.bytes = 0;
|
|
|
|
- FBU.rects -= 1;
|
|
|
|
-
|
|
|
|
- Util.Debug("<< set_cursor");
|
|
|
|
- return true;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-encHandlers.JPEG_quality_lo = function set_jpeg_quality() {
|
|
|
|
- Util.Error("Server sent jpeg_quality pseudo-encoding");
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-encHandlers.compress_lo = function set_compress_level() {
|
|
|
|
- Util.Error("Server sent compress level pseudo-encoding");
|
|
|
|
-};
|
|
|
|
|
|
+ TIGHT: function () { return this._encHandlers.display_tight(false); },
|
|
|
|
+ TIGHT_PNG: function () { return this._encHandlers.display_tight(true); },
|
|
|
|
|
|
-/*
|
|
|
|
- * Client message routines
|
|
|
|
- */
|
|
|
|
|
|
+ last_rect: function () {
|
|
|
|
+ this._FBU.rects = 0;
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
|
|
-pixelFormat = function() {
|
|
|
|
- //Util.Debug(">> pixelFormat");
|
|
|
|
- var arr;
|
|
|
|
- arr = [0]; // msg-type
|
|
|
|
- arr.push8(0); // padding
|
|
|
|
- arr.push8(0); // padding
|
|
|
|
- arr.push8(0); // padding
|
|
|
|
-
|
|
|
|
- arr.push8(fb_Bpp * 8); // bits-per-pixel
|
|
|
|
- arr.push8(fb_depth * 8); // depth
|
|
|
|
- arr.push8(0); // little-endian
|
|
|
|
- arr.push8(conf.true_color ? 1 : 0); // true-color
|
|
|
|
-
|
|
|
|
- arr.push16(255); // red-max
|
|
|
|
- arr.push16(255); // green-max
|
|
|
|
- arr.push16(255); // blue-max
|
|
|
|
- arr.push8(16); // red-shift
|
|
|
|
- arr.push8(8); // green-shift
|
|
|
|
- arr.push8(0); // blue-shift
|
|
|
|
-
|
|
|
|
- arr.push8(0); // padding
|
|
|
|
- arr.push8(0); // padding
|
|
|
|
- arr.push8(0); // padding
|
|
|
|
- //Util.Debug("<< pixelFormat");
|
|
|
|
- return arr;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-clientEncodings = function() {
|
|
|
|
- //Util.Debug(">> clientEncodings");
|
|
|
|
- var arr, i, encList = [];
|
|
|
|
-
|
|
|
|
- for (i=0; i<encodings.length; i += 1) {
|
|
|
|
- if ((encodings[i][0] === "Cursor") &&
|
|
|
|
- (! conf.local_cursor)) {
|
|
|
|
- Util.Debug("Skipping Cursor pseudo-encoding");
|
|
|
|
-
|
|
|
|
- // TODO: remove this when we have tight+non-true-color
|
|
|
|
- } else if ((encodings[i][0] === "TIGHT") &&
|
|
|
|
- (! conf.true_color)) {
|
|
|
|
- Util.Warn("Skipping tight, only support with true color");
|
|
|
|
- } else {
|
|
|
|
- //Util.Debug("Adding encoding: " + encodings[i][0]);
|
|
|
|
- encList.push(encodings[i][1]);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- arr = [2]; // msg-type
|
|
|
|
- arr.push8(0); // padding
|
|
|
|
-
|
|
|
|
- arr.push16(encList.length); // encoding count
|
|
|
|
- for (i=0; i < encList.length; i += 1) {
|
|
|
|
- arr.push32(encList[i]);
|
|
|
|
- }
|
|
|
|
- //Util.Debug("<< clientEncodings: " + arr);
|
|
|
|
- return arr;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-fbUpdateRequest = function(incremental, x, y, xw, yw) {
|
|
|
|
- //Util.Debug(">> fbUpdateRequest");
|
|
|
|
- if (typeof(x) === "undefined") { x = 0; }
|
|
|
|
- if (typeof(y) === "undefined") { y = 0; }
|
|
|
|
- if (typeof(xw) === "undefined") { xw = fb_width; }
|
|
|
|
- if (typeof(yw) === "undefined") { yw = fb_height; }
|
|
|
|
- var arr;
|
|
|
|
- arr = [3]; // msg-type
|
|
|
|
- arr.push8(incremental);
|
|
|
|
- arr.push16(x);
|
|
|
|
- arr.push16(y);
|
|
|
|
- arr.push16(xw);
|
|
|
|
- arr.push16(yw);
|
|
|
|
- //Util.Debug("<< fbUpdateRequest");
|
|
|
|
- return arr;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-// Based on clean/dirty areas, generate requests to send
|
|
|
|
-fbUpdateRequests = function() {
|
|
|
|
- var cleanDirty = display.getCleanDirtyReset(),
|
|
|
|
- arr = [], i, cb, db;
|
|
|
|
-
|
|
|
|
- cb = cleanDirty.cleanBox;
|
|
|
|
- if (cb.w > 0 && cb.h > 0) {
|
|
|
|
- // Request incremental for clean box
|
|
|
|
- arr = arr.concat(fbUpdateRequest(1, cb.x, cb.y, cb.w, cb.h));
|
|
|
|
- }
|
|
|
|
- for (i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
|
|
|
|
- db = cleanDirty.dirtyBoxes[i];
|
|
|
|
- // Force all (non-incremental for dirty box
|
|
|
|
- arr = arr.concat(fbUpdateRequest(0, db.x, db.y, db.w, db.h));
|
|
|
|
- }
|
|
|
|
- return arr;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-keyEvent = function(keysym, down) {
|
|
|
|
- //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
|
|
|
|
- var arr;
|
|
|
|
- arr = [4]; // msg-type
|
|
|
|
- arr.push8(down);
|
|
|
|
- arr.push16(0);
|
|
|
|
- arr.push32(keysym);
|
|
|
|
- //Util.Debug("<< keyEvent");
|
|
|
|
- return arr;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-pointerEvent = function(x, y) {
|
|
|
|
- //Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
|
|
|
|
- // " , mask: " + mouse_buttonMask);
|
|
|
|
- var arr;
|
|
|
|
- arr = [5]; // msg-type
|
|
|
|
- arr.push8(mouse_buttonMask);
|
|
|
|
- arr.push16(x);
|
|
|
|
- arr.push16(y);
|
|
|
|
- //Util.Debug("<< pointerEvent");
|
|
|
|
- return arr;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-clientCutText = function(text) {
|
|
|
|
- //Util.Debug(">> clientCutText");
|
|
|
|
- var arr, i, n;
|
|
|
|
- arr = [6]; // msg-type
|
|
|
|
- arr.push8(0); // padding
|
|
|
|
- arr.push8(0); // padding
|
|
|
|
- arr.push8(0); // padding
|
|
|
|
- arr.push32(text.length);
|
|
|
|
- n = text.length;
|
|
|
|
- for (i=0; i < n; i+=1) {
|
|
|
|
- arr.push(text.charCodeAt(i));
|
|
|
|
- }
|
|
|
|
- //Util.Debug("<< clientCutText:" + arr);
|
|
|
|
- return arr;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-//
|
|
|
|
-// Public API interface functions
|
|
|
|
-//
|
|
|
|
-
|
|
|
|
-that.connect = function(host, port, password, path) {
|
|
|
|
- //Util.Debug(">> connect");
|
|
|
|
-
|
|
|
|
- rfb_host = host;
|
|
|
|
- rfb_port = port;
|
|
|
|
- rfb_password = (password !== undefined) ? password : "";
|
|
|
|
- rfb_path = (path !== undefined) ? path : "";
|
|
|
|
-
|
|
|
|
- if ((!rfb_host) || (!rfb_port)) {
|
|
|
|
- return fail("Must set host and port");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- updateState('connect');
|
|
|
|
- //Util.Debug("<< connect");
|
|
|
|
-
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-that.disconnect = function() {
|
|
|
|
- //Util.Debug(">> disconnect");
|
|
|
|
- updateState('disconnect', 'Disconnecting');
|
|
|
|
- //Util.Debug("<< disconnect");
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-that.sendPassword = function(passwd) {
|
|
|
|
- rfb_password = passwd;
|
|
|
|
- rfb_state = "Authentication";
|
|
|
|
- setTimeout(init_msg, 1);
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-that.sendCtrlAltDel = function() {
|
|
|
|
- if (rfb_state !== "normal" || conf.view_only) { return false; }
|
|
|
|
- Util.Info("Sending Ctrl-Alt-Del");
|
|
|
|
- var arr = [];
|
|
|
|
- arr = arr.concat(keyEvent(0xFFE3, 1)); // Control
|
|
|
|
- arr = arr.concat(keyEvent(0xFFE9, 1)); // Alt
|
|
|
|
- arr = arr.concat(keyEvent(0xFFFF, 1)); // Delete
|
|
|
|
- arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete
|
|
|
|
- arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt
|
|
|
|
- arr = arr.concat(keyEvent(0xFFE3, 0)); // Control
|
|
|
|
- ws.send(arr);
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-that.xvpOp = function(ver, op) {
|
|
|
|
- if (rfb_xvp_ver < ver) { return false; }
|
|
|
|
- Util.Info("Sending XVP operation " + op + " (version " + ver + ")")
|
|
|
|
- ws.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op));
|
|
|
|
- return true;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-that.xvpShutdown = function() {
|
|
|
|
- return that.xvpOp(1, 2);
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-that.xvpReboot = function() {
|
|
|
|
- return that.xvpOp(1, 3);
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-that.xvpReset = function() {
|
|
|
|
- return that.xvpOp(1, 4);
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-// Send a key press. If 'down' is not specified then send a down key
|
|
|
|
-// followed by an up key.
|
|
|
|
-that.sendKey = function(code, down) {
|
|
|
|
- if (rfb_state !== "normal" || conf.view_only) { return false; }
|
|
|
|
- var arr = [];
|
|
|
|
- if (typeof down !== 'undefined') {
|
|
|
|
- Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
|
|
|
|
- arr = arr.concat(keyEvent(code, down ? 1 : 0));
|
|
|
|
- } else {
|
|
|
|
- Util.Info("Sending key code (down + up): " + code);
|
|
|
|
- arr = arr.concat(keyEvent(code, 1));
|
|
|
|
- arr = arr.concat(keyEvent(code, 0));
|
|
|
|
- }
|
|
|
|
- ws.send(arr);
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-that.clipboardPasteFrom = function(text) {
|
|
|
|
- if (rfb_state !== "normal") { return; }
|
|
|
|
- //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
|
|
|
|
- ws.send(clientCutText(text));
|
|
|
|
- //Util.Debug("<< clipboardPasteFrom");
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-// Override internal functions for testing
|
|
|
|
-that.testMode = function(override_send, data_mode) {
|
|
|
|
- test_mode = true;
|
|
|
|
- that.recv_message = ws.testMode(override_send, data_mode);
|
|
|
|
-
|
|
|
|
- checkEvents = function () { /* Stub Out */ };
|
|
|
|
- that.connect = function(host, port, password) {
|
|
|
|
- rfb_host = host;
|
|
|
|
- rfb_port = port;
|
|
|
|
- rfb_password = password;
|
|
|
|
- init_vars();
|
|
|
|
- updateState('ProtocolVersion', "Starting VNC handshake");
|
|
|
|
- };
|
|
|
|
-};
|
|
|
|
|
|
+ DesktopSize: function () {
|
|
|
|
+ Util.Debug(">> set_desktopsize");
|
|
|
|
+ this._fb_width = this._FBU.width;
|
|
|
|
+ this._fb_height = this._FBU.height;
|
|
|
|
+ this._onFBResize(this, this._fb_width, this._fb_height);
|
|
|
|
+ this._display.resize(this._fb_width, this._fb_height);
|
|
|
|
+ this._timing.fbu_rt_start = (new Date()).getTime();
|
|
|
|
+
|
|
|
|
+ this._FBU.bytes = 0;
|
|
|
|
+ this._FBU.rects--;
|
|
|
|
+
|
|
|
|
+ Util.Debug("<< set_desktopsize");
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ Cursor: function () {
|
|
|
|
+ Util.Debug(">> set_cursor");
|
|
|
|
+ var x = this._FBU.x; // hotspot-x
|
|
|
|
+ var y = this._FBU.y; // hotspot-y
|
|
|
|
+ var w = this._FBU.width;
|
|
|
|
+ var h = this._FBU.height;
|
|
|
|
+
|
|
|
|
+ var pixelslength = w * h * this._fb_Bpp;
|
|
|
|
+ var masklength = Math.floor((w + 7) / 8) * h;
|
|
|
|
|
|
|
|
+ this._FBU.bytes = pixelslength + masklength;
|
|
|
|
+ if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
|
|
|
|
|
|
-return constructor(); // Return the public API interface
|
|
|
|
|
|
+ this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
|
|
|
|
+ this._sock.rQshiftBytes(masklength),
|
|
|
|
+ x, y, w, h);
|
|
|
|
|
|
-} // End of RFB()
|
|
|
|
|
|
+ this._FBU.bytes = 0;
|
|
|
|
+ this._FBU.rects--;
|
|
|
|
+
|
|
|
|
+ Util.Debug("<< set_cursor");
|
|
|
|
+ return true;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ JPEG_quality_lo: function () {
|
|
|
|
+ Util.Error("Server sent jpeg_quality pseudo-encoding");
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ compress_lo: function () {
|
|
|
|
+ Util.Error("Server sent compress level pseudo-encoding");
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+})();
|