浏览代码

Viewport clip/drag for mobile/touchscreen devices.

API changes (forward compatible):

- Display: add 'viewport' conf option to turn on and off viewport
  mode.
- RFB: add 'viewportDrag' option to enable/disable viewport dragging
  mode.

Other:

- Add clip mode setting to default UI. For touch devices, clipping is
  forced on.
- Use CSS media queries to adjust visual elements based on screen
  size. Especially disconnected logo size/position and button text size.
- Catch page unload while connected and give a confirm dialog.
- Change mouse button selector to a single button that changes between
  ' ', 'L', 'M', 'R' when clicked (empty means mouse is just being
  moved and doesn't send clicks).
- include/ui.js:setViewClip() routine sets the clipping of the
  viewport to the current size of the viewport area (if clipping is
  enabled).
- include/ui.js:setViewDrag() toggles/enables/disables viewport
  dragging mode.
- Add several images for the UI and for Apple devices:
    - images/clipboard.png: clipboard menu icon
    - images/connect.png: connect menu icon
    - images/disconnect.png: disconnect button icon
    - images/keyboard.png: show keyboard button
    - images/move.png: viewport drag/move toggle button
    - images/settings.png: settings menu icon
    - images/screen_320x460.png: iOS app/desktop link start image
    - images/screen_57x57.png: iOS app icon
    - images/screen_700x700.png: full size noVNC image
Joel Martin 14 年之前
父节点
当前提交
a5df24b488
共有 18 个文件被更改,包括 399 次插入159 次删除
  1. 二进制
      images/clipboard.png
  2. 二进制
      images/connect.png
  3. 二进制
      images/disconnect.png
  4. 二进制
      images/keyboard.png
  5. 二进制
      images/move.png
  6. 二进制
      images/screen_320x460.png
  7. 二进制
      images/screen_57x57.png
  8. 二进制
      images/screen_700x700.png
  9. 二进制
      images/settings.png
  10. 96 25
      include/base.css
  11. 5 0
      include/black.css
  12. 5 0
      include/blue.css
  13. 30 5
      include/display.js
  14. 38 6
      include/rfb.js
  15. 147 77
      include/ui.js
  16. 0 0
      tests/viewport.css
  17. 16 4
      tests/viewport.html
  18. 62 42
      vnc.html

二进制
images/clipboard.png


二进制
images/connect.png


二进制
images/disconnect.png


二进制
images/keyboard.png


二进制
images/move.png


二进制
images/screen_320x460.png


二进制
images/screen_57x57.png


二进制
images/screen_700x700.png


二进制
images/settings.png


+ 96 - 25
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 {
+#noVNC_settings {
+  display:none; 
   margin-top:72px;
+  right:10px;
   position:fixed;
-  right:100px;
-  display:none; 
 }
 
 #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;
+  }
+}

+ 5 - 0
include/black.css

@@ -19,3 +19,8 @@
   background:#000;
   color:#fff;
 }
+
+#keyboardinput {
+  background-color:#000;
+}
+

+ 5 - 0
include/blue.css

@@ -20,3 +20,8 @@
   background:#04073d;
   color:#fff;
 }
+
+#keyboardinput {
+  background-color:#04073d;
+}
+

+ 30 - 5
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

+ 38 - 6
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());

+ 147 - 77
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,32 +462,67 @@ showClipboard: function() {
     }
 },
 
+setViewClip: function(clip) {
+    var display, cur_clip, pos, new_w, new_h;
 
-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();
+    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);
+    }
 },
 
-zoomDisable: function() {
-  //Change viewport meta data to disable zooming.
-  UI.changeViewportMeta("user-scalable=0");
+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);
+    }
 },
 
