From 4be84fd6285c4284f03b3582eaf7a60ecfe684e7 Mon Sep 17 00:00:00 2001 From: Daniel Henrique Barboza Date: Mon, 11 Apr 2016 07:11:43 -0300 Subject: [PATCH] QEMU RFB extension - keyboard.js changes Added a 'QEMUKeyEventDecoder' method to deal with the key events generated when the QEMU extension is active. Another method, 'TrackQEMUKeyState', was also created with this same goal. Although both methods have similaries with the existing methods 'KeyEventDecoder' and 'TrackKeyState', specially when dealing with 'supress' and 'releaseall', the logic behind the QEMU extension does not required keysym generation for most cases (some NumPad keys are an exception) and, as such, there is no need to treat 'keyPressed' events and to handle char modifiers. 'TrackQEMUKeyState' also handles a Windows scenario where the 'AltGR' key generates CtrlLeft and AltRight keystrokes. The solution was to avoid this specific combination to be sent to the VNC server, discarding the extra 'CtrlLeft' key. Considering that the user can send CtrlLeft+AltLeft, CtrlRight+AltRight and even CtrlRight+AltLeft, this workaround to allow Windows users to use AltGR in their noVNC sessions is worthwhile. Signed-off-by: Daniel Henrique Barboza --- include/keyboard.js | 131 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/include/keyboard.js b/include/keyboard.js index 8667031..26543db 100644 --- a/include/keyboard.js +++ b/include/keyboard.js @@ -285,6 +285,137 @@ var kbdUtil = (function() { }; })(); +function QEMUKeyEventDecoder(modifierState, next) { + "use strict"; + + function sendAll(evts) { + for (var i = 0; i < evts.length; ++i) { + next(evts[i]); + } + } + + var numPadCodes = ["Numpad0", "Numpad1", "Numpad2", + "Numpad3", "Numpad4", "Numpad5", "Numpad6", + "Numpad7", "Numpad8", "Numpad9", "NumpadDecimal"]; + + var numLockOnKeySyms = { + "Numpad0": 0xffb0, "Numpad1": 0xffb1, "Numpad2": 0xffb2, + "Numpad3": 0xffb3, "Numpad4": 0xffb4, "Numpad5": 0xffb5, + "Numpad6": 0xffb6, "Numpad7": 0xffb7, "Numpad8": 0xffb8, + "Numpad9": 0xffb9, "NumpadDecimal": 0xffac + }; + + var numLockOnKeyCodes = [96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 108, 110]; + + function isNumPadMultiKey(evt) { + return (numPadCodes.indexOf(evt.code) !== -1); + } + + function getNumPadKeySym(evt) { + if (numLockOnKeyCodes.indexOf(evt.keyCode) !== -1) { + return numLockOnKeySyms[evt.code]; + } + return 0; + } + + function process(evt, type) { + var result = {type: type}; + result.code = evt.code; + result.keysym = 0; + + if (isNumPadMultiKey(evt)) { + result.keysym = getNumPadKeySym(evt); + } + + var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier(); + var isShift = evt.keyCode === 0x10 || evt.key === 'Shift'; + + var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!kbdUtil.nonCharacterKey(evt)); + + next(result); + return suppress; + } + return { + keydown: function(evt) { + sendAll(modifierState.keydown(evt)); + return process(evt, 'keydown'); + }, + keypress: function(evt) { + return true; + }, + keyup: function(evt) { + sendAll(modifierState.keyup(evt)); + return process(evt, 'keyup'); + }, + syncModifiers: function(evt) { + sendAll(modifierState.syncAny(evt)); + }, + releaseAll: function() { next({type: 'releaseall'}); } + }; +} + +function TrackQEMUKeyState(next) { + "use strict"; + var state = []; + + return function (evt) { + var last = state.length !== 0 ? state[state.length-1] : null; + + switch (evt.type) { + case 'keydown': + + if (!last || last.code !== evt.code) { + last = {code: evt.code}; + + if (state.length > 0 && state[state.length-1].code == 'ControlLeft') { + if (evt.code !== 'AltRight') { + next({code: 'ControlLeft', type: 'keydown', keysym: 0}); + } else { + state.pop(); + } + } + state.push(last); + } + if (evt.code !== 'ControlLeft') { + next(evt); + } + break; + + case 'keyup': + if (state.length === 0) { + return; + } + var idx = null; + // do we have a matching key tracked as being down? + for (var i = 0; i !== state.length; ++i) { + if (state[i].code === evt.code) { + idx = i; + break; + } + } + // if we couldn't find a match (it happens), assume it was the last key pressed + if (idx === null) { + if (evt.code === 'ControlLeft') { + return; + } + idx = state.length - 1; + } + + state.splice(idx, 1); + next(evt); + break; + case 'releaseall': + /* jshint shadow: true */ + for (var i = 0; i < state.length; ++i) { + next({code: state[i].code, keysym: 0, type: 'keyup'}); + } + /* jshint shadow: false */ + state = []; + } + }; +} + // Takes a DOM keyboard event and: // - determines which keysym it represents // - determines a keyId identifying the key that was pressed (corresponding to the key/keyCode properties on the DOM event)