From 77c250c44207a787a2954e0137311786f569836f Mon Sep 17 00:00:00 2001 From: Hirokazu Takahashi Date: Tue, 11 Dec 2012 17:30:22 +0900 Subject: [PATCH] AltGr key emulation support Lost key-modifier events recovery support --- include/input.js | 100 +++++++++++++++++++++++++++++++++++++++++++++-- include/rfb.js | 41 +++++++++++++++---- 2 files changed, 130 insertions(+), 11 deletions(-) diff --git a/include/input.js b/include/input.js index c0c74469..6d8215f6 100644 --- a/include/input.js +++ b/include/input.js @@ -29,6 +29,10 @@ Util.conf_defaults(conf, that, defaults, [ ['onKeyPress', 'rw', 'func', null, 'Handler for key press/release'] ]); +// CTRL/ALT/SHIFT/ALTGR key status. +var keyModifiers = {'altKey': false, 'ctrlKey': false, 'shiftKey': false, + 'altgrKey': false, 'altgrKey_fake': false, 'altgrKey_native': false}; + // // Private functions @@ -92,6 +96,10 @@ function getKeysymSpecial(evt) { } if ((!keysym) && (evt.ctrlKey || evt.altKey)) { + if (evt.ctrlKey && evt.altKey) { + // This is a key event with ALTGR. + return keysym; + } if ((typeof(evt.which) !== "undefined") && (evt.which > 0)) { keysym = evt.which; } else { @@ -248,6 +256,81 @@ function ignoreKeyEvent(evt) { return false; } +// 1. Save the current status of modifier keys to enable the keypress event +// handler to refer the status. Some browers may not fire keyup events +// pairing with some keydown events for modifier keys like CTRL, ALT and +// SHIFT keys. noVNC is expected to detect this situation to issue fake +// keyup events to fix it up. +// 2. Emulate ALTGR key events. Most browsers generate two key events +// when hitting an ALTGR key, events which are CTRL and ALT ones. +// But unfortunately there exist some vnc servers that can't handle +// them as an emulated ALTGR key event. noVNC has to convert them into +// one ALTGR key event on behalf of vnc servers. +function saveKeyModifiers(evt, keysym) { + var km = keyModifiers; + km.shiftKey = evt.shiftKey; + + if (evt.type === 'keydown') { + switch (keysym) { + // If this event is ALT or CTRL down and both evt.ctrlKey and + // evt.altKey get true, pass the decision to the next key event + // that whether it should be turned into an ALTGR key event. + case 0xFFE3 : + if (evt.ctrlKey && evt.altKey) break; + km.ctrlKey = true; + break; + case 0xFFE9 : + if (evt.ctrlKey && evt.altKey) break; + km.altKey = true; + break; + default : + switch (keysym) { + case 0xFFE1 : km.shiftKey = true; break; + case 0xFE03 : km.altgrKey_native = true; break; + } + km.altKey = evt.altKey; + km.ctrlKey = evt.ctrlKey; + break; + } + if (!(keysym) && km.ctrlKey && km.altKey) { + // Turn the ALT and CTRL events into an ALTGR. The keycode will + // be translated by the ALTGR even if it is an emulated one. + km.altgrKey_fake = true; + km.ctrlKey = km.altKey = false; + } else { + km.altgrKey_fake = false; + } + } else { // keyup event + switch (keysym) { + // Notice: FireFox 15 for linux doesn't set evt.altKey and + // evt.ctrlKey false even when this is an ALT or CTRL keyup + // event. Don't use evt.altKey and evt.ctrlKey here. + case 0xFFE3 : km.ctrlKey = false; break; + case 0xFFE9 : km.altKey = false; break; + default : + switch (keysym) { + case 0xFFE1 : km.shiftKey = false; break; + case 0xFE03 : km.altgrKey_native = false; break; + } + km.altKey = evt.altKey; + km.ctrlKey = evt.ctrlKey; + break; + } + if (km.altgrKey_fake) { + if (keysym == 0xFFE3 || keysym == 0xFFE9) { + // This is a part of an emulated ALTGR keyup event. + // Don't let ALT or CTRL down yet. + km.altgrKey_fake = false; + km.ctrlKey = km.altKey = false; + } else if (km.altKey && km.ctrlKey) { + km.ctrlKey = km.altKey = false; + } else { + km.altgrKey_fake = false; + } + } + } + km.altgrKey = km.altgrKey_fake || km.altgrKey_native; +}; // // Key Event Handling: @@ -307,6 +390,7 @@ function onKeyDown(e) { keysym = getKeysymSpecial(evt); // Save keysym decoding for use in keyUp fevt.keysym = keysym; + saveKeyModifiers(evt, keysym); if (keysym) { // If it is a key or key combination that might trigger // browser behaviors or it has no corresponding keyPress @@ -315,7 +399,7 @@ function onKeyDown(e) { Util.Debug("onKeyPress down, keysym: " + keysym + " (onKeyDown key: " + evt.keyCode + ", which: " + evt.which + ")"); - conf.onKeyPress(keysym, 1, evt); + conf.onKeyPress(keysym, 1, keyModifiers); pushKeyEvent(fevt); } suppress = true; @@ -356,6 +440,11 @@ function onKeyPress(e) { //show_keyDownMap('press'); + if (Util.Engine.presto && (evt.ctrlKey || evt.altKey)) { + // Opera may fire two keypress events againt one keydown when + // hitting a key with some modifiers. Just ignore the first one. + return false; + } // Send the translated keysym if (conf.onKeyPress && (keysym > 0)) { Util.Debug("onKeyPress down, keysym: " + keysym + @@ -364,7 +453,7 @@ function onKeyPress(e) { Util.Debug("onKeyPress up, keysym: " + keysym + " (onKeyPress key: " + evt.keyCode + ", which: " + evt.which + ")"); - conf.onKeyPress(keysym, 2, evt); + conf.onKeyPress(keysym, 2, keyModifiers); } // Stop keypress events just in case @@ -388,6 +477,7 @@ function onKeyUp(e) { // ") not found on keyDownMap"); keysym = 0; } + saveKeyModifiers(evt, keysym); //show_keyDownMap('up'); @@ -397,7 +487,7 @@ function onKeyUp(e) { Util.Debug("onKeyPress up, keysym: " + keysym + " (onKeyPress key: " + evt.keyCode + ", which: " + evt.which + ")"); - conf.onKeyPress(keysym, 0, evt); + conf.onKeyPress(keysym, 0, keyModifiers); } Util.stopEvent(e); return false; @@ -406,6 +496,8 @@ function onKeyUp(e) { function allKeysUp() { Util.Debug(">> Keyboard.allKeysUp"); var keyCode, keysym, fevt; + keyModifiers = {'altKey': false, 'ctrlKey': false, 'shiftKey': false, + 'altgrKey': false, 'altgrKey_fake': false, 'altgrKey_native': false}; for (keyCode in keyDownMap) { fevt = getKeyEvent(keyCode, true); keysym = fevt.keysym; @@ -413,7 +505,7 @@ function allKeysUp() { Util.Debug("allKeysUp, keysym: " + keysym + " (keyCode: " + fevt.keyCode + ", which: " + fevt.which + ")"); - conf.onKeyPress(keysym, 0, fevt); + conf.onKeyPress(keysym, 0, keyModifiers); } } Util.Debug("<< Keyboard.allKeysUp"); diff --git a/include/rfb.js b/include/rfb.js index e622ca3a..9b671003 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -596,21 +596,48 @@ checkEvents = function() { setTimeout(checkEvents, conf.check_rate); }; -keyPress = function(keysym, down) { - var arr; +// The status of the VNC server side key modifers. +var remote_status = {shift: false, ctrl: false, alt: false, altgr: false}; + +keyPress = function(keysym, down, km) { + var arr = []; if (conf.view_only) { return; } // View only, skip keyboard events - if (down === 2) { - // keypress event + // Generate all modifier keys' events on demand. + // Send key events for modifiers to the vnc server when: + // 1. the status of modifers have been changed + // 2. this is a repeated keydown event for a modifer + if (remote_status.ctrl !== km.ctrlKey || keysym === 0xFFE3) { + arr = arr.concat(keyEvent(0xFFE3, km.ctrlKey)); // CTRL + remote_status.ctrl = km.ctrlKey; + } + if (remote_status.alt !== km.altKey || keysym === 0xFFE9) { + arr = arr.concat(keyEvent(0xFFE9, km.altKey)); // ALT + remote_status.alt = km.altKey; + } + if (remote_status.altgr !== km.altgrKey || keysym === 0xFE03) { + arr = arr.concat(keyEvent(0xFE03, km.altgrKey)); // ALTGR + remote_status.altgr = km.altgrKey; + } + if (remote_status.shift !== km.shiftKey || keysym === 0xFFE1) { + arr = arr.concat(keyEvent(0xFFE1, km.shiftKey)); // SHIFT + remote_status.shift = km.shiftKey; + } + + if (keysym === 0xFFE1 || keysym === 0xFFE3 + || keysym === 0xFFE9 || keysym === 0xFE03) { + // SHIFT, CTRL, ALT and ALTGR events are already set in arr[]. + } else if (down === 2) { + // This is a keypress event. // Send a keyup event here to avoid letting the server side generate // repeated-key events. Otherwise, both sides will independently // generate key down and press events against the same key. - arr = keyEvent(keysym, 1); + arr = arr.concat(keyEvent(keysym, 1)); arr = arr.concat(keyEvent(keysym, 0)); } else { - // keydown or keyup event - arr = keyEvent(keysym, down); + // This is a keydown or keyup event. + arr = arr.concat(keyEvent(keysym, down)); } arr = arr.concat(fbUpdateRequests()); ws.send(arr);