summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSolly Ross <directxman12+github@gmail.com>2016-08-29 13:28:15 -0400
committerGitHub <noreply@github.com>2016-08-29 13:28:15 -0400
commitf4f4e8993d6daeb43b957766cd6973993232ba13 (patch)
tree80387442b16ba5c974fc538381f2534975e8293a
parent4e0c36dda708628836dc6f5d68fc40d05c7716d9 (diff)
parent49637e432e8331ef9775583345be23903ef3e633 (diff)
downloadnovnc-f4f4e8993d6daeb43b957766cd6973993232ba13.tar.gz
Merge pull request #596 from danielhb/master
QEMU RFB extension Fixes #21 🎉 (again)
-rw-r--r--LICENSE.txt5
-rw-r--r--include/input.js10
-rw-r--r--include/keyboard.js131
-rw-r--r--include/rfb.js71
-rw-r--r--include/ui.js5
-rw-r--r--include/xtscancodes.js146
-rw-r--r--karma.conf.js1
-rw-r--r--tests/input.html9
-rw-r--r--tests/test.rfb.js6
-rw-r--r--tests/vnc_perf.html5
-rw-r--r--tests/vnc_playback.html5
-rw-r--r--vnc_auto.html5
12 files changed, 379 insertions, 20 deletions
diff --git a/LICENSE.txt b/LICENSE.txt
index f217929..2ec4a64 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -17,6 +17,7 @@ is not limited to):
include/util.js
include/websock.js
include/webutil.js
+ include/xtscancodes.js
The HTML, CSS, font and images files that included with the noVNC
source distibution (or repository) are not considered part of the
@@ -45,7 +46,7 @@ the noVNC core library. Here is a list of those files and the original
licenses (all MPL 2.0 compatible):
include/base64.js : MPL 2.0
-
+
include/des.js : Various BSD style licenses
include/chrome-app/tcp-stream.js
@@ -53,7 +54,7 @@ licenses (all MPL 2.0 compatible):
utils/websockify
utils/websocket.py : LGPL 3
-
+
utils/inflator.partial.js
include/inflator.js : MIT (for pako)
diff --git a/include/input.js b/include/input.js
index fa6ba44..eb4d18a 100644
--- a/include/input.js
+++ b/include/input.js
@@ -51,10 +51,18 @@ var Keyboard, Mouse;
if (this._onKeyPress) {
Util.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") +
", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")");
- this._onKeyPress(e.keysym.keysym, e.type == 'keydown');
+ this._onKeyPress(e);
}
},
+ setQEMUVNCKeyboardHandler: function () {
+ this._handler = new QEMUKeyEventDecoder(kbdUtil.ModifierSync(),
+ TrackQEMUKeyState(
+ this._handleRfbEvent.bind(this)
+ )
+ );
+ },
+
_handleKeyDown: function (e) {
if (!this._focused) { return true; }
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)
diff --git a/include/rfb.js b/include/rfb.js
index bc5555a..9935962 100644
--- a/include/rfb.js
+++ b/include/rfb.js
@@ -58,7 +58,8 @@ var RFB;
['ExtendedDesktopSize', -308 ],
['xvp', -309 ],
['Fence', -312 ],
- ['ContinuousUpdates', -313 ]
+ ['ContinuousUpdates', -313 ],
+ ['QEMUExtendedKeyEvent', -258 ]
];
this._encHandlers = {};
@@ -129,6 +130,9 @@ var RFB;
this._viewportDragPos = {};
this._viewportHasMoved = false;
+ // QEMU Extended Key Event support - default to false
+ this._qemuExtKeyEventSupported = false;
+
// set the default value on user-facing properties
Util.set_defaults(this, defaults, {
'target': 'null', // VNC display rendering Canvas object
@@ -560,9 +564,22 @@ var RFB;
}
},
- _handleKeyPress: function (keysym, down) {
+ _handleKeyPress: function (keyevent) {
if (this._view_only) { return; } // View only, skip keyboard, events
- RFB.messages.keyEvent(this._sock, keysym, down);
+
+ var down = (keyevent.type == 'keydown');
+ if (this._qemuExtKeyEventSupported) {
+ var scancode = XtScancode[keyevent.code];
+ if (scancode) {
+ var keysym = keyevent.keysym;
+ RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
+ } else {
+ Util.Error('Unable to find a xt scancode for code = ' + keyevent.code);
+ }
+ } else {
+ keysym = keyevent.keysym.keysym;
+ RFB.messages.keyEvent(this._sock, keysym, down);
+ }
},
_handleMouseButton: function (x, y, down, bmask) {
@@ -1348,6 +1365,42 @@ var RFB;
sock.flush();
},
+ QEMUExtendedKeyEvent: function (sock, keysym, down, keycode) {
+ function getRFBkeycode(xt_scancode) {
+ var upperByte = (keycode >> 8);
+ var lowerByte = (keycode & 0x00ff);
+ if (upperByte === 0xe0 && lowerByte < 0x7f) {
+ lowerByte = lowerByte | 0x80;
+ return lowerByte;
+ }
+ return xt_scancode
+ }
+
+ var buff = sock._sQ;
+ var offset = sock._sQlen;
+
+ buff[offset] = 255; // msg-type
+ buff[offset + 1] = 0; // sub msg-type
+
+ buff[offset + 2] = (down >> 8);
+ buff[offset + 3] = down;
+
+ buff[offset + 4] = (keysym >> 24);
+ buff[offset + 5] = (keysym >> 16);
+ buff[offset + 6] = (keysym >> 8);
+ buff[offset + 7] = keysym;
+
+ var RFBkeycode = getRFBkeycode(keycode)
+
+ buff[offset + 8] = (RFBkeycode >> 24);
+ buff[offset + 9] = (RFBkeycode >> 16);
+ buff[offset + 10] = (RFBkeycode >> 8);
+ buff[offset + 11] = RFBkeycode;
+
+ sock._sQlen += 12;
+ sock.flush();
+ },
+
pointerEvent: function (sock, x, y, mask) {
var buff = sock._sQ;
var offset = sock._sQlen;
@@ -2259,6 +2312,16 @@ var RFB;
compress_lo: function () {
Util.Error("Server sent compress level pseudo-encoding");
- }
+ },
+
+ QEMUExtendedKeyEvent: function () {
+ this._FBU.rects--;
+
+ var keyboardEvent = document.createEvent("keyboardEvent");
+ if (keyboardEvent.code !== undefined) {
+ this._qemuExtKeyEventSupported = true;
+ this._keyboard.setQEMUVNCKeyboardHandler();
+ }
+ },
};
})();
diff --git a/include/ui.js b/include/ui.js
index 0386363..d69a4f6 100644
--- a/include/ui.js
+++ b/include/ui.js
@@ -18,8 +18,9 @@ var UI;
// Load supporting scripts
window.onscriptsload = function () { UI.load(); };
Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
- "keysymdef.js", "keyboard.js", "input.js", "display.js",
- "rfb.js", "keysym.js", "inflator.js"]);
+ "keysymdef.js", "xtscancodes.js", "keyboard.js",
+ "input.js", "display.js", "rfb.js", "keysym.js",
+ "inflator.js"]);
UI = {
diff --git a/include/xtscancodes.js b/include/xtscancodes.js
new file mode 100644
index 0000000..d19a017
--- /dev/null
+++ b/include/xtscancodes.js
@@ -0,0 +1,146 @@
+var XtScancode = {};
+XtScancode["Escape"] = 0x0001;
+XtScancode["Digit1"] = 0x0002;
+XtScancode["Digit2"] = 0x0003;
+XtScancode["Digit3"] = 0x0004;
+XtScancode["Digit4"] = 0x0005;
+XtScancode["Digit5"] = 0x0006;
+XtScancode["Digit6"] = 0x0007;
+XtScancode["Digit7"] = 0x0008;
+XtScancode["Digit8"] = 0x0009;
+XtScancode["Digit9"] = 0x000A;
+XtScancode["Digit0"] = 0x000B;
+XtScancode["Minus"] = 0x000C;
+XtScancode["Equal"] = 0x000D;
+XtScancode["Backspace"] = 0x000E;
+XtScancode["Tab"] = 0x000F;
+XtScancode["KeyQ"] = 0x0010;
+XtScancode["KeyW"] = 0x0011;
+XtScancode["KeyE"] = 0x0012;
+XtScancode["KeyR"] = 0x0013;
+XtScancode["KeyT"] = 0x0014;
+XtScancode["KeyY"] = 0x0015;
+XtScancode["KeyU"] = 0x0016;
+XtScancode["KeyI"] = 0x0017;
+XtScancode["KeyO"] = 0x0018;
+XtScancode["KeyP"] = 0x0019;
+XtScancode["BracketLeft"] = 0x001A;
+XtScancode["BracketRight"] = 0x001B;
+XtScancode["Enter"] = 0x001C;
+XtScancode["ControlLeft"] = 0x001D;
+XtScancode["KeyA"] = 0x001E;
+XtScancode["KeyS"] = 0x001F;
+XtScancode["KeyD"] = 0x0020;
+XtScancode["KeyF"] = 0x0021;
+XtScancode["KeyG"] = 0x0022;
+XtScancode["KeyH"] = 0x0023;
+XtScancode["KeyJ"] = 0x0024;
+XtScancode["KeyK"] = 0x0025;
+XtScancode["KeyL"] = 0x0026;
+XtScancode["Semicolon"] = 0x0027;
+XtScancode["Quote"] = 0x0028;
+XtScancode["Backquote"] = 0x0029;
+XtScancode["ShiftLeft"] = 0x002A;
+XtScancode["Backslash"] = 0x002B;
+XtScancode["KeyZ"] = 0x002C;
+XtScancode["KeyX"] = 0x002D;
+XtScancode["KeyC"] = 0x002E;
+XtScancode["KeyV"] = 0x002F;
+XtScancode["KeyB"] = 0x0030;
+XtScancode["KeyN"] = 0x0031;
+XtScancode["KeyM"] = 0x0032;
+XtScancode["Comma"] = 0x0033;
+XtScancode["Period"] = 0x0034;
+XtScancode["Slash"] = 0x0035;
+XtScancode["ShiftRight"] = 0x0036;
+XtScancode["NumpadMultiply"] = 0x0037;
+XtScancode["AltLeft"] = 0x0038;
+XtScancode["Space"] = 0x0039;
+XtScancode["CapsLock"] = 0x003A;
+XtScancode["F1"] = 0x003B;
+XtScancode["F2"] = 0x003C;
+XtScancode["F3"] = 0x003D;
+XtScancode["F4"] = 0x003E;
+XtScancode["F5"] = 0x003F;
+XtScancode["F6"] = 0x0040;
+XtScancode["F7"] = 0x0041;
+XtScancode["F8"] = 0x0042;
+XtScancode["F9"] = 0x0043;
+XtScancode["F10"] = 0x0044;
+XtScancode["Pause"] = 0xE045;
+XtScancode["ScrollLock"] = 0x0046;
+XtScancode["Numpad7"] = 0x0047;
+XtScancode["Numpad8"] = 0x0048;
+XtScancode["Numpad9"] = 0x0049;
+XtScancode["NumpadSubtract"] = 0x004A;
+XtScancode["Numpad4"] = 0x004B;
+XtScancode["Numpad5"] = 0x004C;
+XtScancode["Numpad6"] = 0x004D;
+XtScancode["NumpadAdd"] = 0x004E;
+XtScancode["Numpad1"] = 0x004F;
+XtScancode["Numpad2"] = 0x0050;
+XtScancode["Numpad3"] = 0x0051;
+XtScancode["Numpad0"] = 0x0052;
+XtScancode["NumpadDecimal"] = 0x0053;
+XtScancode["IntlBackslash"] = 0x0056;
+XtScancode["F11"] = 0x0057;
+XtScancode["F12"] = 0x0058;
+XtScancode["IntlYen"] = 0x007D;
+XtScancode["MediaTrackPrevious"] = 0xE010;
+XtScancode["MediaTrackNext"] = 0xE019;
+XtScancode["NumpadEnter"] = 0xE01C;
+XtScancode["ControlRight"] = 0xE01D;
+XtScancode["VolumeMute"] = 0xE020;
+XtScancode["MediaPlayPause"] = 0xE022;
+XtScancode["MediaStop"] = 0xE024;
+XtScancode["VolumeDown"] = 0xE02E;
+XtScancode["VolumeUp"] = 0xE030;
+XtScancode["BrowserHome"] = 0xE032;
+XtScancode["NumpadDivide"] = 0xE035;
+XtScancode["PrintScreen"] = 0xE037;
+XtScancode["AltRight"] = 0xE038;
+XtScancode["NumLock"] = 0x0045;
+XtScancode["Home"] = 0xE047;
+XtScancode["ArrowUp"] = 0xE048;
+XtScancode["PageUp"] = 0xE049;
+XtScancode["ArrowLeft"] = 0xE04B;
+XtScancode["ArrowRight"] = 0xE04D;
+XtScancode["End"] = 0xE04F;
+XtScancode["ArrowDown"] = 0xE050;
+XtScancode["PageDown"] = 0xE051;
+XtScancode["Insert"] = 0xE052;
+XtScancode["Delete"] = 0xE053;
+XtScancode["OSLeft"] = 0xE05B;
+XtScancode["OSRight"] = 0xE05C;
+XtScancode["ContextMenu"] = 0xE05D;
+XtScancode["BrowserSearch"] = 0xE065;
+XtScancode["BrowserFavorites"] = 0xE066;
+XtScancode["BrowserRefresh"] = 0xE067;
+XtScancode["BrowserStop"] = 0xE068;
+XtScancode["BrowserForward"] = 0xE069;
+XtScancode["BrowserBack"] = 0xE06A;
+XtScancode["NumpadComma"] = 0x007E;
+XtScancode["NumpadEqual"] = 0x0059;
+XtScancode["F13"] = 0x0064;
+XtScancode["F14"] = 0x0065;
+XtScancode["F15"] = 0x0066;
+XtScancode["F16"] = 0x0067;
+XtScancode["F17"] = 0x0068;
+XtScancode["F18"] = 0x0069;
+XtScancode["F19"] = 0x006A;
+XtScancode["F20"] = 0x006B;
+XtScancode["F21"] = 0x006C;
+XtScancode["F22"] = 0x006D;
+XtScancode["F23"] = 0x006E;
+XtScancode["F24"] = 0x0076;
+XtScancode["KanaMode"] = 0x0070;
+XtScancode["Lang2"] = 0x0071;
+XtScancode["Lang1"] = 0x0072;
+XtScancode["IntlRo"] = 0x0073;
+XtScancode["Convert"] = 0x0079;
+XtScancode["NonConvert"] = 0x007B;
+XtScancode["LaunchApp2"] = 0xE021;
+XtScancode["Power"] = 0xE05E;
+XtScancode["LaunchApp1"] = 0xE06B;
+XtScancode["LaunchMail"] = 0xE06C;
+XtScancode["MediaSelect"] = 0xE06D;
diff --git a/karma.conf.js b/karma.conf.js
index 870b855..2c49ffc 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -115,6 +115,7 @@ module.exports = function(config) {
'include/base64.js',
'include/keysym.js',
'include/keysymdef.js',
+ 'include/xtscancodes.js',
'include/keyboard.js',
'include/input.js',
'include/websock.js',
diff --git a/tests/input.html b/tests/input.html
index 8416379..301a7f8 100644
--- a/tests/input.html
+++ b/tests/input.html
@@ -20,16 +20,17 @@
</body>
<!--
- <script type='text/javascript'
+ <script type='text/javascript'
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-->
<script src="../include/util.js"></script>
- <script src="../include/webutil.js"></script>
+ <script src="../include/webutil.js"></script>
<script src="../include/base64.js"></script>
<script src="../include/keysym.js"></script>
<script src="../include/keysymdef.js"></script>
- <script src="../include/keyboard.js"></script>
- <script src="../include/input.js"></script>
+ <script src="../include/xtscancodes.js"></script>
+ <script src="../include/keyboard.js"></script>
+ <script src="../include/input.js"></script>
<script src="../include/display.js"></script>
<script>
var msg_cnt = 0, iterations,
diff --git a/tests/test.rfb.js b/tests/test.rfb.js
index 65ce5f8..06eeebe 100644
--- a/tests/test.rfb.js
+++ b/tests/test.rfb.js
@@ -1927,7 +1927,11 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should send a key message on a key press', function () {
- client._keyboard._onKeyPress(1234, 1);
+ var keyevent = {};
+ keyevent.type = 'keydown';
+ keyevent.keysym = {};
+ keyevent.keysym.keysym = 1234;
+ client._keyboard._onKeyPress(keyevent);
var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
RFB.messages.keyEvent(key_msg, 1234, 1);
expect(client._sock).to.have.sent(key_msg._sQ);
diff --git a/tests/vnc_perf.html b/tests/vnc_perf.html
index 9acea88..62f749c 100644
--- a/tests/vnc_perf.html
+++ b/tests/vnc_perf.html
@@ -50,8 +50,9 @@
// Load supporting scripts
Util.load_scripts(["base64.js", "websock.js", "des.js", "keysym.js",
- "keysymdef.js", "keyboard.js", "input.js", "display.js",
- "rfb.js", "playback.js", "inflator.js", fname]);
+ "keysymdef.js", "xtscancodes.js", "keyboard.js",
+ "input.js", "display.js", "rfb.js", "playback.js",
+ "inflator.js", fname]);
} else {
msg("Must specifiy data=FOO.js in query string.");
}
diff --git a/tests/vnc_playback.html b/tests/vnc_playback.html
index cfc5953..c4d2108 100644
--- a/tests/vnc_playback.html
+++ b/tests/vnc_playback.html
@@ -60,8 +60,9 @@
message("Loading " + fname);
// Load supporting scripts
Util.load_scripts(["base64.js", "websock.js", "des.js", "keysym.js",
- "keysymdef.js", "keyboard.js", "input.js", "display.js",
- "rfb.js", "playback.js", "inflator.js", fname]);
+ "keysymdef.js", "xtscancodes.js", "keyboard.js",
+ "input.js", "display.js", "rfb.js", "playback.js",
+ "inflator.js", fname]);
} else {
message("Must specify data=FOO in query string.");
diff --git a/vnc_auto.html b/vnc_auto.html
index 2d81cca..597028e 100644
--- a/vnc_auto.html
+++ b/vnc_auto.html
@@ -78,8 +78,9 @@
// Load supporting scripts
Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
- "keysymdef.js", "keyboard.js", "input.js", "display.js",
- "inflator.js", "rfb.js", "keysym.js"]);
+ "keysymdef.js", "xtscancodes.js", "keyboard.js",
+ "input.js", "display.js", "inflator.js", "rfb.js",
+ "keysym.js"]);
var rfb;
var resizeTimeout;