-zoomEnable: function(){
-  //Change viewport meta data to enable user zooming.
-  UI.changeViewportMeta("user-scalable=1");
+showKeyboard: function() {
+    $D('keyboardinput').focus();
 },
 
+
 changeViewportMeta: function (newattributes) {
 
     // First, get the array of meta-tag elements
@@ -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) {

+ 0 - 0
include/mobile.css → tests/viewport.css


+ 16 - 4
tests/viewport.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
     <head><title>Viewport Test</title>
-        <link rel="stylesheet" href="../include/mobile.css">
+        <link rel="stylesheet" href="viewport.css">
         <!--
         <meta name="apple-mobile-web-app-capable" content="yes" />
         <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
@@ -166,23 +166,35 @@
                     "," + (p.offsetHeight - padH) + "]");
             display.viewportChange(0, 0,
                 p.offsetWidth - padW, p.offsetHeight - padH);
+            /*
+            var pos, new_w, new_h;pos
+            pos = Util.getPosition($D('canvas').parentNode);
+            new_w = window.innerWidth - pos.x;
+            new_h = window.innerHeight - pos.y;
+            display.viewportChange(0, 0, new_w, new_h);
+            */
         }
 
         window.onload = function() {
             detectPad();
+
             display = new Display({'target': $D('canvas')});
             display.resize(1600, 1024);
-            //display.resize(800, 600);
+            display.set_viewport(true);
             ctx = display.get_context();
+
             mouse    = new Mouse({'target': $D('canvas'),
                                 'onMouseButton': mouseButton,
                                 'onMouseMove': mouseMove});
+            mouse.grab();
+
 
             Util.addEvent(window, 'resize', doResize);
-            //doResize();
+            // Shrink viewport for first resize call so that the
+            // scrollbars are disabled
+            display.viewportChange(0, 0, 10, 10);
             setTimeout(doResize, 1);
             setInterval(dirtyRedraw, 50);
-            mouse.grab();
 
             message("Display initialized");
         };

+ 62 - 42
vnc.html

@@ -15,11 +15,18 @@
                 Remove this if you use the .htaccess -->
     <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
 
-    <meta name="viewport" content="user-scalable=1" />
+    <!-- Apple iOS Safari settings -->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
     <meta name="apple-mobile-web-app-capable" content="yes" />
+    <meta names="apple-mobile-web-app-status-bar-style" content="black-translucent" />
+    <!-- App Start Icon  -->
+    <link rel="apple-touch-startup-image" href="images/screen_320x460.png" /> 
+    <!-- For iOS devices set the icon to use if user bookmarks app on their homescreen -->
+    <link rel="apple-touch-icon" href="images/screen_57x57.png">
     <!--
-    <meta name="viewport" content="width=device-width,height=device-height" />
+    <link rel="apple-touch-icon-precomposed" href="images/screen_57x57.png" /> 
     -->
+
     
     <!-- Stylesheets -->
     <link rel="stylesheet" href="include/base.css" />
@@ -28,13 +35,6 @@
 
     <!-- Google web fonts -->
     <link href='http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz|Nova+Square|Orbitron:400,500,700,900|Nova+Round|Nova+Mono|Nova+Slim|Nova+Oval|Nova+Flat|Nova+Cut' rel='stylesheet' type='text/css'>       
-    <!-- App Start Icon  -->
-    <link rel="apple-touch-startup-image" href="images/screen_640x435.png" /> 
-
-    <!-- For iOS devices set the icon to use if user bookmarks app on their homescreen -->
-    <link rel="apple-touch-icon" href="images/mobileicon.png">
-    <link rel="apple-touch-icon-precomposed" href="images/mobileicon.png" /> 
-
     <!--
     <script type='text/javascript'  
         src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
@@ -48,43 +48,60 @@
 <body>
     <div id="noVNC-control-bar">
         <!--noVNC Mobile Device only Buttons-->
-        <div id="noVNC_mobile_buttons" class="noVNC-buttons-left">
-            <nobr>
-                <span class="noVNC_mouse_buttons">
-                    <input type="button" class="noVNC_status_button"
-                        id="noVNC_mouse_button1" value="L"
-                        onclick="UI.setMouseButton(1);">
-                    <input type="button" class="noVNC_status_button"
-                        id="noVNC_mouse_button2" value="M"
-                        onclick="UI.setMouseButton(2);">
-                    <input type="button" class="noVNC_status_button"
-                        id="noVNC_mouse_button4" value="R"
-                        onclick="UI.setMouseButton(4);">
-                    <input type="button" id="showKeyboard"
-                        value="Keyboard" class="noVNC_status_button"
-                        onclick="UI.showKeyboard()"/>
-                </span>
-            </nobr>
+        <div class="noVNC-buttons-left">
+            <input type="image" src="images/move.png"
+                id="noVNC_view_drag_button" class="noVNC_status_button"
+                value="Move" title="Move/Drag Viewport"
+                onclick="UI.setViewDrag();">
+            <div id="noVNC_mobile_buttons">
+                <input type="button" class="noVNC_status_button"
+                    id="noVNC_mouse_button0" value=" "
+                    onclick="UI.setMouseButton(1);">
+                <input type="button" class="noVNC_status_button"
+                    id="noVNC_mouse_button1" value="L"
+                    onclick="UI.setMouseButton(2);">
+                <input type="button" class="noVNC_status_button"
+                    id="noVNC_mouse_button2" value="M"
+                    onclick="UI.setMouseButton(4);">
+                <input type="button" class="noVNC_status_button"
+                    id="noVNC_mouse_button4" value="R"
+                    onclick="UI.setMouseButton(0);">
+                <input type="image" src="images/keyboard.png"
+                    id="showKeyboard" class="noVNC_status_button" 
+                    value="Keyboard" title="Show Keyboard"
+                    onclick="UI.showKeyboard()"/>
+                <input type="text"
+                    id="keyboardinput" class="noVNC_status_button"
+                    onKeyDown="onKeyDown(event);"/>
+            </div>
         </div>
 
         <!--noVNC Buttons-->
         <div class="noVNC-buttons-right">
             <input type="button" class="noVNC_status_button"
                 value="CtrlAltDel" id="sendCtrlAltDelButton"
