From 1d97ec7385d0c8992ef1ef629cf2549e3f9fbadd Mon Sep 17 00:00:00 2001 From: Jeremy White Date: Wed, 3 Sep 2014 13:33:04 -0500 Subject: If an agent is attached, enable dynamic resizing of the guest screen. --- cursor.js | 14 +++++++++++ display.js | 5 ++-- enums.js | 24 +++++++++++++++++- main.js | 76 +++++++++++++++++++++++++++++++++++++++++++++++--------- resize.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++ spice.css | 28 +++++++++++---------- spice.html | 12 ++++++++- spice_auto.html | 11 ++++++++- spiceconn.js | 2 ++ spicemsg.js | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 289 insertions(+), 30 deletions(-) create mode 100644 resize.js diff --git a/cursor.js b/cursor.js index cafdf65..71e941d 100644 --- a/cursor.js +++ b/cursor.js @@ -80,6 +80,20 @@ SpiceCursorConn.prototype.process_channel_message = function(msg) return true; } + if (msg.type == SPICE_MSG_CURSOR_RESET) + { + DEBUG > 1 && console.log("SpiceMsgCursorReset"); + document.getElementById(this.parent.screen_id).style.cursor = "auto"; + return true; + } + + if (msg.type == SPICE_MSG_CURSOR_INVAL_ALL) + { + DEBUG > 1 && console.log("SpiceMsgCursorInvalAll"); + // FIXME - There may be something useful to do here... + return true; + } + return false; } diff --git a/display.js b/display.js index 6d7dafc..2aa5985 100644 --- a/display.js +++ b/display.js @@ -446,8 +446,9 @@ SpiceDisplayConn.prototype.process_channel_message = function(msg) /* This .save() is done entirely to enable SPICE_MSG_DISPLAY_RESET */ canvas.context.save(); document.getElementById(this.parent.screen_id).appendChild(canvas); - document.getElementById(this.parent.screen_id).setAttribute('width', m.surface.width); - document.getElementById(this.parent.screen_id).setAttribute('height', m.surface.height); + + /* We're going to leave width dynamic, but correctly set the height */ + document.getElementById(this.parent.screen_id).style.height = m.surface.height + "px"; this.hook_events(); } return true; diff --git a/enums.js b/enums.js index 7d41226..d99b38e 100644 --- a/enums.js +++ b/enums.js @@ -71,7 +71,12 @@ var SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST = 111; var SPICE_MSG_MAIN_MIGRATE_END = 112; var SPICE_MSG_MAIN_NAME = 113; var SPICE_MSG_MAIN_UUID = 114; -var SPICE_MSG_END_MAIN = 115; +var SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS = 115; +var SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS = 116; +var SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK = 117; +var SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK = 118; +var SPICE_MSG_END_MAIN = 119; + var SPICE_MSGC_ACK_SYNC = 1; @@ -300,3 +305,20 @@ var SPICE_CURSOR_TYPE_ALPHA = 0, SPICE_CURSOR_TYPE_COLOR32 = 6; var SPICE_VIDEO_CODEC_TYPE_MJPEG = 1; + +var VD_AGENT_PROTOCOL = 1; + +var VD_AGENT_MOUSE_STATE = 1, + VD_AGENT_MONITORS_CONFIG = 2, + VD_AGENT_REPLY = 3, + VD_AGENT_CLIPBOARD = 4, + VD_AGENT_DISPLAY_CONFIG = 5, + VD_AGENT_ANNOUNCE_CAPABILITIES = 6, + VD_AGENT_CLIPBOARD_GRAB = 7, + VD_AGENT_CLIPBOARD_REQUEST = 8, + VD_AGENT_CLIPBOARD_RELEASE = 9, + VD_AGENT_FILE_XFER_START =10, + VD_AGENT_FILE_XFER_STATUS =11, + VD_AGENT_FILE_XFER_DATA =12, + VD_AGENT_CLIENT_DISCONNECTED =13, + VD_AGENT_MAX_CLIPBOARD =14; diff --git a/main.js b/main.js index 3656a8d..91f1963 100644 --- a/main.js +++ b/main.js @@ -40,6 +40,9 @@ ** onerror (optional) If given, a function to receive async ** errors. Note that you should also catch ** errors for ones that occur inline +** onagent (optional) If given, a function to be called when +** a VD agent is connected; a good opportunity +** to request a resize ** ** Throws error if there are troubles. Requires a modern (by 2012 standards) ** browser, including WebSocket and WebSocket.binaryType == arraybuffer @@ -78,15 +81,11 @@ SpiceMainConn.prototype.process_channel_message = function(msg) " ; ram_hint " + this.main_init.ram_hint); } - this.mouse_mode = this.main_init.current_mouse_mode; - if (this.main_init.current_mouse_mode != SPICE_MOUSE_MODE_CLIENT && - (this.main_init.supported_mouse_modes & SPICE_MOUSE_MODE_CLIENT)) - { - var mode_request = new SpiceMsgcMainMouseModeRequest(SPICE_MOUSE_MODE_CLIENT); - var mr = new SpiceMiniData(); - mr.build_msg(SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, mode_request); - this.send_msg(mr); - } + this.handle_mouse_mode(this.main_init.current_mouse_mode, + this.main_init.supported_mouse_modes); + + if (this.main_init.agent_connected) + this.connect_agent(); var attach = new SpiceMiniData; attach.type = SPICE_MSGC_MAIN_ATTACH_CHANNELS; @@ -99,9 +98,7 @@ SpiceMainConn.prototype.process_channel_message = function(msg) { var mode = new SpiceMsgMainMouseMode(msg.data); DEBUG > 0 && this.log_info("Mouse supported modes " + mode.supported_modes + "; current " + mode.current_mode); - this.mouse_mode = mode.current_mode; - if (this.inputs) - this.inputs.mouse_mode = mode.current_mode; + this.handle_mouse_mode(mode.current_mode, mode.supported_modes); return true; } @@ -144,6 +141,19 @@ SpiceMainConn.prototype.process_channel_message = function(msg) return true; } + if (msg.type == SPICE_MSG_MAIN_AGENT_CONNECTED || + msg.type == SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS) + { + this.connect_agent(); + return true; + } + + if (msg.type == SPICE_MSG_MAIN_AGENT_DISCONNECTED) + { + this.agent_connected = false; + return true; + } + return false; } @@ -177,3 +187,45 @@ SpiceMainConn.prototype.stop = function(msg) this.extra_channels[e].cleanup(); this.extra_channels = undefined; } + +SpiceMainConn.prototype.resize_window = function(flags, width, height, depth, x, y) +{ + if (this.agent_connected > 0) + { + var monitors_config = new VDAgentMonitorsConfig(flags, width, height, depth, x, y); + var agent_data = new SpiceMsgcMainAgentData(VD_AGENT_MONITORS_CONFIG, monitors_config); + var mr = new SpiceMiniData(); + mr.build_msg(SPICE_MSGC_MAIN_AGENT_DATA, agent_data); + this.send_msg(mr); + } +} + +SpiceMainConn.prototype.connect_agent = function() +{ + this.agent_connected = true; + + var agent_start = new SpiceMsgcMainAgentStart(0); + var mr = new SpiceMiniData(); + mr.build_msg(SPICE_MSGC_MAIN_AGENT_START, agent_start); + this.send_msg(mr); + + if (this.onagent !== undefined) + this.onagent(this); + +} + +SpiceMainConn.prototype.handle_mouse_mode = function(current, supported) +{ + this.mouse_mode = current; + if (current != SPICE_MOUSE_MODE_CLIENT && (supported & SPICE_MOUSE_MODE_CLIENT)) + { + var mode_request = new SpiceMsgcMainMouseModeRequest(SPICE_MOUSE_MODE_CLIENT); + var mr = new SpiceMiniData(); + mr.build_msg(SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, mode_request); + this.send_msg(mr); + } + + if (this.inputs) + this.inputs.mouse_mode = current; +} + diff --git a/resize.js b/resize.js new file mode 100644 index 0000000..f5410d3 --- /dev/null +++ b/resize.js @@ -0,0 +1,70 @@ +"use strict"; +/* + Copyright (C) 2014 by Jeremy P. White + + This file is part of spice-html5. + + spice-html5 is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + spice-html5 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with spice-html5. If not, see . +*/ + +/*---------------------------------------------------------------------------- +** resize.js +** This bit of Javascript is a set of logic to help with window +** resizing, using the agent channel to request screen resizes. +** +** It's a bit tricky, as we want to wait for resizing to settle down +** before sending a size. Further, while horizontal resizing to use the whole +** browser width is fairly easy to arrange with css, resizing an element to use +** the whole vertical space (or to force a middle div to consume the bulk of the browser +** window size) is tricky, and the consensus seems to be that Javascript is +** the only right way to do it. +**--------------------------------------------------------------------------*/ +function resize_helper(sc) +{ + var w = document.getElementById(sc.screen_id).clientWidth; + var h = document.getElementById(sc.screen_id).clientHeight; + + var m = document.getElementById(sc.message_id); + + /* Resize vertically; basically we leave a 20 pixel margin + at the bottom, and use the position of the message window + to figure out how to resize */ + var hd = window.innerHeight - m.offsetHeight - m.offsetTop - 20; + + /* Xorg requires height be a multiple of 8; round up */ + h = h + hd; + if (h % 8 > 0) + h += (8 - (h % 8)); + + /* Xorg requires width be a multiple of 8; round up */ + if (w % 8 > 0) + w += (8 - (w % 8)); + + + sc.resize_window(0, w, h, 32, 0, 0); + sc.spice_resize_timer = undefined; +} + +function handle_resize(e) +{ + var sc = window.spice_connection; + + if (sc && sc.spice_resize_timer) + { + window.clearTimeout(sc.spice_resize_timer); + sc.spice_resize_timer = undefined; + } + + sc.spice_resize_timer = window.setTimeout(resize_helper, 200, sc); +} diff --git a/spice.css b/spice.css index 450cf05..5d092ba 100644 --- a/spice.css +++ b/spice.css @@ -11,11 +11,9 @@ body #login { - width: 790px; - margin-top: 20px; + width: 95%; margin-left: auto; margin-right: auto; - padding: 2px 10px 2px 20px; border: 1px solid #999999; background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#24414e)); background: -moz-linear-gradient(top, #fff, #24414e); @@ -28,7 +26,7 @@ body { display: inline-block; margin-right: 5px; - padding-right: 10px; + padding: 2px 10px 2px 20px; border-right: 1px solid #999999; font-size: 20px; font-weight: bolder; @@ -66,16 +64,12 @@ body #spice-area { - text-align: center; -} -.spice-screen -{ - display: inline-block; - padding: 10px; - min-width: 800px; - min-height: 600px; + height: 100%; + width: 95%; + padding: 0; + margin-left: auto; + margin-right: auto; border: solid #222222 1px; - background-color: #333333; -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2); -moz-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2); box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2); @@ -83,6 +77,14 @@ body -webkit-border-radius: 10px; border-radius: 10px; } +.spice-screen +{ + min-height: 600px; + height: 100%; + margin: 10px; + padding: 0; + background-color: #333333; +} .spice-message { width: 700px; height: 50px; diff --git a/spice.html b/spice.html index 3b7929c..e830eb3 100644 --- a/spice.html +++ b/spice.html @@ -54,6 +54,7 @@ + diff --git a/spice_auto.html b/spice_auto.html index 6e1135a..72c5be2 100644 --- a/spice_auto.html +++ b/spice_auto.html @@ -54,6 +54,7 @@ + diff --git a/spiceconn.js b/spiceconn.js index 81bc301..e33227e 100644 --- a/spiceconn.js +++ b/spiceconn.js @@ -57,6 +57,8 @@ function SpiceConn(o) this.onerror = o.onerror; if (o.onsuccess !== undefined) this.onsuccess = o.onsuccess; + if (o.onagent !== undefined) + this.onagent = o.onagent; this.state = "connecting"; this.ws.parent = this; diff --git a/spicemsg.js b/spicemsg.js index 78371bc..c64f5a3 100644 --- a/spicemsg.js +++ b/spicemsg.js @@ -402,6 +402,83 @@ SpiceMsgcMainMouseModeRequest.prototype = } } +function SpiceMsgcMainAgentStart(num_tokens) +{ + this.num_tokens = num_tokens; +} + +SpiceMsgcMainAgentStart.prototype = +{ + to_buffer: function(a, at) + { + at = at || 0; + var dv = new SpiceDataView(a); + dv.setUint32(at, this.num_tokens, true); at += 4; + }, + buffer_size: function() + { + return 4; + } +} + +function SpiceMsgcMainAgentData(type, data) +{ + this.protocol = VD_AGENT_PROTOCOL; + this.type = type; + this.opaque = 0; + this.size = data.buffer_size(); + this.data = data; +} + +SpiceMsgcMainAgentData.prototype = +{ + to_buffer: function(a, at) + { + at = at || 0; + var dv = new SpiceDataView(a); + dv.setUint32(at, this.protocol, true); at += 4; + dv.setUint32(at, this.type, true); at += 4; + dv.setUint64(at, this.opaque, true); at += 8; + dv.setUint32(at, this.size, true); at += 4; + this.data.to_buffer(a, at); + }, + buffer_size: function() + { + return 4 + 4 + 8 + 4 + this.data.buffer_size(); + } +} + +function VDAgentMonitorsConfig(flags, width, height, depth, x, y) +{ + this.num_mon = 1; + this.flags = flags; + this.width = width; + this.height = height; + this.depth = depth; + this.x = x; + this.y = y; +} + +VDAgentMonitorsConfig.prototype = +{ + to_buffer: function(a, at) + { + at = at || 0; + var dv = new SpiceDataView(a); + dv.setUint32(at, this.num_mon, true); at += 4; + dv.setUint32(at, this.flags, true); at += 4; + dv.setUint32(at, this.height, true); at += 4; + dv.setUint32(at, this.width, true); at += 4; + dv.setUint32(at, this.depth, true); at += 4; + dv.setUint32(at, this.x, true); at += 4; + dv.setUint32(at, this.y, true); at += 4; + }, + buffer_size: function() + { + return 28; + } +} + function SpiceMsgNotify(a, at) { this.from_buffer(a, at); -- cgit v1.2.3