diff --git a/images/power.png b/images/power.png new file mode 100644 index 00000000..f68fd081 Binary files /dev/null and b/images/power.png differ diff --git a/include/base.css b/include/base.css index f57d20e3..2ab41768 100644 --- a/include/base.css +++ b/include/base.css @@ -52,12 +52,19 @@ html { float:right; } +#noVNC_buttons { + white-space: nowrap; +} + #noVNC_view_drag_button { display: none; } #sendCtrlAltDelButton { display: none; } +#noVNC_xvp_buttons { + display: none; +} #noVNC_mobile_buttons { display: none; } @@ -199,6 +206,16 @@ html { border-radius:10px; } +#noVNC_xvp { + display:none; + margin-top:73px; + right:30px; + position:fixed; +} +#noVNC_xvp.top:after { + right:125px; +} + #noVNC_clipboard { display:none; margin-top:73px; diff --git a/include/rfb.js b/include/rfb.js index 55d38c08..b296390d 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -46,6 +46,8 @@ var that = {}, // Public API methods rfb_max_version= 3.8, rfb_auth_scheme= '', + rfb_xvp_ver = 0, + // In preference order encodings = [ @@ -64,7 +66,8 @@ var that = {}, // Public API methods //['JPEG_quality_hi', -23 ], //['compress_lo', -255 ], ['compress_hi', -247 ], - ['last_rect', -224 ] + ['last_rect', -224 ], + ['xvp', -309 ] ], encHandlers = {}, @@ -138,6 +141,7 @@ Util.conf_defaults(conf, that, defaults, [ ['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'], ['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'], ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'], @@ -164,6 +168,8 @@ Util.conf_defaults(conf, that, defaults, [ '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() { }, @@ -644,7 +650,8 @@ init_msg = function() { 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; + green_shift, blue_shift, true_color, name_length, is_repeater, + xvp_sep, xvp_auth, xvp_auth_str; //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0)); switch (rfb_state) { @@ -709,7 +716,7 @@ init_msg = function() { 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] < 3)) { + if ((types[i] > rfb_auth_scheme) && (types[i] < 3 || types[i] == 22)) { rfb_auth_scheme = types[i]; } } @@ -744,6 +751,23 @@ init_msg = function() { } // 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; + } + xvp_auth_str = String.fromCharCode(xvp_auth[0].length) + + String.fromCharCode(xvp_auth[1].length) + + xvp_auth[0] + + 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 @@ -896,7 +920,8 @@ normal_msg = function() { //Util.Debug(">> normal_msg"); var ret = true, msg_type, length, text, - c, first_colour, num_colours, red, green, blue; + c, first_colour, num_colours, red, green, blue, + xvp_ver, xvp_msg; if (FBU.rects > 0) { msg_type = 0; @@ -946,6 +971,24 @@ normal_msg = function() { 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)); @@ -1800,6 +1843,25 @@ that.sendCtrlAltDel = function() { 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) { diff --git a/include/ui.js b/include/ui.js index 9d2f1c4d..2533c17a 100644 --- a/include/ui.js +++ b/include/ui.js @@ -92,6 +92,7 @@ start: function(callback) { UI.rfb = RFB({'target': $D('noVNC_canvas'), 'onUpdateState': UI.updateState, + 'onXvpInit': UI.updateXvpVisualState, 'onClipboard': UI.clipReceive, 'onDesktopName': UI.updateDocumentTitle}); @@ -185,8 +186,12 @@ addMouseHandlers: function() { $D("sendEscButton").onclick = UI.sendEsc; $D("sendCtrlAltDelButton").onclick = UI.sendCtrlAltDel; + $D("xvpShutdownButton").onclick = UI.xvpShutdown; + $D("xvpRebootButton").onclick = UI.xvpReboot; + $D("xvpResetButton").onclick = UI.xvpReset; $D("noVNC_status").onclick = UI.togglePopupStatusPanel; $D("noVNC_popup_status_panel").onclick = UI.togglePopupStatusPanel; + $D("xvpButton").onclick = UI.toggleXvpPanel; $D("clipboardButton").onclick = UI.toggleClipboardPanel; $D("settingsButton").onclick = UI.toggleSettingsPanel; $D("connectButton").onclick = UI.toggleConnectPanel; @@ -303,6 +308,39 @@ togglePopupStatusPanel: function() { } }, +// Show the XVP panel +toggleXvpPanel: function() { + // Close the description panel + $D('noVNC_description').style.display = "none"; + // Close settings if open + if (UI.settingsOpen === true) { + UI.settingsApply(); + UI.closeSettingsMenu(); + } + // Close connection settings if open + if (UI.connSettingsOpen === true) { + UI.toggleConnectPanel(); + } + // Close popup status panel if open + if (UI.popupStatusOpen === true) { + UI.togglePopupStatusPanel(); + } + // Close clipboard panel if open + if (UI.clipboardOpen === true) { + UI.toggleClipboardPanel(); + } + // Toggle XVP panel + if (UI.xvpOpen === true) { + $D('noVNC_xvp').style.display = "none"; + $D('xvpButton').className = "noVNC_status_button"; + UI.xvpOpen = false; + } else { + $D('noVNC_xvp').style.display = "block"; + $D('xvpButton').className = "noVNC_status_button_selected"; + UI.xvpOpen = true; + } +}, + // Show the clipboard panel toggleClipboardPanel: function() { // Close the description panel @@ -320,6 +358,10 @@ toggleClipboardPanel: function() { if (UI.popupStatusOpen === true) { UI.togglePopupStatusPanel(); } + // Close XVP panel if open + if (UI.xvpOpen === true) { + UI.toggleXvpPanel(); + } // Toggle Clipboard Panel if (UI.clipboardOpen === true) { $D('noVNC_clipboard').style.display = "none"; @@ -350,6 +392,10 @@ toggleConnectPanel: function() { if (UI.popupStatusOpen === true) { UI.togglePopupStatusPanel(); } + // Close XVP panel if open + if (UI.xvpOpen === true) { + UI.toggleXvpPanel(); + } // Toggle Connection Panel if (UI.connSettingsOpen === true) { @@ -414,6 +460,10 @@ openSettingsMenu: function() { if (UI.popupStatusOpen === true) { UI.togglePopupStatusPanel(); } + // Close XVP panel if open + if (UI.xvpOpen === true) { + UI.toggleXvpPanel(); + } $D('noVNC_settings').style.display = "block"; $D('settingsButton').className = "noVNC_status_button_selected"; UI.settingsOpen = true; @@ -467,6 +517,18 @@ sendCtrlAltDel: function() { UI.rfb.sendCtrlAltDel(); }, +xvpShutdown: function() { + UI.rfb.xvpShutdown(); +}, + +xvpReboot: function() { + UI.rfb.xvpReboot(); +}, + +xvpReset: function() { + UI.rfb.xvpReset(); +}, + setMouseButton: function(num) { var b, blist = [0, 1,2,4], button; @@ -566,6 +628,7 @@ updateVisualState: function() { $D('showKeyboard').style.display = "none"; $D('noVNC_extra_keys').style.display = "none"; $D('sendCtrlAltDelButton').style.display = "none"; + UI.updateXvpVisualState(0); } // State change disables viewport dragging. @@ -589,6 +652,19 @@ updateVisualState: function() { //Util.Debug("<< updateVisualState"); }, +// Disable/enable XVP button +updateXvpVisualState: function(ver) { + if (ver >= 1) { + $D('xvpButton').style.display = 'inline'; + } else { + $D('xvpButton').style.display = 'none'; + // Close XVP panel if open + if (UI.xvpOpen === true) { + UI.toggleXvpPanel(); + } + } +}, + // Display the desktop name in the document title updateDocumentTitle: function(rfb, name) { diff --git a/vnc.html b/vnc.html index 8b8847cc..42e04154 100644 --- a/vnc.html +++ b/vnc.html @@ -95,6 +95,9 @@ + @@ -138,6 +141,15 @@ value="Clear"> + +