Эх сурвалжийг харах

API change: Mouse/kbd handling to include/input.js

API change: for intergrators that explicitly include the Javascript
files (that do not use include/vnc.js)js, include/input.js is a new
file that must also be included.

The mouse and keyboard handling could be useful on its own so split it
out into a Keyboard and Mouse class in include/input.js.

This refactoring is preparation to deal with issue #21 - non-US
keyboard layouts.
Joel Martin 14 жил өмнө
parent
commit
d3796c149e

+ 8 - 283
include/canvas.js

@@ -7,7 +7,7 @@
  */
 
 /*jslint browser: true, white: false, bitwise: false */
-/*global window, Util, Base64, changeCursor, getKeysym */
+/*global Util, Base64, changeCursor */
 
 function Canvas(conf) {
     "use strict";
@@ -23,10 +23,6 @@ var that           = {},         // Public API interface
 
     c_prevStyle    = "",
 
-    c_keyPress     = null,
-    c_mouseButton  = null,
-    c_mouseMove    = null,
-
     c_webkit_bug   = false,
     c_flush_timer  = null;
 
@@ -91,7 +87,7 @@ that.get_height = function() {
 function constructor() {
     Util.Debug(">> Canvas.init");
 
-    var c, ctx, func, origfunc, imgTest, tval, i, curDat, curSave,
+    var c, ctx, func, imgTest, tval, i, curDat, curSave,
         has_imageData = false, UE = Util.Engine;
 
     if (! conf.target) { throw("target must be set"); }
@@ -214,121 +210,6 @@ function constructor() {
     return that ;
 }
 
-function onMouseButton(e, down) {
-    var evt, pos, bmask;
-    if (! conf.focused) {
-        return true;
-    }
-    evt = (e ? e : window.event);
-    pos = Util.getEventPosition(e, conf.target, conf.scale);
-    if (evt.which) {
-        /* everything except IE */
-        bmask = 1 << evt.button;
-    } else {
-        /* IE including 9 */
-        bmask = (evt.button & 0x1) +      // Left
-                (evt.button & 0x2) * 2 +  // Right
-                (evt.button & 0x4) / 2;   // Middle
-    }
-    //Util.Debug("mouse " + pos.x + "," + pos.y + " down: " + down +
-    //           " bmask: " + bmask + "(evt.button: " + evt.button + ")");
-    if (c_mouseButton) {
-        c_mouseButton(pos.x, pos.y, down, bmask);
-    }
-    Util.stopEvent(e);
-    return false;
-}
-
-function onMouseDown(e) {
-    onMouseButton(e, 1);
-}
-
-function onMouseUp(e) {
-    onMouseButton(e, 0);
-}
-
-function onMouseWheel(e) {
-    var evt, pos, bmask, wheelData;
-    evt = (e ? e : window.event);
-    pos = Util.getEventPosition(e, conf.target, conf.scale);
-    wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
-    if (wheelData > 0) {
-        bmask = 1 << 3;
-    } else {
-        bmask = 1 << 4;
-    }
-    //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
-    if (c_mouseButton) {
-        c_mouseButton(pos.x, pos.y, 1, bmask);
-        c_mouseButton(pos.x, pos.y, 0, bmask);
-    }
-    Util.stopEvent(e);
-    return false;
-}
-
-function onMouseMove(e) {
-    var evt, pos;
-    evt = (e ? e : window.event);
-    pos = Util.getEventPosition(e, conf.target, conf.scale);
-    //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
-    if (c_mouseMove) {
-        c_mouseMove(pos.x, pos.y);
-    }
-}
-
-function onKeyDown(e) {
-    //Util.Debug("keydown: " + getKeysym(e));
-    if (! conf.focused) {
-        return true;
-    }
-    if (c_keyPress) {
-        c_keyPress(getKeysym(e), 1, e.ctrlKey, e.shiftKey, e.altKey);
-    }
-    Util.stopEvent(e);
-    return false;
-}
-
-function onKeyUp(e) {
-    //Util.Debug("keyup: " + getKeysym(e));
-    if (! conf.focused) {
-        return true;
-    }
-    if (c_keyPress) {
-        c_keyPress(getKeysym(e), 0, e.ctrlKey, e.shiftKey, e.altKey);
-    }
-    Util.stopEvent(e);
-    return false;
-}
-
-function onKeyPress(e) {
-    //Util.Debug("keypress: " + e.charCode);
-    if (! conf.focused) {
-        return true;
-    }
-    // Stop keypress events. Necessary for Opera because stopping
-    // keydown and keyup events still results in a keypress event.
-    Util.stopEvent(e);
-    return false;
-}
-
-function onMouseDisable(e) {
-    var evt, pos;
-    if (! conf.focused) {
-        return true;
-    }
-    evt = (e ? e : window.event);
-    pos = Util.getEventPosition(e, conf.target, conf.scale);
-    /* Stop propagation if inside canvas area */
-    if ((pos.x >= 0) && (pos.y >= 0) &&
-        (pos.x < c_width) && (pos.y < c_height)) {
-        //Util.Debug("mouse event disabled");
-        Util.stopEvent(e);
-        return false;
-    }
-    //Util.Debug("mouse event not disabled");
-    return true;
-}
-
 //
 // Public API interface functions
 //
@@ -337,52 +218,6 @@ that.getContext = function () {
     return conf.ctx;
 };
 
-that.start = function(keyPressFunc, mouseButtonFunc, mouseMoveFunc) {
-    var c;
-    Util.Debug(">> Canvas.start");
-
-    c = conf.target;
-    c_keyPress = keyPressFunc || null;
-    c_mouseButton = mouseButtonFunc || null;
-    c_mouseMove = mouseMoveFunc || null;
-
-    Util.addEvent(conf.focusContainer, 'keydown', onKeyDown);
-    Util.addEvent(conf.focusContainer, 'keyup', onKeyUp);
-    Util.addEvent(conf.focusContainer, 'keypress', onKeyPress);
-    Util.addEvent(c, 'mousedown', onMouseDown);
-    Util.addEvent(c, 'mouseup', onMouseUp);
-    Util.addEvent(c, 'mousemove', onMouseMove);
-    Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
-            onMouseWheel);
-
-    /* Work around right and middle click browser behaviors */
-    Util.addEvent(conf.focusContainer, 'click', onMouseDisable);
-    Util.addEvent(conf.focusContainer.body, 'contextmenu', onMouseDisable);
-
-    Util.Debug("<< Canvas.start");
-};
-
-that.stop = function() {
-    var c = conf.target;
-    Util.removeEvent(conf.focusContainer, 'keydown', onKeyDown);
-    Util.removeEvent(conf.focusContainer, 'keyup', onKeyUp);
-    Util.removeEvent(conf.focusContainer, 'keypress', onKeyPress);
-    Util.removeEvent(c, 'mousedown', onMouseDown);
-    Util.removeEvent(c, 'mouseup', onMouseUp);
-    Util.removeEvent(c, 'mousemove', onMouseMove);
-    Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
-            onMouseWheel);
-
-    /* Work around right and middle click browser behaviors */
-    Util.removeEvent(conf.focusContainer, 'click', onMouseDisable);
-    Util.removeEvent(conf.focusContainer.body, 'contextmenu', onMouseDisable);
-
-    // Turn off cursor rendering
-    if (conf.cursor_uri) {
-        c.style.cursor = "default";
-    }
-};
-
 that.rescale = function(factor) {
     var c, tp, x, y, 
         properties = ['transform', 'WebkitTransform', 'MozTransform', null];
@@ -480,7 +315,7 @@ that.copyImage = function(old_x, old_y, new_x, new_y, width, height) {
  *   gecko, Javascript array handling is much slower.
  */
 that.getTile = function(x, y, width, height, color) {
-    var img, data = [], p, rgb, red, green, blue, j, i;
+    var img, data = [], rgb, red, green, blue, i;
     img = {'x': x, 'y': y, 'width': width, 'height': height,
            'data': data};
     if (conf.prefer_js) {
@@ -534,9 +369,8 @@ that.setSubTile = function(img, x, y, w, h, color) {
 that.putTile = function(img) {
     if (conf.prefer_js) {
         that.rgbxImage(img.x, img.y, img.width, img.height, img.data, 0);
-    } else {
-        // No-op, under gecko already done by setSubTile
     }
+    // else: No-op, under gecko already done by setSubTile
 };
 
 that.imageDataGet = function(width, height) {
@@ -628,6 +462,10 @@ that.changeCursor = function(pixels, mask, hotx, hoty, w, h) {
     }
 };
 
+that.defaultCursor = function() {
+        conf.target.style.cursor = "default";
+};
+
 return constructor();  // Return the public API interface
 
 }  // End of Canvas()
@@ -731,116 +569,3 @@ function changeCursor(target, pixels, mask, hotx, hoty, w, h, cmap) {
     target.style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default";
     //Util.Debug("<< changeCursor, cur.length: " + cur.length);
 }
-
-
-
-/* Translate DOM key down/up event to keysym value */
-function getKeysym(e) {
-    var evt, keysym;
-    evt = (e ? e : window.event);
-
-    /* Remap modifier and special keys */
-    switch ( evt.keyCode ) {
-        case 8         : keysym = 0xFF08; break; // BACKSPACE
-        case 9         : keysym = 0xFF09; break; // TAB
-        case 13        : keysym = 0xFF0D; break; // ENTER
-        case 27        : keysym = 0xFF1B; break; // ESCAPE
-        case 45        : keysym = 0xFF63; break; // INSERT
-        case 46        : keysym = 0xFFFF; break; // DELETE
-        case 36        : keysym = 0xFF50; break; // HOME
-        case 35        : keysym = 0xFF57; break; // END
-        case 33        : keysym = 0xFF55; break; // PAGE_UP
-        case 34        : keysym = 0xFF56; break; // PAGE_DOWN
-        case 37        : keysym = 0xFF51; break; // LEFT
-        case 38        : keysym = 0xFF52; break; // UP
-        case 39        : keysym = 0xFF53; break; // RIGHT
-        case 40        : keysym = 0xFF54; break; // DOWN
-        case 112       : keysym = 0xFFBE; break; // F1
-        case 113       : keysym = 0xFFBF; break; // F2
-        case 114       : keysym = 0xFFC0; break; // F3
-        case 115       : keysym = 0xFFC1; break; // F4
-        case 116       : keysym = 0xFFC2; break; // F5
-        case 117       : keysym = 0xFFC3; break; // F6
-        case 118       : keysym = 0xFFC4; break; // F7
-        case 119       : keysym = 0xFFC5; break; // F8
-        case 120       : keysym = 0xFFC6; break; // F9
-        case 121       : keysym = 0xFFC7; break; // F10
-        case 122       : keysym = 0xFFC8; break; // F11
-        case 123       : keysym = 0xFFC9; break; // F12
-        case 16        : keysym = 0xFFE1; break; // SHIFT
-        case 17        : keysym = 0xFFE3; break; // CONTROL
-        //case 18        : keysym = 0xFFE7; break; // Left Meta (Mac Option)
-        case 18        : keysym = 0xFFE9; break; // Left ALT (Mac Command)
-        default        : keysym = evt.keyCode; break;
-    }
-
-    /* Remap symbols */
-    switch (keysym) {
-        case 186       : keysym = 59; break; // ;  (IE)
-        case 187       : keysym = 61; break; // =  (IE)
-        case 188       : keysym = 44; break; // ,  (Mozilla, IE)
-        case 109       :                     // -  (Mozilla, Opera)
-            if (Util.Engine.gecko || Util.Engine.presto) {
-                         keysym = 45; }
-                                      break;
-        case 189       : keysym = 45; break; // -  (IE)
-        case 190       : keysym = 46; break; // .  (Mozilla, IE)
-        case 191       : keysym = 47; break; // /  (Mozilla, IE)
-        case 192       : keysym = 96; break; // `  (Mozilla, IE)
-        case 219       : keysym = 91; break; // [  (Mozilla, IE)
-        case 220       : keysym = 92; break; // \  (Mozilla, IE)
-        case 221       : keysym = 93; break; // ]  (Mozilla, IE)
-        case 222       : keysym = 39; break; // '  (Mozilla, IE)
-    }
-    
-    /* Remap shifted and unshifted keys */
-    if (!!evt.shiftKey) {
-        switch (keysym) {
-            case 48        : keysym = 41 ; break; // )  (shifted 0)
-            case 49        : keysym = 33 ; break; // !  (shifted 1)
-            case 50        : keysym = 64 ; break; // @  (shifted 2)
-            case 51        : keysym = 35 ; break; // #  (shifted 3)
-            case 52        : keysym = 36 ; break; // $  (shifted 4)
-            case 53        : keysym = 37 ; break; // %  (shifted 5)
-            case 54        : keysym = 94 ; break; // ^  (shifted 6)
-            case 55        : keysym = 38 ; break; // &  (shifted 7)
-            case 56        : keysym = 42 ; break; // *  (shifted 8)
-            case 57        : keysym = 40 ; break; // (  (shifted 9)
-
-            case 59        : keysym = 58 ; break; // :  (shifted `)
-            case 61        : keysym = 43 ; break; // +  (shifted ;)
-            case 44        : keysym = 60 ; break; // <  (shifted ,)
-            case 45        : keysym = 95 ; break; // _  (shifted -)
-            case 46        : keysym = 62 ; break; // >  (shifted .)
-            case 47        : keysym = 63 ; break; // ?  (shifted /)
-            case 96        : keysym = 126; break; // ~  (shifted `)
-            case 91        : keysym = 123; break; // {  (shifted [)
-            case 92        : keysym = 124; break; // |  (shifted \)
-            case 93        : keysym = 125; break; // }  (shifted ])
-            case 39        : keysym = 34 ; break; // "  (shifted ')
-        }
-    } else if ((keysym >= 65) && (keysym <=90)) {
-        /* Remap unshifted A-Z */
-        keysym += 32;
-    } else if (evt.keyLocation === 3) {
-        // numpad keys
-        switch (keysym) {
-            case 96 : keysym = 48; break; // 0
-            case 97 : keysym = 49; break; // 1
-            case 98 : keysym = 50; break; // 2
-            case 99 : keysym = 51; break; // 3
-            case 100: keysym = 52; break; // 4
-            case 101: keysym = 53; break; // 5
-            case 102: keysym = 54; break; // 6
-            case 103: keysym = 55; break; // 7
-            case 104: keysym = 56; break; // 8
-            case 105: keysym = 57; break; // 9
-            case 109: keysym = 45; break; // -
-            case 110: keysym = 46; break; // .
-            case 111: keysym = 47; break; // /
-        }
-    }
-
-    return keysym;
-}
-

+ 378 - 0
include/input.js

@@ -0,0 +1,378 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-3 (see LICENSE.txt)
+ */
+
+/*jslint browser: true, white: false, bitwise: false */
+/*global window, Util */
+
+
+//
+// Keyboard event handler
+//
+
+function Keyboard(conf) {
+    "use strict";
+
+conf               = conf || {}; // Configuration
+var that           = {};         // Public API interface
+
+
+// Configuration settings
+function cdef(v, type, defval, desc) {
+    Util.conf_default(conf, that, v, type, defval, desc); }
+
+// Capability settings, default can be overridden
+cdef('target',         'dom',  document, 'DOM element that grabs keyboard input');
+cdef('focused',        'bool', true, 'Capture and send key strokes');
+
+cdef('keyPress',       'func', null, 'Handler for key press/release');
+
+that.set_target = function () { throw("target cannot be changed"); }
+
+// 
+// Private functions
+//
+
+function onKeyDown(e) {
+    //Util.Debug("keydown: " + that.getKeysym(e));
+    if (! conf.focused) {
+        return true;
+    }
+    if (conf.keyPress) {
+        conf.keyPress(that.getKeysym(e), 1, e.ctrlKey, e.shiftKey, e.altKey);
+    }
+    Util.stopEvent(e);
+    return false;
+}
+
+function onKeyUp(e) {
+    //Util.Debug("keyup: " + that.getKeysym(e));
+    if (! conf.focused) {
+        return true;
+    }
+    if (conf.keyPress) {
+        conf.keyPress(that.getKeysym(e), 0, e.ctrlKey, e.shiftKey, e.altKey);
+    }
+    Util.stopEvent(e);
+    return false;
+}
+
+function onKeyPress(e) {
+    //Util.Debug("keypress: " + e.charCode);
+    if (! conf.focused) {
+        return true;
+    }
+    // Stop keypress events. Necessary for Opera because stopping
+    // keydown and keyup events still results in a keypress event.
+    Util.stopEvent(e);
+    return false;
+}
+
+//
+// Public API interface functions
+//
+
+/* Translate DOM key down/up event to keysym value */
+that.getKeysym = function getKeysym(e) {
+    var evt, keysym;
+    evt = (e ? e : window.event);
+    /*
+    Util.Debug(">> getKeysym - keyCode: " + evt.keyCode + ", which: " + evt.which +
+            ", charCode: " + evt.charCode + ", keyIdentifier: " + evt.keyIdentifier +
+            ", altKey: " + evt.altKey + ", ctrlKey: " + evt.ctrlKey +
+            ", shiftKey: " + evt.shiftKey + ", metaKey: " + evt.metaKey +
+            ", type: " + evt.type + ", keyLocation: " + evt.keyLocation);
+    */
+
+    /* Remap modifier and special keys */
+    switch ( evt.keyCode ) {
+        case 8         : keysym = 0xFF08; break; // BACKSPACE
+        case 9         : keysym = 0xFF09; break; // TAB
+        case 13        : keysym = 0xFF0D; break; // ENTER
+        case 27        : keysym = 0xFF1B; break; // ESCAPE
+        case 45        : keysym = 0xFF63; break; // INSERT
+        case 46        : keysym = 0xFFFF; break; // DELETE
+        case 36        : keysym = 0xFF50; break; // HOME
+        case 35        : keysym = 0xFF57; break; // END
+        case 33        : keysym = 0xFF55; break; // PAGE_UP
+        case 34        : keysym = 0xFF56; break; // PAGE_DOWN
+        case 37        : keysym = 0xFF51; break; // LEFT
+        case 38        : keysym = 0xFF52; break; // UP
+        case 39        : keysym = 0xFF53; break; // RIGHT
+        case 40        : keysym = 0xFF54; break; // DOWN
+        case 112       : keysym = 0xFFBE; break; // F1
+        case 113       : keysym = 0xFFBF; break; // F2
+        case 114       : keysym = 0xFFC0; break; // F3
+        case 115       : keysym = 0xFFC1; break; // F4
+        case 116       : keysym = 0xFFC2; break; // F5
+        case 117       : keysym = 0xFFC3; break; // F6
+        case 118       : keysym = 0xFFC4; break; // F7
+        case 119       : keysym = 0xFFC5; break; // F8
+        case 120       : keysym = 0xFFC6; break; // F9
+        case 121       : keysym = 0xFFC7; break; // F10
+        case 122       : keysym = 0xFFC8; break; // F11
+        case 123       : keysym = 0xFFC9; break; // F12
+        case 16        : keysym = 0xFFE1; break; // SHIFT
+        case 17        : keysym = 0xFFE3; break; // CONTROL
+        //case 18        : keysym = 0xFFE7; break; // Left Meta (Mac Option)
+        case 18        : keysym = 0xFFE9; break; // Left ALT (Mac Command)
+        default        : keysym = evt.keyCode; break;
+    }
+
+    /* Remap symbols */
+    switch (keysym) {
+        case 186       : keysym = 59; break; // ;  (IE)
+        case 187       : keysym = 61; break; // =  (IE)
+        case 188       : keysym = 44; break; // ,  (Mozilla, IE)
+        case 109       :                     // -  (Mozilla, Opera)
+            if (Util.Engine.gecko || Util.Engine.presto) {
+                         keysym = 45; }
+                                      break;
+        case 189       : keysym = 45; break; // -  (IE)
+        case 190       : keysym = 46; break; // .  (Mozilla, IE)
+        case 191       : keysym = 47; break; // /  (Mozilla, IE)
+        case 192       : keysym = 96; break; // `  (Mozilla, IE)
+        case 219       : keysym = 91; break; // [  (Mozilla, IE)
+        case 220       : keysym = 92; break; // \  (Mozilla, IE)
+        case 221       : keysym = 93; break; // ]  (Mozilla, IE)
+        case 222       : keysym = 39; break; // '  (Mozilla, IE)
+    }
+    
+    /* Remap shifted and unshifted keys */
+    if (!!evt.shiftKey) {
+        switch (keysym) {
+            case 48        : keysym = 41 ; break; // )  (shifted 0)
+            case 49        : keysym = 33 ; break; // !  (shifted 1)
+            case 50        : keysym = 64 ; break; // @  (shifted 2)
+            case 51        : keysym = 35 ; break; // #  (shifted 3)
+            case 52        : keysym = 36 ; break; // $  (shifted 4)
+            case 53        : keysym = 37 ; break; // %  (shifted 5)
+            case 54        : keysym = 94 ; break; // ^  (shifted 6)
+            case 55        : keysym = 38 ; break; // &  (shifted 7)
+            case 56        : keysym = 42 ; break; // *  (shifted 8)
+            case 57        : keysym = 40 ; break; // (  (shifted 9)
+
+            case 59        : keysym = 58 ; break; // :  (shifted `)
+            case 61        : keysym = 43 ; break; // +  (shifted ;)
+            case 44        : keysym = 60 ; break; // <  (shifted ,)
+            case 45        : keysym = 95 ; break; // _  (shifted -)
+            case 46        : keysym = 62 ; break; // >  (shifted .)
+            case 47        : keysym = 63 ; break; // ?  (shifted /)
+            case 96        : keysym = 126; break; // ~  (shifted `)
+            case 91        : keysym = 123; break; // {  (shifted [)
+            case 92        : keysym = 124; break; // |  (shifted \)
+            case 93        : keysym = 125; break; // }  (shifted ])
+            case 39        : keysym = 34 ; break; // "  (shifted ')
+        }
+    } else if ((keysym >= 65) && (keysym <=90)) {
+        /* Remap unshifted A-Z */
+        keysym += 32;
+    } else if (evt.keyLocation === 3) {
+        // numpad keys
+        switch (keysym) {
+            case 96 : keysym = 48; break; // 0
+            case 97 : keysym = 49; break; // 1
+            case 98 : keysym = 50; break; // 2
+            case 99 : keysym = 51; break; // 3
+            case 100: keysym = 52; break; // 4
+            case 101: keysym = 53; break; // 5
+            case 102: keysym = 54; break; // 6
+            case 103: keysym = 55; break; // 7
+            case 104: keysym = 56; break; // 8
+            case 105: keysym = 57; break; // 9
+            case 109: keysym = 45; break; // -
+            case 110: keysym = 46; break; // .
+            case 111: keysym = 47; break; // /
+        }
+    }
+
+    return keysym;
+};
+
+
+that.grab = function() {
+    //Util.Debug(">> Keyboard.grab");
+    var c = conf.target;
+
+    Util.addEvent(c, 'keydown', onKeyDown);
+    Util.addEvent(c, 'keyup', onKeyUp);
+    Util.addEvent(c, 'keypress', onKeyPress);
+
+    //Util.Debug("<< Keyboard.grab");
+};
+
+that.ungrab = function() {
+    //Util.Debug(">> Keyboard.ungrab");
+    var c = conf.target;
+
+    Util.removeEvent(c, 'keydown', onKeyDown);
+    Util.removeEvent(c, 'keyup', onKeyUp);
+    Util.removeEvent(c, 'keypress', onKeyPress);
+
+    //Util.Debug(">> Keyboard.ungrab");
+};
+
+return that;  // Return the public API interface
+
+}  // End of Keyboard()
+
+
+//
+// Mouse event handler
+//
+
+function Mouse(conf) {
+    "use strict";
+
+conf               = conf || {}; // Configuration
+var that           = {};         // Public API interface
+
+
+// Configuration settings
+function cdef(v, type, defval, desc) {
+    Util.conf_default(conf, that, v, type, defval, desc); }
+
+// Capability settings, default can be overridden
+cdef('target',         'dom',  document, 'DOM element that grabs mouse input');
+cdef('focused',        'bool', true, 'Capture and send mouse clicks/movement');
+
+cdef('mouseButton',    'func', null, 'Handler for mouse button click/release');
+cdef('mouseMove',      'func', null, 'Handler for mouse movement');
+
+that.set_target = function () { throw("target cannot be changed"); }
+
+// 
+// Private functions
+//
+
+function onMouseButton(e, down) {
+    var evt, pos, bmask;
+    if (! conf.focused) {
+        return true;
+    }
+    evt = (e ? e : window.event);
+    pos = Util.getEventPosition(e, conf.target, conf.scale);
+    if (evt.which) {
+        /* everything except IE */
+        bmask = 1 << evt.button;
+    } else {
+        /* IE including 9 */
+        bmask = (evt.button & 0x1) +      // Left
+                (evt.button & 0x2) * 2 +  // Right
+                (evt.button & 0x4) / 2;   // Middle
+    }
+    //Util.Debug("mouse " + pos.x + "," + pos.y + " down: " + down +
+    //           " bmask: " + bmask + "(evt.button: " + evt.button + ")");
+    if (conf.mouseButton) {
+        conf.mouseButton(pos.x, pos.y, down, bmask);
+    }
+    Util.stopEvent(e);
+    return false;
+}
+
+function onMouseDown(e) {
+    onMouseButton(e, 1);
+}
+
+function onMouseUp(e) {
+    onMouseButton(e, 0);
+}
+
+function onMouseWheel(e) {
+    var evt, pos, bmask, wheelData;
+    if (! conf.focused) {
+        return true;
+    }
+    evt = (e ? e : window.event);
+    pos = Util.getEventPosition(e, conf.target, conf.scale);
+    wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
+    if (wheelData > 0) {
+        bmask = 1 << 3;
+    } else {
+        bmask = 1 << 4;
+    }
+    //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
+    if (conf.mouseButton) {
+        conf.mouseButton(pos.x, pos.y, 1, bmask);
+        conf.mouseButton(pos.x, pos.y, 0, bmask);
+    }
+    Util.stopEvent(e);
+    return false;
+}
+
+function onMouseMove(e) {
+    var evt, pos;
+    if (! conf.focused) {
+        return true;
+    }
+    evt = (e ? e : window.event);
+    pos = Util.getEventPosition(e, conf.target, conf.scale);
+    //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
+    if (conf.mouseMove) {
+        conf.mouseMove(pos.x, pos.y);
+    }
+}
+
+function onMouseDisable(e) {
+    var evt, pos;
+    if (! conf.focused) {
+        return true;
+    }
+    evt = (e ? e : window.event);
+    pos = Util.getEventPosition(e, conf.target, conf.scale);
+    /* Stop propagation if inside canvas area */
+    if ((pos.x >= 0) && (pos.y >= 0) &&
+        (pos.x < conf.target.offsetWidth) &&
+        (pos.y < conf.target.offsetHeight)) {
+        //Util.Debug("mouse event disabled");
+        Util.stopEvent(e);
+        return false;
+    }
+    //Util.Debug("mouse event not disabled");
+    return true;
+}
+
+//
+// Public API interface functions
+//
+
+that.grab = function() {
+    //Util.Debug(">> Mouse.grab");
+    var c = conf.target;
+
+    Util.addEvent(c, 'mousedown', onMouseDown);
+    Util.addEvent(c, 'mouseup', onMouseUp);
+    Util.addEvent(c, 'mousemove', onMouseMove);
+    Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
+            onMouseWheel);
+
+    /* Work around right and middle click browser behaviors */
+    Util.addEvent(document, 'click', onMouseDisable);
+    Util.addEvent(document.body, 'contextmenu', onMouseDisable);
+
+    //Util.Debug("<< Mouse.grab");
+};
+
+that.ungrab = function() {
+    //Util.Debug(">> Mouse.ungrab");
+    var c = conf.target;
+
+    Util.removeEvent(c, 'mousedown', onMouseDown);
+    Util.removeEvent(c, 'mouseup', onMouseUp);
+    Util.removeEvent(c, 'mousemove', onMouseMove);
+    Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
+            onMouseWheel);
+
+    /* Work around right and middle click browser behaviors */
+    Util.removeEvent(document, 'click', onMouseDisable);
+    Util.removeEvent(document.body, 'contextmenu', onMouseDisable);
+
+    //Util.Debug(">> Mouse.ungrab");
+};
+
+return that;  // Return the public API interface
+
+}  // End of Mouse()
+

+ 22 - 12
include/rfb.js

@@ -7,7 +7,7 @@
  */
 
 /*jslint white: false, browser: true, bitwise: false, plusplus: false */
-/*global window, Util, Canvas, Websock, Websock_native, Base64, DES, noVNC_logo */
+/*global window, Util, Canvas, Keyboard, Mouse, Websock, Websock_native, Base64, DES, noVNC_logo */
 
 
 function RFB(conf) {
@@ -24,6 +24,7 @@ var that           = {},         // Public API interface
     keyEvent, pointerEvent, clientCutText,
 
     extract_data_uri, scan_tight_imgQ,
+    keyPress, mouseButton, mouseMove,
 
     checkEvents,  // Overridable for testing
 
@@ -64,6 +65,8 @@ var that           = {},         // Public API interface
 
     ws             = null,   // Websock object
     canvas         = null,   // Canvas object
+    keyboard       = null,   // Keyboard input handler object
+    mouse          = null,   // Mouse input handler object
     sendTimer      = null,   // Send Queue check timer
     connTimer      = null,   // connection timer
     disconnTimer   = null,   // disconnection timer
@@ -182,10 +185,14 @@ function constructor() {
         encNames[encodings[i][1]] = encodings[i][0];
         encStats[encodings[i][1]] = [0, 0];
     }
-    // Initialize canvas
+    // Initialize canvas, mouse and keyboard
     try {
-        canvas = new Canvas({'target': conf.target,
-                             'focusContainer': conf.focusContainer});
+        canvas   = new Canvas({'target': conf.target});
+        keyboard = new Keyboard({'target': conf.focusContainer,
+                                 'keyPress': keyPress});
+        mouse    = new Mouse({'target': conf.target,
+                              'mouseButton': mouseButton,
+                              'mouseMove': mouseMove});
     } catch (exc) {
         Util.Error("Canvas exception: " + exc);
         updateState('fatal', "No working Canvas");
@@ -349,7 +356,9 @@ updateState = function(state, statusMsg) {
         }
 
         if (canvas && canvas.getContext()) {
-            canvas.stop();
+            keyboard.ungrab();
+            mouse.ungrab();
+            canvas.defaultCursor();
             if (Util.get_logging() !== 'debug') {
                 canvas.clear();
             }
@@ -543,14 +552,14 @@ checkEvents = function() {
     setTimeout(checkEvents, conf.check_rate);
 };
 
-function keyPress(keysym, down) {
+keyPress = function(keysym, down) {
     var arr;
     arr = keyEvent(keysym, down);
     arr = arr.concat(fbUpdateRequest(1));
     ws.send(arr);
-}
+};
 
-function mouseButton(x, y, down, bmask) {
+mouseButton = function(x, y, down, bmask) {
     if (down) {
         mouse_buttonMask |= bmask;
     } else {
@@ -558,12 +567,12 @@ function mouseButton(x, y, down, bmask) {
     }
     mouse_arr = mouse_arr.concat( pointerEvent(x, y) );
     flushClient();
-}
+};
 
-function mouseMove(x, y) {
+mouseMove = function(x, y) {
     //Util.Debug('>> mouseMove ' + x + "," + y);
     mouse_arr = mouse_arr.concat( pointerEvent(x, y) );
-}
+};
 
 
 //
@@ -744,7 +753,8 @@ init_msg = function() {
         fb_name = ws.rQshiftStr(name_length);
 
         canvas.resize(fb_width, fb_height, conf.true_color);
-        canvas.start(keyPress, mouseButton, mouseMove);
+        keyboard.grab();
+        mouse.grab();
 
         if (conf.true_color) {
             fb_Bpp           = 4;

+ 5 - 1
include/util.js

@@ -78,7 +78,7 @@ Util.init_logging = function (level) {
 };
 Util.get_logging = function () {
     return Util._log_level;
-}
+};
 // Initialize logging level
 Util.init_logging();
 
@@ -104,6 +104,10 @@ Util.conf_default = function(cfg, api, v, type, defval, desc) {
                     }
                 } else if (type in {'integer':1, 'int':1}) {
                     val = parseInt(val, 10);
+                } else if (type === 'func') {
+                    if (!val) {
+                        val = function () {};
+                    }
                 }
                 cfg[v] = val;
             };

+ 1 - 0
include/vnc.js

@@ -33,6 +33,7 @@ function get_INCLUDE_URI() {
     extra += start + "base64.js" + end;
     extra += start + "websock.js" + end;
     extra += start + "des.js" + end;
+    extra += start + "input.js" + end;
     extra += start + "canvas.js" + end;
     extra += start + "rfb.js" + end;
 

+ 8 - 1
tests/input.html

@@ -22,6 +22,7 @@
     <script src="../include/util.js"></script>
     <script src="../include/webutil.js"></script> 
     <script src="../include/base64.js"></script>
+    <script src="../include/input.js"></script> 
     <script src="../include/canvas.js"></script>
     <script>
         var msg_cnt = 0;
@@ -55,8 +56,14 @@
 
         window.onload = function() {
             var canvas = new Canvas({'target' : $D('canvas')});
+            keyboard = new Keyboard({'target': document,
+                                    'keyPress': keyPress});
+            mouse    = new Mouse({'target': $D('canvas'),
+                                'mouseButton': mouseButton,
+                                'mouseMove': mouseMove});
             canvas.resize(width, height, true);
-            canvas.start(keyPress, mouseButton, mouseMove);
+            keyboard.grab();
+            mouse.grab();
             message("Canvas initialized");
         }
     </script>

+ 6 - 11
tests/keyboard.html

@@ -41,24 +41,22 @@
             msg = "Dn: key:" + e.keyCode + " char:" + e.charCode + " which:" + e.which + " id:" + e.keyIdentifier + " ksym:" + getKeysym(evt) + " alt:" + e.altKey + " shift:" + e.shiftKey + " ctrl:" + e.ctrlKey;
             message(msg);
 
-            /*
             if (e.stopPropagation) { e.stopPropagation(); }
             else                   { e.cancelBubble = true; }
 
+            /*
             if (e.preventDefault)  { Util.Debug("here1"); e.preventDefault(); }
             else                   { Util.Debug("here2"); e.returnValue = false; }
-
-            return false;
             */
 
+            return false;
         }
 
-        function keyUp(evt) {
+        function keyPress(evt) {
             var e = (evt ? evt : window.event);
-            msg = "Up: key:" + e.keyCode + " char:" + e.charCode + " which:" + e.which + " id:" + e.keyIdentifier + " ksym:" + getKeysym(evt) + " alt:" + e.altKey + " shift:" + e.shiftKey + " ctrl:" + e.ctrlKey;
+            msg = "Pr: key:" + e.keyCode + " char:" + e.charCode + " which:" + e.which + " id:" + e.keyIdentifier + " ksym:" + getKeysym(evt) + " alt:" + e.altKey + " shift:" + e.shiftKey + " ctrl:" + e.ctrlKey;
             message(msg);
 
-            /*
             if (e.stopPropagation) { e.stopPropagation(); }
             else                   { e.cancelBubble = true; }
 
@@ -66,13 +64,11 @@
             else                   { e.returnValue = false; }
 
             return false;
-            */
-
         }
 
-        function keyPress(evt) {
+        function keyUp(evt) {
             var e = (evt ? evt : window.event);
-            msg = "Pr: key:" + e.keyCode + " char:" + e.charCode + " which:" + e.which + " id:" + e.keyIdentifier + " ksym:" + getKeysym(evt) + " alt:" + e.altKey + " shift:" + e.shiftKey + " ctrl:" + e.ctrlKey;
+            msg = "Up: key:" + e.keyCode + " char:" + e.charCode + " which:" + e.which + " id:" + e.keyIdentifier + " ksym:" + getKeysym(evt) + " alt:" + e.altKey + " shift:" + e.shiftKey + " ctrl:" + e.ctrlKey;
             message(msg);
 
             /*
@@ -84,7 +80,6 @@
 
             return false;
             */
-
         }
 
         window.onload = function() {