import { decodeUTF8 } from './util/strings.js';
import { set_defaults, make_properties } from './util/properties.js';
import Display from "./display.js";
-import { Keyboard, Mouse } from "./input/devices.js";
+import Keyboard from "./input/keyboard.js";
+import Mouse from "./input/mouse.js";
import Websock from "./websock.js";
import Base64 from "./base64.js";
import DES from "./des.js";
import KeyTable from "./input/keysym.js";
import XtScancode from "./input/xtscancodes.js";
import Inflator from "./inflator.js";
+import { encodings, encodingName } from "./encodings.js";
/*jslint white: false, browser: true */
/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */
defaults = {};
}
- this._rfb_host = '';
- this._rfb_port = 5900;
- this._rfb_password = '';
- this._rfb_path = '';
+ // Connection details
+ this._url = '';
+ this._rfb_credentials = {};
+ // Internal state
this._rfb_connection_state = '';
this._rfb_init_state = '';
- this._rfb_version = 0;
- this._rfb_max_version = 3.8;
this._rfb_auth_scheme = '';
this._rfb_disconnect_reason = "";
+ // Server capabilities
+ this._rfb_version = 0;
+ this._rfb_max_version = 3.8;
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 ],
+ this._fb_width = 0;
+ this._fb_height = 0;
- // Psuedo-encoding settings
+ this._fb_name = "";
- //['JPEG_quality_lo', -32 ],
- ['JPEG_quality_med', -26 ],
- //['JPEG_quality_hi', -23 ],
- //['compress_lo', -255 ],
- ['compress_hi', -254 ],
- //['compress_max', -247 ],
-
- ['DesktopSize', -223 ],
- ['last_rect', -224 ],
- ['Cursor', -239 ],
- ['QEMUExtendedKeyEvent', -258 ],
- ['ExtendedDesktopSize', -308 ],
- ['xvp', -309 ],
- ['Fence', -312 ],
- ['ContinuousUpdates', -313 ]
- ];
+ this._capabilities = { power: false, resize: false };
- this._encHandlers = {};
- this._encNames = {};
- this._encStats = {};
+ this._supportsFence = false;
+ this._supportsContinuousUpdates = false;
+ this._enabledContinuousUpdates = false;
+
+ this._supportsSetDesktopSize = false;
+ this._screen_id = 0;
+ this._screen_flags = 0;
+
+ this._qemuExtKeyEventSupported = false;
+
+ // Internal objects
this._sock = null; // Websock object
this._display = null; // Display object
this._flushing = false; // Display flushing state
this._keyboard = null; // Keyboard input handler object
this._mouse = null; // Mouse input handler object
- this._disconnTimer = null; // disconnection timer
- this._supportsFence = false;
+ // Timers
+ this._disconnTimer = null; // disconnection timer
- this._supportsContinuousUpdates = false;
- this._enabledContinuousUpdates = false;
+ // Decoder states and stats
+ this._encHandlers = {};
+ this._encStats = {};
- // Frame buffer update state
this._FBU = {
rects: 0,
- subrects: 0, // RRE
+ subrects: 0, // RRE and HEXTILE
lines: 0, // RAW
tiles: 0, // HEXTILE
bytes: 0,
encoding: 0,
subencoding: -1,
background: null,
- zlib: [] // TIGHT zlib streams
+ zlibs: [] // TIGHT zlib streams
};
-
- this._fb_width = 0;
- this._fb_height = 0;
- this._fb_name = "";
+ for (var i = 0; i < 4; i++) {
+ this._FBU.zlibs[i] = new Inflator();
+ }
this._destBuff = null;
this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
pixels: 0
};
- this._supportsSetDesktopSize = false;
- this._screen_id = 0;
- this._screen_flags = 0;
-
// Mouse state
this._mouse_buttonMask = 0;
this._mouse_arr = [];
this._viewportDragPos = {};
this._viewportHasMoved = false;
- // QEMU Extended Key Event support - default to false
- this._qemuExtKeyEventSupported = false;
-
// set the default value on user-facing properties
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
'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'], // Protocols to use in the WebSocket connection
'repeaterID': '', // [UltraVNC] RepeaterID to connect to
'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate): connection state change
'onNotification': function () { }, // onNotification(rfb, msg, level, options): notification for UI
'onDisconnected': function () { }, // onDisconnected(rfb, reason): disconnection finished
- 'onPasswordRequired': function () { }, // onPasswordRequired(rfb, msg): VNC password is required
+ 'onCredentialsRequired': function () { }, // onCredentialsRequired(rfb, types): VNC credentials are 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
+ 'onCapabilities': function () { } // onCapabilities(rfb, caps): the supported capabilities has changed
});
// main setup
Log.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];
+ // Target canvas must be able to have focus
+ if (!this._target.hasAttribute('tabindex')) {
+ this._target.tabIndex = -1;
}
+ // populate encHandlers with bound versions
+ this._encHandlers[encodings.encodingRaw] = RFB.encodingHandlers.RAW.bind(this);
+ this._encHandlers[encodings.encodingCopyRect] = RFB.encodingHandlers.COPYRECT.bind(this);
+ this._encHandlers[encodings.encodingRRE] = RFB.encodingHandlers.RRE.bind(this);
+ this._encHandlers[encodings.encodingHextile] = RFB.encodingHandlers.HEXTILE.bind(this);
+ this._encHandlers[encodings.encodingTight] = RFB.encodingHandlers.TIGHT.bind(this);
+
+ this._encHandlers[encodings.pseudoEncodingDesktopSize] = RFB.encodingHandlers.DesktopSize.bind(this);
+ this._encHandlers[encodings.pseudoEncodingLastRect] = RFB.encodingHandlers.last_rect.bind(this);
+ this._encHandlers[encodings.pseudoEncodingCursor] = RFB.encodingHandlers.Cursor.bind(this);
+ this._encHandlers[encodings.pseudoEncodingQEMUExtendedKeyEvent] = RFB.encodingHandlers.QEMUExtendedKeyEvent.bind(this);
+ this._encHandlers[encodings.pseudoEncodingExtendedDesktopSize] = RFB.encodingHandlers.ExtendedDesktopSize.bind(this);
+
// NB: nothing that needs explicit teardown should be done
// before this point, since this can throw an exception
try {
Log.Error("Display exception: " + exc);
throw exc;
}
+ this._display.clear();
- this._keyboard = new Keyboard({target: this._focusContainer,
+ this._keyboard = new Keyboard({target: this._target,
onKeyEvent: this._handleKeyEvent.bind(this)});
this._mouse = new Mouse({target: this._target,
Log.Warn("WebSocket on-error event");
});
- this._init_vars();
- this._cleanup();
-
var rmode = this._display.get_render_mode();
Log.Info("Using native WebSockets, render mode: " + rmode);
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 : "";
+ connect: function (url, creds) {
+ this._url = url;
+ this._rfb_credentials = (creds !== undefined) ? creds : {};
- if (!this._rfb_host || !this._rfb_port) {
- return this._fail(
- _("Must set host and port"));
+ if (!url) {
+ this._fail(_("Must specify URL"));
+ return;
}
this._rfb_init_state = '';
this._sock.off('open');
},
- sendPassword: function (passwd) {
- this._rfb_password = passwd;
+ sendCredentials: function (creds) {
+ this._rfb_credentials = creds;
setTimeout(this._init_msg.bind(this), 0);
},
return true;
},
- xvpOp: function (ver, op) {
- if (this._rfb_xvp_ver < ver) { return false; }
- Log.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);
+ machineShutdown: function () {
+ this._xvpOp(1, 2);
},
- xvpReboot: function () {
- return this.xvpOp(1, 3);
+ machineReboot: function () {
+ this._xvpOp(1, 3);
},
- xvpReset: function () {
- return this.xvpOp(1, 4);
+ machineReset: function () {
+ this._xvpOp(1, 4);
},
// Send a key press. If 'down' is not specified then send a down key
return true;
}
- if (this._qemuExtKeyEventSupported) {
- var scancode = XtScancode[code];
+ var scancode = XtScancode[code];
- if (scancode === undefined) {
- Log.Error('Unable to find a xt scancode for code: ' + code);
- // FIXME: not in the spec, but this is what
- // gtk-vnc does
- scancode = 0;
- }
+ if (this._qemuExtKeyEventSupported && scancode) {
+ // 0 is NoSymbol
+ keysym = keysym || 0;
Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + keysym + ", scancode " + scancode);
RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
} else {
+ if (!keysym) {
+ return false;
+ }
Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
}
RFB.messages.clientCutText(this._sock, text);
},
+ autoscale: function (width, height, downscaleOnly) {
+ if (this._rfb_connection_state !== 'connected') { return; }
+ this._display.autoscale(width, height, downscaleOnly);
+ },
+
+ viewportChangeSize: function(width, height) {
+ if (this._rfb_connection_state !== 'connected') { return; }
+ this._display.viewportChangeSize(width, height);
+ },
+
+ clippingDisplay: function () {
+ if (this._rfb_connection_state !== 'connected') { return false; }
+ return this._display.clippingDisplay();
+ },
+
// Requests a change of remote desktop size. This message is an extension
// and may only be sent if we have received an ExtendedDesktopSize message
requestDesktopSize: function (width, height) {
_connect: function () {
Log.Debug(">> RFB.connect");
- this._init_vars();
-
- var uri;
- if (typeof UsingSocketIO !== 'undefined') {
- uri = 'http';
- } else {
- uri = this._encrypt ? 'wss' : 'ws';
- }
- uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path;
- Log.Info("connecting to " + uri);
+ Log.Info("connecting to " + this._url);
try {
// WebSocket.onopen transitions to the RFB init states
- this._sock.open(uri, this._wsProtocols);
+ this._sock.open(this._url, this._wsProtocols);
} catch (e) {
if (e.name === 'SyntaxError') {
this._fail("Invalid host or port value given", e);
}
}
+ // Always grab focus on some kind of click event
+ this._target.addEventListener("mousedown", this._focusCanvas);
+ this._target.addEventListener("touchstart", this._focusCanvas);
+
Log.Debug("<< RFB.connect");
},
_disconnect: function () {
Log.Debug(">> RFB.disconnect");
+ this._target.removeEventListener("mousedown", this._focusCanvas);
+ this._target.removeEventListener("touchstart", this._focusCanvas);
this._cleanup();
this._sock.close();
this._print_stats();
Log.Debug("<< RFB.disconnect");
},
- _init_vars: function () {
- // reset state
- 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 Inflator();
- }
- },
-
_print_stats: function () {
+ var stats = this._encStats;
+
Log.Info("Encoding stats for this connection:");
- var i, s;
- for (i = 0; i < this._encodings.length; i++) {
- s = this._encStats[this._encodings[i][1]];
+ Object.keys(stats).forEach(function (key) {
+ var s = stats[key];
if (s[0] + s[1] > 0) {
- Log.Info(" " + this._encodings[i][0] + ": " + s[0] + " rects");
+ Log.Info(" " + encodingName(key) + ": " + s[0] + " rects");
}
- }
+ });
Log.Info("Encoding stats since page load:");
- for (i = 0; i < this._encodings.length; i++) {
- s = this._encStats[this._encodings[i][1]];
- Log.Info(" " + this._encodings[i][0] + ": " + s[1] + " rects");
- }
+ Object.keys(stats).forEach(function (key) {
+ var s = stats[key];
+ Log.Info(" " + encodingName(key) + ": " + s[1] + " rects");
+ });
},
_cleanup: function () {
if (!this._view_only) { this._mouse.ungrab(); }
this._display.defaultCursor();
if (Log.get_logging() !== 'debug') {
- // Show noVNC logo on load and when disconnected, unless in
+ // Show noVNC logo when disconnected, unless in
// debug mode
this._display.clear();
}
},
+ // Event handler for canvas so this points to the canvas element
+ _focusCanvas: function(event) {
+ // Respect earlier handlers' request to not do side-effects
+ if (!event.defaultPrevented)
+ this.focus();
+ },
+
/*
* Connection states:
* connecting
}
},
+ _setCapability: function (cap, val) {
+ this._capabilities[cap] = val;
+ this._onCapabilities(this, this._capabilities);
+ },
+
_handle_message: function () {
if (this._sock.rQlen() === 0) {
Log.Warn("handle_message called on an empty receive queue");
// 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) {
- var msg = 'XVP credentials required (user' + xvp_sep +
- 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password;
- this._onPasswordRequired(this, msg);
+ if (!this._rfb_credentials.username ||
+ !this._rfb_credentials.password ||
+ !this._rfb_credentials.target) {
+ this._onCredentialsRequired(this, ["username", "password", "target"]);
return false;
}
- var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
- String.fromCharCode(xvp_auth[1].length) +
- xvp_auth[0] +
- xvp_auth[1];
+ var xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) +
+ String.fromCharCode(this._rfb_credentials.target.length) +
+ this._rfb_credentials.username +
+ this._rfb_credentials.target;
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) {
- this._onPasswordRequired(this);
+ if (this._sock.rQwait("auth challenge", 16)) { return false; }
+
+ if (!this._rfb_credentials.password) {
+ this._onCredentialsRequired(this, ["password"]);
return false;
}
- if (this._sock.rQwait("auth challenge", 16)) { return false; }
-
// TODO(directxman12): make genDES not require an Array
var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
- var response = RFB.genDES(this._rfb_password, challenge);
+ var response = RFB.genDES(this._rfb_credentials.password, challenge);
this._sock.send(response);
this._rfb_init_state = "SecurityResult";
return true;
} else {
return this._fail("Authentication failure");
}
- return false;
case 2:
return this._fail("Too many authentication attempts");
default:
if (this._sock.rQwait("server initialization", 24)) { return false; }
/* Screen size */
- this._fb_width = this._sock.rQshift16();
- this._fb_height = this._sock.rQshift16();
- this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
+ var width = this._sock.rQshift16();
+ var height = this._sock.rQshift16();
/* PIXEL_FORMAT */
var bpp = this._sock.rQshift8();
// NB(directxman12): these are down here so that we don't run them multiple times
// if we backtrack
- Log.Info("Screen: " + this._fb_width + "x" + this._fb_height +
+ Log.Info("Screen: " + width + "x" + height +
", bpp: " + bpp + ", depth: " + depth +
", big_endian: " + big_endian +
", true_color: " + true_color +
// we're past the point where we could backtrack, so it's safe to call this
this._onDesktopName(this, this._fb_name);
- this._display.resize(this._fb_width, this._fb_height);
- this._onFBResize(this, this._fb_width, this._fb_height);
+ this._resize(width, height);
if (!this._view_only) { this._keyboard.grab(); }
if (!this._view_only) { this._mouse.grab(); }
- RFB.messages.pixelFormat(this._sock, 4, 3, true);
- RFB.messages.clientEncodings(this._sock, this._encodings, this._local_cursor);
+ this._fb_depth = 24;
+
+ if (this._fb_name === "Intel(r) AMT KVM") {
+ Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
+ this._fb_depth = 8;
+ }
+
+ RFB.messages.pixelFormat(this._sock, this._fb_depth, true);
+ this._sendEncodings();
RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
this._timing.fbu_rt_start = (new Date()).getTime();
this._timing.pixels = 0;
+ // Cursor will be server side until the server decides to honor
+ // our request and send over the cursor image
+ this._display.disableLocalCursor();
+
this._updateConnectionState('connected');
return true;
},
+ _sendEncodings: function () {
+ var encs = [];
+
+ // In preference order
+ encs.push(encodings.encodingCopyRect);
+ // Only supported with full depth support
+ if (this._fb_depth == 24) {
+ encs.push(encodings.encodingTight);
+ encs.push(encodings.encodingHextile);
+ encs.push(encodings.encodingRRE);
+ }
+ encs.push(encodings.encodingRaw);
+
+ // Psuedo-encoding settings
+ encs.push(encodings.pseudoEncodingTightPNG);
+ encs.push(encodings.pseudoEncodingQualityLevel0 + 6);
+ encs.push(encodings.pseudoEncodingCompressLevel0 + 2);
+
+ encs.push(encodings.pseudoEncodingDesktopSize);
+ encs.push(encodings.pseudoEncodingLastRect);
+ encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
+ encs.push(encodings.pseudoEncodingExtendedDesktopSize);
+ encs.push(encodings.pseudoEncodingXvp);
+ encs.push(encodings.pseudoEncodingFence);
+ encs.push(encodings.pseudoEncodingContinuousUpdates);
+
+ if (this._local_cursor && this._fb_depth == 24) {
+ encs.push(encodings.pseudoEncodingCursor);
+ }
+
+ RFB.messages.clientEncodings(this._sock, encs);
+ },
+
/* RFB protocol initialization states:
* ProtocolVersion
* Security
case 1: // XVP_INIT
this._rfb_xvp_ver = xvp_ver;
Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
- this._onXvpInit(this._rfb_xvp_ver);
+ this._setCapability("power", true);
break;
default:
this._fail("Unexpected server message",
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]) {
+ if (!this._encHandlers[this._FBU.encoding]) {
this._fail("Unexpected server message",
"Unsupported encoding " +
this._FBU.encoding);
this._timing.cur_fbu += (now - this._timing.last_fbu);
if (ret) {
+ if (!(this._FBU.encoding in this._encStats)) {
+ this._encStats[this._FBU.encoding] = [0, 0];
+ }
this._encStats[this._FBU.encoding][0]++;
this._encStats[this._FBU.encoding][1]++;
this._timing.pixels += this._FBU.width * this._FBU.height;
this._display.flip();
- 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]});
-
return true; // We finished this FBU
},
RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
this._fb_width, this._fb_height);
- }
+ },
+
+ _resize: function(width, height) {
+ this._fb_width = width;
+ this._fb_height = height;
+
+ this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
+
+ this._display.resize(this._fb_width, this._fb_height);
+ this._onFBResize(this, this._fb_width, this._fb_height);
+
+ this._timing.fbu_rt_start = (new Date()).getTime();
+ this._updateContinuousUpdates();
+ },
+
+ _xvpOp: function (ver, op) {
+ if (this._rfb_xvp_ver < ver) { return; }
+ Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
+ RFB.messages.xvpOp(this._sock, ver, op);
+ },
};
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
['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
+ ['touchButton', 'rw', 'int'], // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
+ ['scale', 'rw', 'float'], // Display area scale factor
+ ['viewport', 'rw', 'bool'], // Use viewport clipping
['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
+ ['capabilities', 'ro', 'arr'], // Supported capabilities
// Callback functions
['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate): connection state change
['onNotification', 'rw', 'func'], // onNotification(rfb, msg, level, options): notification for the UI
['onDisconnected', 'rw', 'func'], // onDisconnected(rfb, reason): disconnection finished
- ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb, msg): VNC password is required
+ ['onCredentialsRequired', 'rw', 'func'], // onCredentialsRequired(rfb, types): VNC credentials are 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
+ ['onCapabilities', 'rw', 'func'] // onCapabilities(rfb, caps): the supported capabilities has changed
]);
RFB.prototype.set_local_cursor = function (cursor) {
// Need to send an updated list of encodings if we are connected
if (this._rfb_connection_state === "connected") {
- RFB.messages.clientEncodings(this._sock, this._encodings, cursor);
+ this._sendEncodings();
}
};
}
};
-RFB.prototype.get_display = function () { return this._display; };
-RFB.prototype.get_keyboard = function () { return this._keyboard; };
-RFB.prototype.get_mouse = function () { return this._mouse; };
+RFB.prototype.set_touchButton = function (button) {
+ this._mouse.set_touchButton(button);
+};
+
+RFB.prototype.get_touchButton = function () {
+ return this._mouse.get_touchButton();
+};
+
+RFB.prototype.set_scale = function (scale) {
+ this._display.set_scale(scale);
+};
+
+RFB.prototype.get_scale = function () {
+ return this._display.get_scale();
+};
+
+RFB.prototype.set_viewport = function (viewport) {
+ this._display.set_viewport(viewport);
+};
+
+RFB.prototype.get_viewport = function () {
+ return this._display.get_viewport();
+};
// Class Methods
RFB.messages = {
sock.flush();
},
- pixelFormat: function (sock, bpp, depth, true_color) {
+ pixelFormat: function (sock, depth, true_color) {
var buff = sock._sQ;
var offset = sock._sQlen;
+ var bpp, bits;
+
+ if (depth > 16) {
+ bpp = 32;
+ } else if (depth > 8) {
+ bpp = 16;
+ } else {
+ bpp = 8;
+ }
+
+ bits = Math.floor(depth/3);
+
buff[offset] = 0; // msg-type
buff[offset + 1] = 0; // padding
buff[offset + 2] = 0; // padding
buff[offset + 3] = 0; // padding
- buff[offset + 4] = bpp * 8; // bits-per-pixel
- buff[offset + 5] = depth * 8; // depth
+ buff[offset + 4] = bpp; // bits-per-pixel
+ buff[offset + 5] = depth; // depth
buff[offset + 6] = 0; // little-endian
buff[offset + 7] = true_color ? 1 : 0; // true-color
buff[offset + 8] = 0; // red-max
- buff[offset + 9] = 255; // red-max
+ buff[offset + 9] = (1 << bits) - 1; // red-max
buff[offset + 10] = 0; // green-max
- buff[offset + 11] = 255; // green-max
+ buff[offset + 11] = (1 << bits) - 1; // green-max
buff[offset + 12] = 0; // blue-max
- buff[offset + 13] = 255; // blue-max
+ buff[offset + 13] = (1 << bits) - 1; // blue-max
- buff[offset + 14] = 16; // red-shift
- buff[offset + 15] = 8; // green-shift
- buff[offset + 16] = 0; // blue-shift
+ buff[offset + 14] = bits * 2; // red-shift
+ buff[offset + 15] = bits * 1; // green-shift
+ buff[offset + 16] = bits * 0; // blue-shift
buff[offset + 17] = 0; // padding
buff[offset + 18] = 0; // padding
sock.flush();
},
- clientEncodings: function (sock, encodings, local_cursor) {
+ clientEncodings: function (sock, encodings) {
var buff = sock._sQ;
var offset = sock._sQlen;
buff[offset] = 2; // msg-type
buff[offset + 1] = 0; // padding
- // offset + 2 and offset + 3 are encoding count
+ buff[offset + 2] = encodings.length >> 8;
+ buff[offset + 3] = encodings.length;
- var i, j = offset + 4, cnt = 0;
+ var i, j = offset + 4;
for (i = 0; i < encodings.length; i++) {
- if (encodings[i][0] === "Cursor" && !local_cursor) {
- Log.Debug("Skipping Cursor pseudo-encoding");
- } else {
- var enc = encodings[i][1];
- buff[j] = enc >> 24;
- buff[j + 1] = enc >> 16;
- buff[j + 2] = enc >> 8;
- buff[j + 3] = enc;
-
- j += 4;
- cnt++;
- }
- }
+ var enc = encodings[i];
+ buff[j] = enc >> 24;
+ buff[j + 1] = enc >> 16;
+ buff[j + 2] = enc >> 8;
+ buff[j + 3] = enc;
- buff[offset + 2] = cnt >> 8;
- buff[offset + 3] = cnt;
+ j += 4;
+ }
sock._sQlen += j - offset;
sock.flush();
sock._sQlen += 10;
sock.flush();
- }
+ },
+
+ xvpOp: function (sock, ver, op) {
+ var buff = sock._sQ;
+ var offset = sock._sQlen;
+
+ buff[offset] = 250; // msg-type
+ buff[offset + 1] = 0; // padding
+
+ buff[offset + 2] = ver;
+ buff[offset + 3] = op;
+
+ sock._sQlen += 4;
+ sock.flush();
+ },
};
RFB.genDES = function (password, challenge) {
this._FBU.lines = this._FBU.height;
}
- this._FBU.bytes = this._FBU.width * 4; // at least a line
+ var pixelSize = this._fb_depth == 8 ? 1 : 4;
+ this._FBU.bytes = this._FBU.width * pixelSize; // 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 * 4)));
+ Math.floor(this._sock.rQlen() / (this._FBU.width * pixelSize)));
+ var data = this._sock.get_rQ();
+ var index = this._sock.get_rQi();
+ if (this._fb_depth == 8) {
+ var pixels = this._FBU.width * curr_height
+ var newdata = new Uint8Array(pixels * 4);
+ var i;
+ for (i = 0;i < pixels;i++) {
+ newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
+ newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
+ newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
+ newdata[i * 4 + 4] = 0;
+ }
+ data = newdata;
+ index = 0;
+ }
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 * 4);
+ curr_height, data, index);
+ this._sock.rQskipBytes(this._FBU.width * curr_height * pixelSize);
this._FBU.lines -= curr_height;
if (this._FBU.lines > 0) {
- this._FBU.bytes = this._FBU.width * 4; // At least another line
+ this._FBU.bytes = this._FBU.width * pixelSize; // At least another line
} else {
this._FBU.rects--;
this._FBU.bytes = 0;
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;
- }
- }
- return [header, data];
- },
-
- display_tight: function (isTightPNG) {
+ TIGHT: function () {
this._FBU.bytes = 1; // compression-control byte
if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
"Illegal tight compression received, " +
"ctl: " + ctl);
- if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
- return this._fail("Unexpected server message",
- "filter/copy received in tightPNG mode");
- }
-
switch (cmode) {
// fill use depth because TPIXELs drop the padding byte
case "fill": // TPIXEL
return true;
},
- TIGHT: function () { return this._encHandlers.display_tight(false); },
- TIGHT_PNG: function () { return this._encHandlers.display_tight(true); },
-
last_rect: function () {
this._FBU.rects = 0;
return true;
},
- handle_FB_resize: function () {
- this._fb_width = this._FBU.width;
- this._fb_height = this._FBU.height;
- this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
- this._display.resize(this._fb_width, this._fb_height);
- this._onFBResize(this, this._fb_width, this._fb_height);
- this._timing.fbu_rt_start = (new Date()).getTime();
- this._updateContinuousUpdates();
-
- this._FBU.bytes = 0;
- this._FBU.rects -= 1;
- return true;
- },
-
ExtendedDesktopSize: function () {
this._FBU.bytes = 1;
if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
this._supportsSetDesktopSize = true;
+ this._setCapability("resize", true);
+
var number_of_screens = this._sock.rQpeek8();
this._FBU.bytes = 4 + (number_of_screens * 16);
}
this._notification("Server did not accept the resize request: "
+ msg, 'normal');
- return true;
+ } else {
+ this._resize(this._FBU.width, this._FBU.height);
}
- this._encHandlers.handle_FB_resize();
+ this._FBU.bytes = 0;
+ this._FBU.rects -= 1;
return true;
},
DesktopSize: function () {
- this._encHandlers.handle_FB_resize();
+ this._resize(this._FBU.width, this._FBU.height);
+ this._FBU.bytes = 0;
+ this._FBU.rects -= 1;
return true;
},
QEMUExtendedKeyEvent: function () {
this._FBU.rects--;
- var keyboardEvent = document.createEvent("keyboardEvent");
- if (keyboardEvent.code !== undefined) {
- this._qemuExtKeyEventSupported = true;
+ // Old Safari doesn't support creating keyboard events
+ try {
+ var keyboardEvent = document.createEvent("keyboardEvent");
+ if (keyboardEvent.code !== undefined) {
+ this._qemuExtKeyEventSupported = true;
+ }
+ } catch (err) {
}
},
-
- JPEG_quality_lo: function () {
- Log.Error("Server sent jpeg_quality pseudo-encoding");
- },
-
- compress_lo: function () {
- Log.Error("Server sent compress level pseudo-encoding");
- }
};