Use standard DOM identifiers for physical keys

This commit is contained in:
Pierre Ossman 2017-01-24 15:16:10 +01:00
parent a5c8a755e8
commit 80cb8ffddd
7 changed files with 389 additions and 155 deletions

View File

@ -1,5 +1,6 @@
import KeyTable from "./keysym.js";
import keysyms from "./keysymdef.js";
import vkeys from "./vkeys.js";
function isMac() {
return navigator && !!(/mac/i).exec(navigator.platform);
@ -131,18 +132,64 @@ export function ModifierSync(charModifier) {
};
}
// Get a key ID from a keyboard event
// May be a string or an integer depending on the available properties
export function getKey(evt){
if ('keyCode' in evt && 'key' in evt) {
return evt.key + ':' + evt.keyCode;
// Get 'KeyboardEvent.code', handling legacy browsers
export function getKeycode(evt){
// Are we getting proper key identifiers?
// (unfortunately Firefox and Chrome are crappy here and gives
// us an empty string on some platforms, rather than leaving it
// undefined)
if (evt.code) {
// Mozilla isn't fully in sync with the spec yet
switch (evt.code) {
case 'OSLeft': return 'MetaLeft';
case 'OSRight': return 'MetaRight';
}
else if ('keyCode' in evt) {
return evt.keyCode;
return evt.code;
}
else {
return evt.key;
// The de-facto standard is to use Windows Virtual-Key codes
// in the 'keyCode' field for non-printable characters. However
// Webkit sets it to the same as charCode in 'keypress' events.
if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
var code = vkeys[evt.keyCode];
// macOS has messed up this code for some reason
if (isMac() && (code === 'ContextMenu')) {
code = 'MetaRight';
}
// The keyCode doesn't distinguish between left and right
// for the standard modifiers
if (evt.location === 2) {
switch (code) {
case 'ShiftLeft': return 'ShiftRight';
case 'ControlLeft': return 'ControlRight';
case 'AltLeft': return 'AltRight';
}
}
// Nor a bunch of the numpad keys
if (evt.location === 3) {
switch (code) {
case 'Delete': return 'NumpadDecimal';
case 'Insert': return 'Numpad0';
case 'End': return 'Numpad1';
case 'ArrowDown': return 'Numpad2';
case 'PageDown': return 'Numpad3';
case 'ArrowLeft': return 'Numpad4';
case 'ArrowRight': return 'Numpad6';
case 'Home': return 'Numpad7';
case 'ArrowUp': return 'Numpad8';
case 'PageUp': return 'Numpad9';
case 'Enter': return 'NumpadEnter';
}
}
return code;
}
return 'Unidentified';
}
// Get the most reliable keysym value we can get from a key event
@ -290,7 +337,7 @@ export function QEMUKeyEventDecoder (modifierState, next) {
function process(evt, type) {
var result = {type: type};
result.code = evt.code;
result.code = getKeycode(evt);
result.keysym = 0;
if (isNumPadMultiKey(evt)) {
@ -298,7 +345,7 @@ export function QEMUKeyEventDecoder (modifierState, next) {
}
var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
var isShift = evt.keyCode === 0x10 || evt.key === 'Shift';
var isShift = result.code === 'ShiftLeft' || result.code === 'ShiftRight';
var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt));
@ -387,7 +434,7 @@ export function TrackQEMUKeyState (next) {
// 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)
// - determines a code identifying the key that was pressed (corresponding to the code/keyCode properties on the DOM event)
// - synthesizes events to synchronize modifier key state between which modifiers are actually down, and which we thought were down
// - marks each event with an 'escape' property if a modifier was down which should be "escaped"
// - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown
@ -401,10 +448,16 @@ export function KeyEventDecoder (modifierState, next) {
}
function process(evt, type) {
var result = {type: type};
var keyId = getKey(evt);
if (keyId) {
result.keyId = keyId;
var code = getKeycode(evt);
if (code === 'Unidentified') {
// Unstable, but we don't have anything else to go on
// (don't use it for 'keypress' events thought since
// WebKit sets it to the same as charCode)
if (evt.keyCode && (evt.type !== 'keypress')) {
code = 'Platform' + evt.keyCode;
}
}
result.code = code;
var keysym = getKeysym(evt);
@ -416,7 +469,7 @@ export function KeyEventDecoder (modifierState, next) {
result.keysym = keysym;
}
var isShift = evt.keyCode === 0x10 || evt.key === 'Shift';
var isShift = code === 'ShiftLeft' || code === 'ShiftRight';
// Should we prevent the browser from handling the event?
// Doing so on a keydown (in most browsers) prevents keypress from being generated
@ -546,8 +599,8 @@ export function TrackKeyState (next) {
switch (evt.type) {
case 'keydown':
// insert a new entry if last seen key was different.
if (!last || !evt.keyId || last.keyId !== evt.keyId) {
last = {keyId: evt.keyId, keysyms: {}};
if (!last || evt.code === 'Unidentified' || last.code !== evt.code) {
last = {code: evt.code, keysyms: {}};
state.push(last);
}
if (evt.keysym) {
@ -560,7 +613,7 @@ export function TrackKeyState (next) {
break;
case 'keypress':
if (!last) {
last = {keyId: evt.keyId, keysyms: {}};
last = {code: evt.code, keysyms: {}};
state.push(last);
}
if (!evt.keysym) {
@ -582,7 +635,7 @@ export function TrackKeyState (next) {
var idx = null;
// do we have a matching key tracked as being down?
for (var i = 0; i !== state.length; ++i) {
if (state[i].keyId === evt.keyId) {
if (state[i].code === evt.code) {
idx = i;
break;
}
@ -609,7 +662,7 @@ export function TrackKeyState (next) {
for (var i = 0; i < state.length; ++i) {
for (var key in state[i].keysyms) {
var keysym = state[i].keysyms[key];
next({keyId: 0, keysym: keysym, type: 'keyup'});
next({code: 'Unidentified', keysym: keysym, type: 'keyup'});
}
}
/* jshint shadow: false */
@ -629,14 +682,14 @@ export function EscapeModifiers (next) {
}
// undo modifiers
for (var i = 0; i < evt.escape.length; ++i) {
next({type: 'keyup', keyId: 0, keysym: evt.escape[i]});
next({type: 'keyup', code: 'Unidentified', keysym: evt.escape[i]});
}
// send the character event
next(evt);
// redo modifiers
/* jshint shadow: true */
for (var i = 0; i < evt.escape.length; ++i) {
next({type: 'keydown', keyId: 0, keysym: evt.escape[i]});
next({type: 'keydown', code: 'Unidentified', keysym: evt.escape[i]});
}
/* jshint shadow: false */
};

116
core/input/vkeys.js Normal file
View File

@ -0,0 +1,116 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2017 Pierre Ossman for Cendio AB
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/
/*
* Mapping between Microsoft® Windows® Virtual-Key codes and
* HTML key codes.
*/
export default {
0x08: 'Backspace',
0x09: 'Tab',
0x0a: 'NumpadClear',
0x0d: 'Enter',
0x10: 'ShiftLeft',
0x11: 'ControlLeft',
0x12: 'AltLeft',
0x13: 'Pause',
0x14: 'CapsLock',
0x15: 'Lang1',
0x19: 'Lang2',
0x1b: 'Escape',
0x1c: 'Convert',
0x1d: 'NonConvert',
0x20: 'Space',
0x21: 'PageUp',
0x22: 'PageDown',
0x23: 'End',
0x24: 'Home',
0x25: 'ArrowLeft',
0x26: 'ArrowUp',
0x27: 'ArrowRight',
0x28: 'ArrowDown',
0x29: 'Select',
0x2c: 'PrintScreen',
0x2d: 'Insert',
0x2e: 'Delete',
0x2f: 'Help',
0x30: 'Digit0',
0x31: 'Digit1',
0x32: 'Digit2',
0x33: 'Digit3',
0x34: 'Digit4',
0x35: 'Digit5',
0x36: 'Digit6',
0x37: 'Digit7',
0x38: 'Digit8',
0x39: 'Digit9',
0x5b: 'MetaLeft',
0x5c: 'MetaRight',
0x5d: 'ContextMenu',
0x5f: 'Sleep',
0x60: 'Numpad0',
0x61: 'Numpad1',
0x62: 'Numpad2',
0x63: 'Numpad3',
0x64: 'Numpad4',
0x65: 'Numpad5',
0x66: 'Numpad6',
0x67: 'Numpad7',
0x68: 'Numpad8',
0x69: 'Numpad9',
0x6a: 'NumpadMultiply',
0x6b: 'NumpadAdd',
0x6c: 'NumpadDecimal',
0x6d: 'NumpadSubtract',
0x6e: 'NumpadDecimal', // Duplicate, because buggy on Windows
0x6f: 'NumpadDivide',
0x70: 'F1',
0x71: 'F2',
0x72: 'F3',
0x73: 'F4',
0x74: 'F5',
0x75: 'F6',
0x76: 'F7',
0x77: 'F8',
0x78: 'F9',
0x79: 'F10',
0x7a: 'F11',
0x7b: 'F12',
0x7c: 'F13',
0x7d: 'F14',
0x7e: 'F15',
0x7f: 'F16',
0x80: 'F17',
0x81: 'F18',
0x82: 'F19',
0x83: 'F20',
0x84: 'F21',
0x85: 'F22',
0x86: 'F23',
0x87: 'F24',
0x90: 'NumLock',
0x91: 'ScrollLock',
0xa6: 'BrowserBack',
0xa7: 'BrowserForward',
0xa8: 'BrowserRefresh',
0xa9: 'BrowserStop',
0xaa: 'BrowserSearch',
0xab: 'BrowserFavorites',
0xac: 'BrowserHome',
0xad: 'AudioVolumeMute',
0xae: 'AudioVolumeDown',
0xaf: 'AudioVolumeUp',
0xb0: 'MediaTrackNext',
0xb1: 'MediaTrackPrevious',
0xb2: 'MediaStop',
0xb3: 'MediaPlayPause',
0xb4: 'LaunchMail',
0xb5: 'MediaSelect',
0xb6: 'LaunchApp1',
0xb7: 'LaunchApp2',
0xe1: 'AltRight', // Only when it is AltGraph
};

View File

@ -112,8 +112,6 @@ export default {
"Delete": 0xE053,
"MetaLeft": 0xE05B,
"MetaRight": 0xE05C,
"OSLeft": 0xE05B, // OSLeft and OSRight are kept for compatability since
"OSRight": 0xE05C, // Firefox haven't updated to MetaLeft and MetaRight yet
"ContextMenu": 0xE05D,
"BrowserSearch": 0xE065,
"BrowserFavorites": 0xE066,

View File

@ -29,6 +29,7 @@
<script src="../core/input/keysym.js"></script>
<script src="../core/input/keysymdef.js"></script>
<script src="../core/input/xtscancodes.js"></script>
<script src="../core/input/vkeys.js"></script>
<script src="../core/input/util.js"></script>
<script src="../core/input/devices.js"></script>
<script src="../core/display.js"></script>

View File

@ -33,6 +33,71 @@ describe('Helpers', function() {
});
});
describe('getKeycode', function() {
it('should pass through proper code', function() {
expect(KeyboardUtil.getKeycode({code: 'Semicolon'})).to.be.equal('Semicolon');
});
it('should map legacy values', function() {
expect(KeyboardUtil.getKeycode({code: ''})).to.be.equal('Unidentified');
expect(KeyboardUtil.getKeycode({code: 'OSLeft'})).to.be.equal('MetaLeft');
});
it('should map keyCode to code when possible', function() {
expect(KeyboardUtil.getKeycode({keyCode: 0x14})).to.be.equal('CapsLock');
expect(KeyboardUtil.getKeycode({keyCode: 0x5b})).to.be.equal('MetaLeft');
expect(KeyboardUtil.getKeycode({keyCode: 0x35})).to.be.equal('Digit5');
expect(KeyboardUtil.getKeycode({keyCode: 0x65})).to.be.equal('Numpad5');
});
it('should map keyCode left/right side', function() {
expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 1})).to.be.equal('ShiftLeft');
expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 2})).to.be.equal('ShiftRight');
expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 1})).to.be.equal('ControlLeft');
expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 2})).to.be.equal('ControlRight');
});
it('should map keyCode on numpad', function() {
expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 0})).to.be.equal('Enter');
expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 3})).to.be.equal('NumpadEnter');
expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 0})).to.be.equal('End');
expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 3})).to.be.equal('Numpad1');
});
it('should return Unidentified when it cannot map the keyCode', function() {
expect(KeyboardUtil.getKeycode({keycode: 0x42})).to.be.equal('Unidentified');
});
describe('Fix Meta on macOS', function() {
var origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.platform = "Mac x86_64";
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
});
it('should respect ContextMenu on modern browser', function() {
expect(KeyboardUtil.getKeycode({code: 'ContextMenu', keyCode: 0x5d})).to.be.equal('ContextMenu');
});
it('should translate legacy ContextMenu to MetaRight', function() {
expect(KeyboardUtil.getKeycode({keyCode: 0x5d})).to.be.equal('MetaRight');
});
});
});
describe('getKeysym', function() {
it('should prefer char', function() {
expect(KeyboardUtil.getKeysym({char : 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x61);

View File

@ -12,26 +12,26 @@ describe('Key Event Pipeline Stages', function() {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.an.object;
done();
}).keydown({keyCode: 0x41});
}).keydown({code: 'KeyA', keyCode: 0x41});
});
it('should pass the right keysym through', function(done) {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt.keysym).to.be.deep.equal(0x61);
done();
}).keypress({keyCode: 0x41});
}).keypress({code: 'KeyA', keyCode: 0x41});
});
it('should pass the right keyid through', function(done) {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.have.property('keyId', 0x41);
expect(evt).to.have.property('code', 'KeyA');
done();
}).keydown({keyCode: 0x41});
}).keydown({code: 'KeyA', keyCode: 0x41});
});
it('should not sync modifiers on a keypress', function() {
// Firefox provides unreliable modifier state on keypress events
var count = 0;
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
++count;
}).keypress({keyCode: 0x41, ctrlKey: true});
}).keypress({code: 'KeyA', keyCode: 0x41, ctrlKey: true});
expect(count).to.be.equal(1);
});
it('should sync modifiers if necessary', function(done) {
@ -43,29 +43,29 @@ describe('Key Event Pipeline Stages', function() {
++count;
break;
case 1:
expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown', keysym: 0x61});
expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown', keysym: 0x61});
done();
break;
}
}).keydown({keyCode: 0x41, ctrlKey: true});
}).keydown({code: 'KeyA', keyCode: 0x41, ctrlKey: true});
});
it('should forward keydown events with the right type', function(done) {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'});
expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'});
done();
}).keydown({keyCode: 0x41});
}).keydown({code: 'KeyA', keyCode: 0x41});
});
it('should forward keyup events with the right type', function(done) {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keyup'});
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'});
done();
}).keyup({keyCode: 0x41});
}).keyup({code: 'KeyA', keyCode: 0x41});
});
it('should forward keypress events with the right type', function(done) {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keypress'});
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'});
done();
}).keypress({keyCode: 0x41});
}).keypress({code: 'KeyA', keyCode: 0x41});
});
it('should generate stalls if a char modifier is down while a key is pressed', function(done) {
var count = 0;
@ -82,14 +82,14 @@ describe('Key Event Pipeline Stages', function() {
case 2: // 'a'
expect(evt).to.be.deep.equal({
type: 'keydown',
keyId: 0x41,
code: 'KeyA',
keysym: 0x61
});
done();
break;
}
}).keydown({keyCode: 0x41, altGraphKey: true});
}).keydown({code: 'KeyA', keyCode: 0x41, altGraphKey: true});
});
describe('suppress the right events at the right time', function() {
@ -184,7 +184,7 @@ describe('Key Event Pipeline Stages', function() {
});
obj.keydown({keyCode: 0xe1}); // press altgr
obj.keydown({keyCode: 'A'.charCodeAt()});
obj.keydown({code: 'KeyA', keyCode: 0x41});
});
it('should indicate on events if a single-key char modifier is down', function(done) {
@ -196,8 +196,8 @@ describe('Key Event Pipeline Stages', function() {
case 1: // 'a'
expect(evt).to.be.deep.equal({
type: 'keypress',
keyId: 'A'.charCodeAt(),
keysym: 'a'.charCodeAt(),
code: 'KeyA',
keysym: 0x61,
escape: [0xfe03]
});
done();
@ -206,7 +206,7 @@ describe('Key Event Pipeline Stages', function() {
});
obj.keydown({keyCode: 0xe1}); // press altgr
obj.keypress({keyCode: 'A'.charCodeAt()});
obj.keypress({code: 'KeyA', keyCode: 0x41});
});
it('should indicate on events if a multi-key char modifier is down', function(done) {
var times_called = 0;
@ -219,8 +219,8 @@ describe('Key Event Pipeline Stages', function() {
case 2: // 'a'
expect(evt).to.be.deep.equal({
type: 'keypress',
keyId: 'A'.charCodeAt(),
keysym: 'a'.charCodeAt(),
code: 'KeyA',
keysym: 0x61,
escape: [0xffe9, 0xffe3]
});
done();
@ -230,7 +230,7 @@ describe('Key Event Pipeline Stages', function() {
obj.keydown({keyCode: 0x11}); // press ctrl
obj.keydown({keyCode: 0x12}); // press alt
obj.keypress({keyCode: 'A'.charCodeAt()});
obj.keypress({code: 'KeyA', keyCode: 0x41});
});
it('should not consider a char modifier to be down on the modifier key itself', function() {
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
@ -244,18 +244,18 @@ describe('Key Event Pipeline Stages', function() {
describe('add/remove keysym', function() {
it('should remove keysym from keydown if a char key and no modifier', function() {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'});
}).keydown({keyCode: 0x41});
expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'});
}).keydown({code: 'KeyA', keyCode: 0x41});
});
it('should not remove keysym from keydown if a shortcut modifier is down', function() {
var times_called = 0;
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
switch (times_called++) {
case 1:
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keydown'});
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'});
break;
}
}).keydown({keyCode: 0x41, ctrlKey: true});
}).keydown({code: 'KeyA', keyCode: 0x41, ctrlKey: true});
expect(times_called).to.be.equal(2);
});
it('should not remove keysym from keydown if a char modifier is down', function() {
@ -263,30 +263,30 @@ describe('Key Event Pipeline Stages', function() {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
switch (times_called++) {
case 2:
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keydown'});
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'});
break;
}
}).keydown({keyCode: 0x41, altGraphKey: true});
}).keydown({code: 'KeyA', keyCode: 0x41, altGraphKey: true});
expect(times_called).to.be.equal(3);
});
it('should not remove keysym from keydown if key is noncharacter', function() {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt, 'bacobjpace').to.be.deep.equal({keyId: 0x09, keysym: 0xff09, type: 'keydown'});
expect(evt, 'tab').to.be.deep.equal({code: 'Tab', keysym: 0xff09, type: 'keydown'});
}).keydown({keyCode: 0x09});
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt, 'ctrl').to.be.deep.equal({keyId: 0x11, keysym: 0xffe3, type: 'keydown'});
expect(evt, 'ctrl').to.be.deep.equal({code: 'ControlLeft', keysym: 0xffe3, type: 'keydown'});
}).keydown({keyCode: 0x11});
});
it('should never remove keysym from keypress', function() {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keypress'});
}).keypress({keyCode: 0x41});
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'});
}).keypress({code: 'KeyA', keyCode: 0x41});
});
it('should never remove keysym from keyup', function() {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: 0x61, type: 'keyup'});
}).keyup({keyCode: 0x41});
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'});
}).keyup({code: 'KeyA', keyCode: 0x41});
});
});
// on keypress, keyup(?), always set keysym
@ -296,31 +296,31 @@ describe('Key Event Pipeline Stages', function() {
describe('Verify that char modifiers are active', function() {
it('should pass keydown events through if there is no stall', function(done) {
var obj = KeyboardUtil.VerifyCharModifier(function(evt){
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x41});
expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x41});
done();
})({type: 'keydown', keyId: 0x41, keysym: 0x41});
})({type: 'keydown', code: 'KeyA', keysym: 0x41});
});
it('should pass keyup events through if there is no stall', function(done) {
var obj = KeyboardUtil.VerifyCharModifier(function(evt){
expect(evt).to.deep.equal({type: 'keyup', keyId: 0x41, keysym: 0x41});
expect(evt).to.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x41});
done();
})({type: 'keyup', keyId: 0x41, keysym: 0x41});
})({type: 'keyup', code: 'KeyA', keysym: 0x41});
});
it('should pass keypress events through if there is no stall', function(done) {
var obj = KeyboardUtil.VerifyCharModifier(function(evt){
expect(evt).to.deep.equal({type: 'keypress', keyId: 0x41, keysym: 0x41});
expect(evt).to.deep.equal({type: 'keypress', code: 'KeyA', keysym: 0x41});
done();
})({type: 'keypress', keyId: 0x41, keysym: 0x41});
})({type: 'keypress', code: 'KeyA', keysym: 0x41});
});
it('should not pass stall events through', function(done){
var obj = KeyboardUtil.VerifyCharModifier(function(evt){
// should only be called once, for the keydown
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x41});
expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x41});
done();
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: 0x41});
obj({type: 'keydown', code: 'KeyA', keysym: 0x41});
});
it('should merge keydown and keypress events if they come after a stall', function(done) {
var next_called = false;
@ -328,13 +328,13 @@ describe('Key Event Pipeline Stages', function() {
// should only be called once, for the keydown
expect(next_called).to.be.false;
next_called = true;
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x44});
expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x44});
done();
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: 0x42});
obj({type: 'keypress', keyId: 0x43, keysym: 0x44});
obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
obj({type: 'keypress', code: 'KeyC', keysym: 0x44});
expect(next_called).to.be.false;
});
it('should preserve modifier attribute when merging if keysyms differ', function(done) {
@ -343,13 +343,13 @@ describe('Key Event Pipeline Stages', function() {
// should only be called once, for the keydown
expect(next_called).to.be.false;
next_called = true;
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x44, escape: [0xffe3]});
expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x44, escape: [0xffe3]});
done();
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: 0x42});
obj({type: 'keypress', keyId: 0x43, keysym: 0x44, escape: [0xffe3]});
obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
obj({type: 'keypress', code: 'KeyC', keysym: 0x44, escape: [0xffe3]});
expect(next_called).to.be.false;
});
it('should not preserve modifier attribute when merging if keysyms are the same', function() {
@ -358,18 +358,18 @@ describe('Key Event Pipeline Stages', function() {
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: 0x42});
obj({type: 'keypress', keyId: 0x43, keysym: 0x42, escape: [0xffe3]});
obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
obj({type: 'keypress', code: 'KeyC', keysym: 0x42, escape: [0xffe3]});
});
it('should not merge keydown and keypress events if there is no stall', function(done) {
var times_called = 0;
var obj = KeyboardUtil.VerifyCharModifier(function(evt){
switch(times_called) {
case 0:
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x42});
expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42});
break;
case 1:
expect(evt).to.deep.equal({type: 'keypress', keyId: 0x43, keysym: 0x44});
expect(evt).to.deep.equal({type: 'keypress', code: 'KeyC', keysym: 0x44});
done();
break;
}
@ -377,21 +377,21 @@ describe('Key Event Pipeline Stages', function() {
++times_called;
});
obj({type: 'keydown', keyId: 0x41, keysym: 0x42});
obj({type: 'keypress', keyId: 0x43, keysym: 0x44});
obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
obj({type: 'keypress', code: 'KeyC', keysym: 0x44});
});
it('should not merge keydown and keypress events if separated by another event', function(done) {
var times_called = 0;
var obj = KeyboardUtil.VerifyCharModifier(function(evt){
switch(times_called) {
case 0:
expect(evt,1).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x42});
expect(evt,1).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42});
break;
case 1:
expect(evt,2).to.deep.equal({type: 'keyup', keyId: 0x43, keysym: 0x44});
expect(evt,2).to.deep.equal({type: 'keyup', code: 'KeyC', keysym: 0x44});
break;
case 2:
expect(evt,3).to.deep.equal({type: 'keypress', keyId: 0x45, keysym: 0x46});
expect(evt,3).to.deep.equal({type: 'keypress', code: 'KeyE', keysym: 0x46});
done();
break;
}
@ -400,9 +400,9 @@ describe('Key Event Pipeline Stages', function() {
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: 0x42});
obj({type: 'keyup', keyId: 0x43, keysym: 0x44});
obj({type: 'keypress', keyId: 0x45, keysym: 0x46});
obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
obj({type: 'keyup', code: 'KeyC', keysym: 0x44});
obj({type: 'keypress', code: 'KeyE', keysym: 0x46});
});
});
@ -411,7 +411,7 @@ describe('Key Event Pipeline Stages', function() {
var obj = KeyboardUtil.TrackKeyState(function(evt) {
expect(true).to.be.false;
});
obj({type: 'keyup', keyId: 0x41});
obj({type: 'keyup', code: 'KeyA'});
});
it('should insert into the queue on keydown if no keys are down', function() {
var times_called = 0;
@ -432,11 +432,11 @@ describe('Key Event Pipeline Stages', function() {
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0x41, keysym: 0x42};
elem = {type: 'keydown', code: 'KeyA', keysym: 0x42};
keysymsdown[0x42] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
elem = {type: 'keyup', code: 'KeyA'};
obj(elem);
expect(elem).to.be.null;
expect(times_called).to.be.equal(2);
@ -460,16 +460,16 @@ describe('Key Event Pipeline Stages', function() {
});
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x41, keysym: 0x42};
elem = {type: 'keypress', code: 'KeyA', keysym: 0x42};
keysymsdown[0x42] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
elem = {type: 'keyup', code: 'KeyA'};
obj(elem);
expect(elem).to.be.null;
expect(times_called).to.be.equal(2);
});
it('should add keysym to last key entry if keyId matches', function() {
it('should add keysym to last key entry if code matches', function() {
// this implies that a single keyup will release both keysyms
var times_called = 0;
var elem = null;
@ -489,19 +489,19 @@ describe('Key Event Pipeline Stages', function() {
});
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x41, keysym: 0x42};
elem = {type: 'keypress', code: 'KeyA', keysym: 0x42};
keysymsdown[0x42] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x41, keysym: 0x43};
elem = {type: 'keypress', code: 'KeyA', keysym: 0x43};
keysymsdown[0x43] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
elem = {type: 'keyup', code: 'KeyA'};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should create new key entry if keyId matches and keysym does not', function() {
it('should create new key entry if code matches and keysym does not', function() {
// this implies that a single keyup will release both keysyms
var times_called = 0;
var elem = null;
@ -521,23 +521,23 @@ describe('Key Event Pipeline Stages', function() {
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: 0x42};
elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
keysymsdown[0x42] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: 0x43};
elem = {type: 'keydown', code: 'Unidentified', keysym: 0x43};
keysymsdown[0x43] = true;
obj(elem);
expect(times_called).to.be.equal(2);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0};
elem = {type: 'keyup', code: 'Unidentified'};
obj(elem);
expect(times_called).to.be.equal(3);
elem = {type: 'keyup', keyId: 0};
elem = {type: 'keyup', code: 'Unidentified'};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should merge key entry if keyIds are zero and keysyms match', function() {
it('should merge key entry if codes are zero and keysyms match', function() {
// this implies that a single keyup will release both keysyms
var times_called = 0;
var elem = null;
@ -557,20 +557,20 @@ describe('Key Event Pipeline Stages', function() {
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: 0x42};
elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
keysymsdown[0x42] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: 0x42};
elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
keysymsdown[0x42] = true;
obj(elem);
expect(times_called).to.be.equal(2);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0};
elem = {type: 'keyup', code: 'Unidentified'};
obj(elem);
expect(times_called).to.be.equal(3);
});
it('should add keysym as separate entry if keyId does not match last event', function() {
it('should add keysym as separate entry if code does not match last event', function() {
// this implies that separate keyups are required
var times_called = 0;
var elem = null;
@ -590,22 +590,22 @@ describe('Key Event Pipeline Stages', function() {
});
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x41, keysym: 0x42};
elem = {type: 'keypress', code: 'KeyA', keysym: 0x42};
keysymsdown[0x42] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x42, keysym: 0x43};
elem = {type: 'keypress', code: 'KeyB', keysym: 0x43};
keysymsdown[0x43] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
elem = {type: 'keyup', code: 'KeyA'};
obj(elem);
expect(times_called).to.be.equal(4);
elem = {type: 'keyup', keyId: 0x42};
elem = {type: 'keyup', code: 'KeyB'};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should add keysym as separate entry if keyId does not match last event and first is zero', function() {
it('should add keysym as separate entry if code does not match last event and first is zero', function() {
// this implies that separate keyups are required
var times_called = 0;
var elem = null;
@ -625,23 +625,23 @@ describe('Key Event Pipeline Stages', function() {
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: 0x42};
elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
keysymsdown[0x42] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0x42, keysym: 0x43};
elem = {type: 'keydown', code: 'KeyB', keysym: 0x43};
keysymsdown[0x43] = true;
obj(elem);
expect(elem).to.be.null;
expect(times_called).to.be.equal(2);
elem = {type: 'keyup', keyId: 0};
elem = {type: 'keyup', code: 'Unidentified'};
obj(elem);
expect(times_called).to.be.equal(3);
elem = {type: 'keyup', keyId: 0x42};
elem = {type: 'keyup', code: 'KeyB'};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should add keysym as separate entry if keyId does not match last event and second is zero', function() {
it('should add keysym as separate entry if code does not match last event and second is zero', function() {
// this implies that a separate keyups are required
var times_called = 0;
var elem = null;
@ -661,18 +661,18 @@ describe('Key Event Pipeline Stages', function() {
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0x41, keysym: 0x42};
elem = {type: 'keydown', code: 'KeyA', keysym: 0x42};
keysymsdown[0x42] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: 0x43};
elem = {type: 'keydown', code: 'Unidentified', keysym: 0x43};
keysymsdown[0x43] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
elem = {type: 'keyup', code: 'KeyA'};
obj(elem);
expect(times_called).to.be.equal(3);
elem = {type: 'keyup', keyId: 0};
elem = {type: 'keyup', code: 'Unidentified'};
obj(elem);
expect(times_called).to.be.equal(4);
});
@ -686,18 +686,18 @@ describe('Key Event Pipeline Stages', function() {
expect(evt.type).to.be.equal('keydown');
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x42, keysym: 0x62});
expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyB', keysym: 0x62});
break;
}
});
obj({type: 'keydown', keyId: 0x41, keysym: 0x61});
obj({type: 'keydown', keyId: 0x42, keysym: 0x62});
obj({type: 'keydown', keyId: 0x43, keysym: 0x63});
obj({type: 'keyup', keyId: 0x42});
obj({type: 'keydown', code: 'KeyA', keysym: 0x61});
obj({type: 'keydown', code: 'KeyB', keysym: 0x62});
obj({type: 'keydown', code: 'KeyC', keysym: 0x63});
obj({type: 'keyup', code: 'KeyB'});
expect(times_called).to.equal(4);
});
it('should pop the first zero keyevent on keyup with zero keyId', function() {
it('should pop the first zero keyevent on keyup with zero code', function() {
var times_called = 0;
var obj = KeyboardUtil.TrackKeyState(function(evt) {
switch (times_called++) {
@ -707,18 +707,18 @@ describe('Key Event Pipeline Stages', function() {
expect(evt.type).to.be.equal('keydown');
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0x61});
expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x61});
break;
}
});
obj({type: 'keydown', keyId: 0, keysym: 0x61});
obj({type: 'keydown', keyId: 0, keysym: 0x62});
obj({type: 'keydown', keyId: 0x41, keysym: 0x63});
obj({type: 'keyup', keyId: 0x0});
obj({type: 'keydown', code: 'Unidentified', keysym: 0x61});
obj({type: 'keydown', code: 'Unidentified', keysym: 0x62});
obj({type: 'keydown', code: 'KeyA', keysym: 0x63});
obj({type: 'keyup', code: 'Unidentified'});
expect(times_called).to.equal(4);
});
it('should pop the last keyevents keysym if no match is found for keyId', function() {
it('should pop the last keyevents keysym if no match is found for code', function() {
var times_called = 0;
var obj = KeyboardUtil.TrackKeyState(function(evt) {
switch (times_called++) {
@ -728,15 +728,15 @@ describe('Key Event Pipeline Stages', function() {
expect(evt.type).to.be.equal('keydown');
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x44, keysym: 0x63});
expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyD', keysym: 0x63});
break;
}
});
obj({type: 'keydown', keyId: 0x41, keysym: 0x61});
obj({type: 'keydown', keyId: 0x42, keysym: 0x62});
obj({type: 'keydown', keyId: 0x43, keysym: 0x63});
obj({type: 'keyup', keyId: 0x44});
obj({type: 'keydown', code: 'KeyA', keysym: 0x61});
obj({type: 'keydown', code: 'KeyB', keysym: 0x62});
obj({type: 'keydown', code: 'KeyC', keysym: 0x63});
obj({type: 'keyup', code: 'KeyD'});
expect(times_called).to.equal(4);
});
describe('Firefox sends keypress even when keydown is suppressed', function() {
@ -747,9 +747,9 @@ describe('Key Event Pipeline Stages', function() {
++times_called;
});
obj({type: 'keydown', keyId: 0x41, keysym: 0x42});
obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
expect(times_called).to.be.equal(1);
obj({type: 'keypress', keyId: 0x41, keysym: 0x43});
obj({type: 'keypress', code: 'KeyA', keysym: 0x43});
});
});
describe('releaseAll', function() {
@ -766,15 +766,15 @@ describe('Key Event Pipeline Stages', function() {
var obj = KeyboardUtil.TrackKeyState(function(evt) {
switch (times_called++) {
case 2:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0x41});
expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x41});
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0x42});
expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x42});
break;
}
});
obj({type: 'keydown', keyId: 0x41, keysym: 0x41});
obj({type: 'keydown', keyId: 0x42, keysym: 0x42});
obj({type: 'keydown', code: 'KeyA', keysym: 0x41});
obj({type: 'keydown', code: 'KeyB', keysym: 0x42});
expect(times_called).to.be.equal(2);
obj({type: 'releaseall'});
expect(times_called).to.be.equal(4);
@ -792,8 +792,8 @@ describe('Key Event Pipeline Stages', function() {
KeyboardUtil.EscapeModifiers(function(evt) {
expect(times_called).to.be.equal(0);
++times_called;
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x42});
})({type: 'keydown', keyId: 0x41, keysym: 0x42});
expect(evt).to.be.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42});
})({type: 'keydown', code: 'KeyA', keysym: 0x42});
expect(times_called).to.be.equal(1);
});
it('should generate fake undo/redo events when a char modifier is down', function() {
@ -801,22 +801,22 @@ describe('Key Event Pipeline Stages', function() {
KeyboardUtil.EscapeModifiers(function(evt) {
switch(times_called++) {
case 0:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0xffe9});
expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0xffe9});
break;
case 1:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: 0xffe3});
expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0xffe3});
break;
case 2:
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: 0x42, escape: [0xffe9, 0xffe3]});
expect(evt).to.be.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42, escape: [0xffe9, 0xffe3]});
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: 0xffe9});
expect(evt).to.be.deep.equal({type: 'keydown', code: 'Unidentified', keysym: 0xffe9});
break;
case 4:
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: 0xffe3});
expect(evt).to.be.deep.equal({type: 'keydown', code: 'Unidentified', keysym: 0xffe3});
break;
}
})({type: 'keydown', keyId: 0x41, keysym: 0x42, escape: [0xffe9, 0xffe3]});
})({type: 'keydown', code: 'KeyA', keysym: 0x42, escape: [0xffe9, 0xffe3]});
expect(times_called).to.be.equal(5);
});
});
@ -826,8 +826,8 @@ describe('Key Event Pipeline Stages', function() {
KeyboardUtil.EscapeModifiers(function(evt) {
expect(times_called).to.be.equal(0);
++times_called;
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: 0x42, escape: [0xfe03]});
})({type: 'keyup', keyId: 0x41, keysym: 0x42, escape: [0xfe03]});
expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x42, escape: [0xfe03]});
})({type: 'keyup', code: 'KeyA', keysym: 0x42, escape: [0xfe03]});
expect(times_called).to.be.equal(1);
});
it('should pass through when a char modifier is not down', function() {
@ -835,8 +835,8 @@ describe('Key Event Pipeline Stages', function() {
KeyboardUtil.EscapeModifiers(function(evt) {
expect(times_called).to.be.equal(0);
++times_called;
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: 0x42});
})({type: 'keyup', keyId: 0x41, keysym: 0x42});
expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x42});
})({type: 'keyup', code: 'KeyA', keysym: 0x42});
expect(times_called).to.be.equal(1);
});
});

View File

@ -50,7 +50,8 @@
WebUtil.load_scripts({
'core': ["base64.js", "websock.js", "des.js", "input/keysym.js",
"input/keysymdef.js", "input/xtscancodes.js", "input/util.js",
"input/devices.js", "display.js", "rfb.js", "inflator.js"],
"input/devices.js", "display.js", "rfb.js", "inflator.js",
"input/vkeys.js"],
'tests': ["playback.js"],
'recordings': [fname]});
} else {