Skip to content

Commit

Permalink
Merge pull request #596 from danielhb/master
Browse files Browse the repository at this point in the history
QEMU RFB extension

Fixes #21 🎉 (again)
  • Loading branch information
DirectXMan12 authored Aug 29, 2016
2 parents 4e0c36d + 49637e4 commit f4f4e89
Show file tree
Hide file tree
Showing 12 changed files with 379 additions and 20 deletions.
5 changes: 3 additions & 2 deletions LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -45,15 +46,15 @@ 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
: Apache 2.0 license

utils/websockify
utils/websocket.py : LGPL 3

utils/inflator.partial.js
include/inflator.js : MIT (for pako)

Expand Down
10 changes: 9 additions & 1 deletion include/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
131 changes: 131 additions & 0 deletions include/keyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
71 changes: 67 additions & 4 deletions include/rfb.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ var RFB;
['ExtendedDesktopSize', -308 ],
['xvp', -309 ],
['Fence', -312 ],
['ContinuousUpdates', -313 ]
['ContinuousUpdates', -313 ],
['QEMUExtendedKeyEvent', -258 ]
];

this._encHandlers = {};
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
},
};
})();
5 changes: 3 additions & 2 deletions include/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {

Expand Down
Loading

0 comments on commit f4f4e89

Please sign in to comment.