diff --git a/images/clipboard.png b/images/clipboard.png new file mode 100644 index 00000000..571a9098 Binary files /dev/null and b/images/clipboard.png differ diff --git a/images/connect.png b/images/connect.png new file mode 100644 index 00000000..73c57047 Binary files /dev/null and b/images/connect.png differ diff --git a/images/disconnect.png b/images/disconnect.png new file mode 100644 index 00000000..d2dcf9c0 Binary files /dev/null and b/images/disconnect.png differ diff --git a/images/keyboard.png b/images/keyboard.png new file mode 100644 index 00000000..f7c47ce4 Binary files /dev/null and b/images/keyboard.png differ diff --git a/images/move.png b/images/move.png new file mode 100644 index 00000000..b6156b58 Binary files /dev/null and b/images/move.png differ diff --git a/images/screen_320x460.png b/images/screen_320x460.png new file mode 100644 index 00000000..172ec555 Binary files /dev/null and b/images/screen_320x460.png differ diff --git a/images/screen_57x57.png b/images/screen_57x57.png new file mode 100644 index 00000000..e2085f29 Binary files /dev/null and b/images/screen_57x57.png differ diff --git a/images/screen_700x700.png b/images/screen_700x700.png new file mode 100644 index 00000000..ae677685 Binary files /dev/null and b/images/screen_700x700.png differ diff --git a/images/settings.png b/images/settings.png new file mode 100644 index 00000000..3ff91d37 Binary files /dev/null and b/images/settings.png differ diff --git a/include/base.css b/include/base.css index 172b0e8d..fa620112 100644 --- a/include/base.css +++ b/include/base.css @@ -65,21 +65,27 @@ html { z-index:200; } +#noVNC_view_drag_button { + display: none; +} +#sendCtrlAltDelButton { + display: none; +} #noVNC_mobile_buttons { display: none; } .noVNC-buttons-left { - position:fixed; + float: left; padding-left:10px; - padding-top:9px; + padding-top:4px; } .noVNC-buttons-right { float:right; right: 0px; padding-right:10px; - padding-top:9px; + padding-top:4px; } #noVNC_status_bar { @@ -93,10 +99,6 @@ html { width:100%; } -.noVNC_status_button, #clipboardbutton, #connectbutton { - font-size: 14px; -} - #noVNC_status { height:20px; text-align: center; @@ -141,8 +143,14 @@ html { border-bottom-right-radius: 800px 600px; /*border-top-left-radius: 800px 600px;*/ } -#VNC_canvas { - background: #eee; + +#noVNC_container, #noVNC_canvas { + margin: 0px; + padding: 0px; +} + +#noVNC_canvas { + left: 0px; } #VNC_clipboard_clear_button { @@ -152,10 +160,6 @@ html { font-size: 11px; } -#noVNC_Settings { - width:200px; -} - #noVNC_clipboard_clear_button { float:right; } @@ -211,32 +215,36 @@ html { } /*Bubble contents divs*/ -#noVNC_Settings { - margin-top:72px; - position:fixed; - right:100px; +#noVNC_settings { display:none; + margin-top:72px; + right:10px; + position:fixed; } #noVNC_controls { margin-top:72px; - position:fixed; right:10px; + position:fixed; +} +#noVNC_controls.top:after { + right:15px; } #noVNC_clipboard { display:none; margin-top:72px; + right:20px; position:fixed; - right:180px; +} +#noVNC_clipboard.top:after { + right:85px; } /*Default noVNC logo.*/ #noVNC_logo { - width:400px; - margin-left:auto; - margin-right:auto; - font-size:160px; + margin-top: 170px; + margin-left: 10px; color:yellow; text-align:left; font-family: 'Orbitron', sans-serif; @@ -249,17 +257,80 @@ html { 1px 1px 0 #000; } + #noVNC_logo span{ color:green; } #keyboardinput { - width:0px; - height:0px; - background-color:#313131; + width:1px; + height:1px; + background-color:#6d84a2; border:0; } .noVNC_status_warn { background-color:yellow; } + +/* ---------------------------------------- + * Media sizing + * ---------------------------------------- + */ + + +.noVNC_status_button { + font-size: 12px; + padding: 2px; + vertical-align: middle; +} +#noVNC_clipboard_text { + width: 500px; + border-color: red; +} + +#noVNC_logo { + font-size: 180px; +} + +@media screen and (min-width: 481px) and (max-width: 640px) { + .noVNC_status_button { + font-size: 10px; + padding: 1px; + } + #noVNC_clipboard_text { + width: 410px; + border-color: yellow; + } + #noVNC_logo { + font-size: 150px; + } +} + +@media screen and (min-width: 321px) and (max-width: 480px) { + .noVNC_status_button { + font-size: 10px; + padding: 0px; + } + #noVNC_clipboard_text { + width: 250px; + border-color: green; + } + #noVNC_logo { + font-size: 110px; + } +} + +@media screen and (max-width: 320px) { + .noVNC_status_button { + font-size: 9px; + padding: 0px; + } + #noVNC_clipboard_text { + width: 220px; + border-color: blue; + } + #noVNC_logo { + font-size: 90px; + } +} diff --git a/include/black.css b/include/black.css index a194dca2..b155a116 100644 --- a/include/black.css +++ b/include/black.css @@ -19,3 +19,8 @@ background:#000; color:#fff; } + +#keyboardinput { + background-color:#000; +} + diff --git a/include/blue.css b/include/blue.css index f3de920c..a8baf705 100644 --- a/include/blue.css +++ b/include/blue.css @@ -20,3 +20,8 @@ background:#04073d; color:#fff; } + +#keyboardinput { + background-color:#04073d; +} + diff --git a/include/display.js b/include/display.js index cd228580..b11e1aaa 100644 --- a/include/display.js +++ b/include/display.js @@ -45,6 +45,7 @@ Util.conf_defaults(conf, that, defaults, [ ['true_color', 'rw', 'bool', true, 'Use true-color pixel data'], ['colourMap', 'rw', 'arr', [], 'Colour map array (when not true-color)'], ['scale', 'rw', 'float', 1.0, 'Display area scale factor 0.0 - 1.0'], + ['viewport', 'rw', 'bool', false, 'Use a viewport set with viewportChange()'], ['width', 'rw', 'int', null, 'Display area width'], ['height', 'rw', 'int', null, 'Display area height'], @@ -245,6 +246,14 @@ that.viewportChange = function(deltaX, deltaY, width, height) { var c = conf.target, v = viewport, cr = cleanRect, saveImg = null, saveStyle, x1, y1, vx2, vy2, w, h; + if (!conf.viewport) { + Util.Debug("Setting viewport to full display region"); + deltaX = -v.w; // Clamped later if out of bounds + deltaY = -v.h; // Clamped later if out of bounds + width = fb_width; + height = fb_height; + } + if (typeof(deltaX) === "undefined") { deltaX = 0; } if (typeof(deltaY) === "undefined") { deltaY = 0; } if (typeof(width) === "undefined") { width = v.w; } @@ -269,7 +278,10 @@ that.viewportChange = function(deltaX, deltaY, width, height) { v.h = height; - if (v.w > 0 && v.h > 0) { + if (v.w > 0 && v.h > 0 && c.width > 0 && c.height > 0) { + console.log("here1:", + ((c.width < v.w) ? c.width : v.w), + ((c.height < v.h) ? c.height : v.h)); saveImg = c_ctx.getImageData(0, 0, (c.width < v.w) ? c.width : v.w, (c.height < v.h) ? c.height : v.h); @@ -406,6 +418,13 @@ that.getCleanDirtyReset = function() { 'x2': v.x + v.w - 1, 'y2': v.y + v.h - 1}; return {'cleanBox': cleanBox, 'dirtyBoxes': dirtyBoxes}; +}; + +that.absX = function(x) { + return x + viewport.x; +} +that.absY = function(y) { + return y + viewport.y; } @@ -454,11 +473,9 @@ that.clear = function() { if (conf.logo) { that.resize(conf.logo.width, conf.logo.height); - that.viewportChange(0, 0, conf.logo.width, conf.logo.height); that.blitStringImage(conf.logo.data, 0, 0); } else { that.resize(640, 20); - that.viewportChange(0, 0, 640, 20); c_ctx.clearRect(0, 0, viewport.w, viewport.h); } @@ -551,7 +568,15 @@ imageDataCreate = function(width, height) { }; rgbxImageData = function(x, y, width, height, arr, offset) { - var img, i, j, data; + var img, i, j, data, v = viewport; + /* + if ((x - v.x >= v.w) || (y - v.y >= v.h) || + (x - v.x + width < 0) || (y - v.y + height < 0)) { + //console.log("skipping, out of bounds: ", x, y); + // Skipping because outside of viewport + return; + } + */ img = c_imageData(width, height); data = img.data; for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) { @@ -560,7 +585,7 @@ rgbxImageData = function(x, y, width, height, arr, offset) { data[i + 2] = arr[j + 2]; data[i + 3] = 255; // Set Alpha } - c_ctx.putImageData(img, x - viewport.x, y - viewport.y); + c_ctx.putImageData(img, x - v.x, y - v.y); }; // really slow fallback if we don't have imageData diff --git a/include/rfb.js b/include/rfb.js index 7cca8e60..ca8ec03d 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -118,7 +118,9 @@ var that = {}, // Public API methods /* Mouse state */ mouse_buttonMask = 0, - mouse_arr = []; + mouse_arr = [], + viewportDragging = false, + viewportDragPos = {}; // Configuration attributes Util.conf_defaults(conf, that, defaults, [ @@ -133,6 +135,8 @@ Util.conf_defaults(conf, that, defaults, [ ['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'], ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'], + ['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'], + ['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'], ['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'], @@ -547,7 +551,7 @@ function flushClient() { // overridable for testing checkEvents = function() { var now; - if (rfb_state === 'normal') { + if (rfb_state === 'normal' && !viewportDragging) { if (! flushClient()) { now = new Date().getTime(); if (now > last_req_time + conf.fbu_req_rate) { @@ -572,13 +576,43 @@ mouseButton = function(x, y, down, bmask) { } else { mouse_buttonMask ^= bmask; } - mouse_arr = mouse_arr.concat( pointerEvent(x, y) ); + + if (conf.viewportDrag) { + if (down && !viewportDragging) { + viewportDragging = true; + viewportDragPos = {'x': x, 'y': y}; + + // Skip sending mouse events + return; + } else { + viewportDragging = false; + } + } + + mouse_arr = mouse_arr.concat( + pointerEvent(display.absX(x), display.absY(y)) ); flushClient(); }; mouseMove = function(x, y) { //Util.Debug('>> mouseMove ' + x + "," + y); - mouse_arr = mouse_arr.concat( pointerEvent(x, y) ); + var deltaX, deltaY; + + 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}; + + display.viewportChange(deltaX, deltaY); + + // Skip sending mouse events + return; + } + + mouse_arr = mouse_arr.concat( + pointerEvent(display.absX(x), display.absY(y)) ); }; @@ -778,7 +812,6 @@ init_msg = function() { display.set_true_color(conf.true_color); display.resize(fb_width, fb_height); - display.viewportChange(0, 0, fb_width, fb_height); keyboard.grab(); mouse.grab(); @@ -1309,7 +1342,6 @@ encHandlers.DesktopSize = function set_desktopsize() { fb_width = FBU.width; fb_height = FBU.height; display.resize(fb_width, fb_height); - display.viewportChange(0, 0, fb_width, fb_height); timing.fbu_rt_start = (new Date()).getTime(); // Send a new non-incremental request ws.send(fbUpdateRequests()); diff --git a/include/ui.js b/include/ui.js index fa41854c..82f7d963 100644 --- a/include/ui.js +++ b/include/ui.js @@ -12,6 +12,7 @@ var UI = { +rfb_state : 'loaded', settingsOpen : false, connSettingsOpen : true, clipboardOpen: false, @@ -36,8 +37,8 @@ load: function() { // Settings with immediate effects UI.initSetting('logging', 'warn'); WebUtil.init_logging(UI.getSetting('logging')); - UI.initSetting('stylesheet', 'default'); + UI.initSetting('stylesheet', 'default'); WebUtil.selectStylesheet(null); // call twice to get around webkit bug WebUtil.selectStylesheet(UI.getSetting('stylesheet')); @@ -55,6 +56,7 @@ load: function() { UI.rfb = RFB({'target': $D('noVNC_canvas'), 'onUpdateState': UI.updateState, 'onClipboard': UI.clipReceive}); + UI.updateSettingsState(false); // Unfocus clipboard when over the VNC area //$D('VNC_screen').onmousemove = function () { @@ -66,9 +68,15 @@ load: function() { // Show mouse selector buttons on touch screen devices if ('ontouchstart' in document.documentElement) { + // Show mobile buttons $D('noVNC_mobile_buttons').style.display = "inline"; UI.setMouseButton(); - window.scrollTo(0, 1); + // Remove the address bar + setTimeout(function() { window.scrollTo(0, 1); }, 100); + UI.initSetting('clip', true); + $D('noVNC_clip').disabled = true; + } else { + UI.initSetting('clip', false); } //iOS Safari does not support CSS position:fixed. @@ -76,11 +84,21 @@ load: function() { if ((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i))) { - UI.setOnscroll(); - UI.setResize(); + //UI.setOnscroll(); + //UI.setResize(); } $D('noVNC_host').focus(); + + UI.setViewClip(); + Util.addEvent(window, 'resize', UI.setViewClip); + + Util.addEvent(window, 'beforeunload', function () { + if (UI.rfb_state === 'normal') { + return "You are currently connected."; + } + } ); + }, // Read form control compatible setting from cookie @@ -166,7 +184,6 @@ initSetting: function(name, defVal) { clickSettingsMenu: function() { if (UI.settingsOpen) { UI.settingsApply(); - UI.closeSettingsMenu(); } else { UI.updateSetting('encrypt'); @@ -177,6 +194,7 @@ clickSettingsMenu: function() { UI.updateSetting('cursor', false); $D('noVNC_cursor').disabled = true; } + UI.updateSetting('clip'); UI.updateSetting('shared'); UI.updateSetting('connectTimeout'); UI.updateSetting('stylesheet'); @@ -195,32 +213,16 @@ openSettingsMenu: function() { if (UI.connSettingsOpen == true) { UI.connectPanelbutton(); } - $D('noVNC_Settings').style.display = "block"; + $D('noVNC_settings').style.display = "block"; UI.settingsOpen = true; }, // Close menu (without applying settings) closeSettingsMenu: function() { - $D('noVNC_Settings').style.display = "none"; + $D('noVNC_settings').style.display = "none"; UI.settingsOpen = false; }, -// Disable/enable controls depending on connection state -settingsDisabled: function(disabled, rfb) { - //Util.Debug(">> settingsDisabled"); - $D('noVNC_encrypt').disabled = disabled; - $D('noVNC_true_color').disabled = disabled; - if (rfb && rfb.get_display() && rfb.get_display().get_cursor_uri()) { - $D('noVNC_cursor').disabled = disabled; - } else { - UI.updateSetting('cursor', false); - $D('noVNC_cursor').disabled = true; - } - $D('noVNC_shared').disabled = disabled; - $D('noVNC_connectTimeout').disabled = disabled; - //Util.Debug("<< settingsDisabled"); -}, - // Save/apply settings when 'Apply' button is pressed settingsApply: function() { //Util.Debug(">> settingsApply"); @@ -229,6 +231,7 @@ settingsApply: function() { if (UI.rfb.get_display().get_cursor_uri()) { UI.saveSetting('cursor'); } + UI.saveSetting('clip'); UI.saveSetting('shared'); UI.saveSetting('connectTimeout'); UI.saveSetting('stylesheet'); @@ -237,6 +240,7 @@ settingsApply: function() { // Settings with immediate (non-connected related) effect WebUtil.selectStylesheet(UI.getSetting('stylesheet')); WebUtil.init_logging(UI.getSetting('logging')); + UI.setViewClip(); //Util.Debug("<< settingsApply"); }, @@ -257,65 +261,60 @@ sendCtrlAltDel: function() { }, setMouseButton: function(num) { - var b, blist = [1,2,4], button, - mouse = UI.rfb.get_mouse(); + var b, blist = [0, 1,2,4], button; if (typeof num === 'undefined') { - // Show the default - num = mouse.get_touchButton(); - } else if (num === mouse.get_touchButton()) { - // Set all buttons off (no clicks) - mouse.set_touchButton(0); - num = 0; - } else { - // Turn on one button - mouse.set_touchButton(num); + // Disable mouse buttons + num = -1; + } + if (UI.rfb) { + UI.rfb.get_mouse().set_touchButton(num); } for (b = 0; b < blist.length; b++) { button = $D('noVNC_mouse_button' + blist[b]); if (blist[b] === num) { + button.style.display = ""; + } else { + button.style.display = "none"; + /* button.style.backgroundColor = "black"; button.style.color = "lightgray"; - } else { button.style.backgroundColor = ""; button.style.color = ""; + */ } } }, updateState: function(rfb, state, oldstate, msg) { - var s, sb, c, cad, klass; + var s, sb, c, d, cad, vd, klass; + UI.rfb_state = state; s = $D('noVNC_status'); sb = $D('noVNC_status_bar'); c = $D('connectPanelbutton'); - cad = $D('sendCtrlAltDelButton'); + d = $D('disconnectButton'); switch (state) { case 'failed': case 'fatal': - c.disabled = true; - cad.style.display = "none"; - UI.settingsDisabled(true, rfb); + c.style.display = ""; + d.style.display = "none"; + UI.updateSettingsState(false); klass = "noVNC_status_error"; break; case 'normal': - c.value = "Disconnect"; - c.onclick = UI.disconnect; - c.disabled = false; - cad.style.display = ""; - UI.settingsDisabled(true, rfb); + c.style.display = "none"; + d.style.display = ""; + UI.updateSettingsState(true); klass = "noVNC_status_normal"; break; case 'disconnected': $D('noVNC_logo').style.display = "block"; - c.value = "Connection"; - c.onclick = UI.connectPanelbutton; case 'loaded': - c.value = "Connection"; - c.onclick = UI.connectPanelbutton; - c.disabled = false; - cad.style.display = "none"; - UI.settingsDisabled(false, rfb); + //c.value = "Connection"; + c.style.display = ""; + d.style.display = "none"; + UI.updateSettingsState(false); klass = "noVNC_status_normal"; break; case 'password': @@ -325,15 +324,15 @@ updateState: function(rfb, state, oldstate, msg) { $D('noVNC_connect_button').onclick = UI.setPassword; $D('noVNC_password').focus(); - c.disabled = false; - cad.style.display = "none"; - UI.settingsDisabled(true, rfb); + c.style.display = "none"; + d.style.display = ""; + UI.updateSettingsState(false); klass = "noVNC_status_warn"; break; default: - c.disabled = true; - cad.style.display = "none"; - UI.settingsDisabled(true, rfb); + c.style.display = "none"; + d.style.display = ""; + UI.updateSettingsState(false); klass = "noVNC_status_warn"; break; } @@ -346,6 +345,40 @@ updateState: function(rfb, state, oldstate, msg) { }, +// Disable/enable controls depending on connection state +updateSettingsState: function(connected) { + + //Util.Debug(">> updateSettingsState"); + $D('noVNC_encrypt').disabled = connected; + $D('noVNC_true_color').disabled = connected; + if (UI.rfb && UI.rfb.get_display() && + UI.rfb.get_display().get_cursor_uri()) { + $D('noVNC_cursor').disabled = connected; + } else { + UI.updateSetting('cursor', false); + $D('noVNC_cursor').disabled = true; + } + $D('noVNC_shared').disabled = connected; + $D('noVNC_connectTimeout').disabled = connected; + + if (connected) { + UI.setViewClip(); + UI.setMouseButton(1); + $D('sendCtrlAltDelButton').style.display = "inline"; + $D('noVNC_view_drag_button').style.display = "inline"; + } else { + UI.setMouseButton(); + $D('sendCtrlAltDelButton').style.display = "none"; + $D('noVNC_view_drag_button').style.display = "none"; + } + + // State change disables viewport dragging. + // It is enabled (toggled) by direct click on the button + UI.setViewDrag(false); + //Util.Debug("<< updateSettingsState"); +}, + + clipReceive: function(rfb, text) { Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "..."); $D('noVNC_clipboard_text').value = text; @@ -412,6 +445,7 @@ clipSend: function() { showClipboard: function() { //Close settings if open if (UI.settingsOpen == true) { + UI.settingsApply(); UI.closeSettingsMenu(); } //Close connection settings if open @@ -428,31 +462,66 @@ showClipboard: function() { } }, +setViewClip: function(clip) { + var display, cur_clip, pos, new_w, new_h; + + if (UI.rfb) { + display = UI.rfb.get_display(); + } else { + return; + } + + cur_clip = display.get_viewport(); + + if (typeof(clip) === 'undefined') { + // Nothing + clip = UI.getSetting('clip'); + } + + if (clip && !cur_clip) { + // Turn clipping on + UI.updateSetting('clip', true); + } else if (!clip && cur_clip) { + // Turn clipping off + UI.updateSetting('clip', false); + display.set_viewport(false); + $D('noVNC_canvas').style.position = 'static'; + display.viewportChange(); + } + if (UI.getSetting('clip')) { + // If clipping, update clipping settings + $D('noVNC_canvas').style.position = 'absolute'; + pos = Util.getPosition($D('noVNC_canvas')); + new_w = window.innerWidth - pos.x; + new_h = window.innerHeight - pos.y; + display.set_viewport(true); + display.viewportChange(0, 0, new_w, new_h); + } +}, + +setViewDrag: function(drag) { + var vmb = $D('noVNC_view_drag_button'); + if (!UI.rfb) { return; } + + if (typeof(drag) === "undefined") { + // If not specified, then toggle + drag = !UI.rfb.get_viewportDrag(); + } + if (drag) { + vmb.style.backgroundColor = "black"; + vmb.style.color = "lightgray"; + UI.rfb.set_viewportDrag(true); + } else { + vmb.style.backgroundColor = ""; + vmb.style.color = ""; + UI.rfb.set_viewportDrag(false); + } +}, showKeyboard: function() { - //Get Current Scroll Position - var scrollx = - (document.all)?document.body.scrollLeft:window.pageXOffset; - var scrolly = - (document.all)?document.body.scrollTop:window.pageYOffset; - - //Stop browser zooming on textbox. - UI.zoomDisable(); - $D('keyboardinput').focus(); - scroll(scrollx,scrolly); - //Renable user zoom. - UI.zoomEnable(); + $D('keyboardinput').focus(); }, -zoomDisable: function() { - //Change viewport meta data to disable zooming. - UI.changeViewportMeta("user-scalable=0"); -}, - -zoomEnable: function(){ - //Change viewport meta data to enable user zooming. - UI.changeViewportMeta("user-scalable=1"); -}, changeViewportMeta: function (newattributes) { @@ -505,6 +574,7 @@ setBarPosition: function() { connectPanelbutton: function() { //Close connection settings if open if (UI.settingsOpen == true) { + UI.settingsApply(); UI.closeSettingsMenu(); } if (UI.clipboardOpen == true) { diff --git a/include/mobile.css b/tests/viewport.css similarity index 100% rename from include/mobile.css rename to tests/viewport.css diff --git a/tests/viewport.html b/tests/viewport.html index fbbf286d..1fb0a793 100644 --- a/tests/viewport.html +++ b/tests/viewport.html @@ -1,7 +1,7 @@