-                onclick="UI.sendCtrlAltDel();">
-            <input type="button" id="clipboardbutton" value="Clipboard"
-                onclick="UI.showClipboard();"/>
-            <input type="button" class="noVNC_status_button"
-                value="Settings" id="menuButton"
-                onclick="UI.clickSettingsMenu();">
-            <input type="button" id="connectPanelbutton"
-                value="Connection" class="noVNC_status_button"
+                onclick="UI.sendCtrlAltDel();" />
+            <input type="image" src="images/clipboard.png"
+                id="clipboardButton" class="noVNC_status_button"
+                value="Clipboard" title="Clipboard"
+                onclick="UI.showClipboard();" />
+            <input type="image" src="images/settings.png"
+                id="menuButton" class="noVNC_status_button"
+                value="Settings" title="Settings"
+                onclick="UI.clickSettingsMenu();" />
+            <input type="image" src="images/connect.png"
+                id="connectPanelbutton" class="noVNC_status_button"
+                value="Connect" title="Connect"
                 onclick="UI.connectPanelbutton()" />
+            <input type="image" src="images/disconnect.png"
+                id="disconnectButton" class="noVNC_status_button"
+                value="Disconnect" title="Disconnect"
+                onclick="UI.disconnect()" />
         </div>
 
         <!-- Clipboard Panel -->
         <div id="noVNC_clipboard" class="triangle-right top">
-            <textarea id="noVNC_clipboard_text" cols=88 rows=5
+            <textarea id="noVNC_clipboard_text" rows=5
                 onfocus="UI.displayBlur();" onblur="UI.displayFocus();"
                 onchange="UI.clipSend();">
             </textarea>
@@ -94,13 +111,14 @@
         </div>
 
         <!-- Settings Panel -->
-        <div id="noVNC_Settings" class="triangle-right top">
+        <div id="noVNC_settings" class="triangle-right top">
             <span id="noVNC_settings_menu" onmouseover="UI.displayBlur();"
                                            onmouseout="UI.displayFocus();">
                 <ul>
                     <li><input id="noVNC_encrypt" type="checkbox"> Encrypt</li>
                     <li><input id="noVNC_true_color" type="checkbox" checked> True Color</li>
                     <li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li>
+                    <li><input id="noVNC_clip" type="checkbox"> Clip to window</li>
                     <li><input id="noVNC_shared" type="checkbox"> Shared Mode</li>
                     <li><input id="noVNC_connectTimeout" type="input"> Connect Timeout (s)</li>
                     <hr>
@@ -143,13 +161,15 @@
                 <div id="noVNC_status">Loading</div>
         </div>
         
-        <!-- HTML5 Canvas -->
         <h1 id="noVNC_logo"><span>no</span><br />VNC</h1>
-        <canvas id="noVNC_canvas" width="640px" height="20px">
-                    Canvas not supported.
-        </canvas>
-        
-        <input id="keyboardinput" type="text" onKeyDown="onKeyDown(event);"/>
+
+        <!-- HTML5 Canvas -->
+        <div id="noVNC_container">
+            <canvas id="noVNC_canvas" width="640px" height="20px">
+                        Canvas not supported.
+            </canvas>
+        </div>
+
     </div>
   
     <script>