Move keyboard handling in to Keyboard class
Replace the multi stage pipeline system with something simpler. That level of abstraction is not needed.
This commit is contained in:
parent
9e6f71cb75
commit
f7363fd26d
|
@ -22,18 +22,13 @@ const Keyboard = function (defaults) {
|
||||||
this._keyDownList = []; // List of depressed keys
|
this._keyDownList = []; // List of depressed keys
|
||||||
// (even if they are happy)
|
// (even if they are happy)
|
||||||
|
|
||||||
|
this._modifierState = KeyboardUtil.ModifierSync();
|
||||||
|
|
||||||
set_defaults(this, defaults, {
|
set_defaults(this, defaults, {
|
||||||
'target': document,
|
'target': document,
|
||||||
'focused': true
|
'focused': true
|
||||||
});
|
});
|
||||||
|
|
||||||
// create the keyboard handler
|
|
||||||
this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(),
|
|
||||||
KeyboardUtil.TrackKeyState(
|
|
||||||
KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// keep these here so we can refer to them later
|
// keep these here so we can refer to them later
|
||||||
this._eventHandlers = {
|
this._eventHandlers = {
|
||||||
'keyup': this._handleKeyUp.bind(this),
|
'keyup': this._handleKeyUp.bind(this),
|
||||||
|
@ -46,47 +41,220 @@ const Keyboard = function (defaults) {
|
||||||
Keyboard.prototype = {
|
Keyboard.prototype = {
|
||||||
// private methods
|
// private methods
|
||||||
|
|
||||||
_handleRfbEvent: function (e) {
|
_sendKeyEvent: function (keysym, code, down) {
|
||||||
if (this._onKeyEvent) {
|
if (!this._onKeyEvent) {
|
||||||
Log.Debug("onKeyEvent " + (e.type == 'keydown' ? "down" : "up") +
|
return;
|
||||||
", keysym: " + e.keysym);
|
|
||||||
this._onKeyEvent(e.keysym, e.code, e.type == 'keydown');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.Debug("onKeyEvent " + (down ? "down" : "up") +
|
||||||
|
", keysym: " + keysym, ", code: " + code);
|
||||||
|
|
||||||
|
this._onKeyEvent(keysym, code, down);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getKeyCode: function (e) {
|
||||||
|
var code = KeyboardUtil.getKeycode(e);
|
||||||
|
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 (e.keyCode && (e.type !== 'keypress')) {
|
||||||
|
code = 'Platform' + e.keyCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
},
|
},
|
||||||
|
|
||||||
_handleKeyDown: function (e) {
|
_handleKeyDown: function (e) {
|
||||||
if (!this._focused) { return; }
|
if (!this._focused) { return; }
|
||||||
|
|
||||||
if (this._handler.keydown(e)) {
|
this._modifierState.keydown(e);
|
||||||
// Suppress bubbling/default actions
|
|
||||||
|
var code = this._getKeyCode(e);
|
||||||
|
var keysym = KeyboardUtil.getKeysym(e);
|
||||||
|
|
||||||
|
// If this is a legacy browser then we'll need to wait for
|
||||||
|
// a keypress event as well. Otherwise we supress the
|
||||||
|
// browser's handling at this point
|
||||||
|
if (keysym) {
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if a char modifier is pressed, get the keys it consists
|
||||||
|
// of (on Windows, AltGr is equivalent to Ctrl+Alt)
|
||||||
|
var active = this._modifierState.activeCharModifier();
|
||||||
|
|
||||||
|
// If we have a char modifier down, and we're able to
|
||||||
|
// determine a keysym reliably then (a) we know to treat
|
||||||
|
// the modifier as a char modifier, and (b) we'll have to
|
||||||
|
// "escape" the modifier to undo the modifier when sending
|
||||||
|
// the char.
|
||||||
|
if (active && keysym) {
|
||||||
|
var isCharModifier = false;
|
||||||
|
for (var i = 0; i < active.length; ++i) {
|
||||||
|
if (active[i] === keysym) {
|
||||||
|
isCharModifier = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isCharModifier) {
|
||||||
|
var escape = this._modifierState.activeCharModifier();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var last;
|
||||||
|
if (this._keyDownList.length === 0) {
|
||||||
|
last = null;
|
||||||
} else {
|
} else {
|
||||||
// Allow the event to bubble and become a keyPress event which
|
last = this._keyDownList[this._keyDownList.length-1];
|
||||||
// will have the character code translated
|
}
|
||||||
|
|
||||||
|
// insert a new entry if last seen key was different.
|
||||||
|
if (!last || code === 'Unidentified' || last.code !== code) {
|
||||||
|
last = {code: code, keysyms: {}};
|
||||||
|
this._keyDownList.push(last);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for keypress?
|
||||||
|
if (!keysym) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure last event contains this keysym (a single "logical" keyevent
|
||||||
|
// can cause multiple key events to be sent to the VNC server)
|
||||||
|
last.keysyms[keysym] = keysym;
|
||||||
|
last.ignoreKeyPress = true;
|
||||||
|
|
||||||
|
// undo modifiers
|
||||||
|
if (escape) {
|
||||||
|
for (var i = 0; i < escape.length; ++i) {
|
||||||
|
this._sendKeyEvent(escape[i], 'Unidentified', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the character event
|
||||||
|
this._sendKeyEvent(keysym, code, true);
|
||||||
|
|
||||||
|
// redo modifiers
|
||||||
|
if (escape) {
|
||||||
|
for (i = 0; i < escape.length; ++i) {
|
||||||
|
this._sendKeyEvent(escape[i], 'Unidentified', true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Legacy event for browsers without code/key
|
||||||
_handleKeyPress: function (e) {
|
_handleKeyPress: function (e) {
|
||||||
if (!this._focused) { return; }
|
if (!this._focused) { return; }
|
||||||
|
|
||||||
if (this._handler.keypress(e)) {
|
stopEvent(e);
|
||||||
// Suppress bubbling/default actions
|
|
||||||
stopEvent(e);
|
var code = this._getKeyCode(e);
|
||||||
|
var keysym = KeyboardUtil.getKeysym(e);
|
||||||
|
|
||||||
|
// if a char modifier is pressed, get the keys it consists
|
||||||
|
// of (on Windows, AltGr is equivalent to Ctrl+Alt)
|
||||||
|
var active = this._modifierState.activeCharModifier();
|
||||||
|
|
||||||
|
// If we have a char modifier down, and we're able to
|
||||||
|
// determine a keysym reliably then (a) we know to treat
|
||||||
|
// the modifier as a char modifier, and (b) we'll have to
|
||||||
|
// "escape" the modifier to undo the modifier when sending
|
||||||
|
// the char.
|
||||||
|
if (active && keysym) {
|
||||||
|
var isCharModifier = false;
|
||||||
|
for (var i = 0; i < active.length; ++i) {
|
||||||
|
if (active[i] === keysym) {
|
||||||
|
isCharModifier = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isCharModifier) {
|
||||||
|
var escape = this._modifierState.activeCharModifier();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var last;
|
||||||
|
if (this._keyDownList.length === 0) {
|
||||||
|
last = null;
|
||||||
|
} else {
|
||||||
|
last = this._keyDownList[this._keyDownList.length-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!last) {
|
||||||
|
last = {code: code, keysyms: {}};
|
||||||
|
this._keyDownList.push(last);
|
||||||
|
}
|
||||||
|
if (!keysym) {
|
||||||
|
console.log('keypress with no keysym:', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't expect a keypress, and already sent a keydown to the VNC server
|
||||||
|
// based on the keydown, make sure to skip this event.
|
||||||
|
if (last.ignoreKeyPress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
last.keysyms[keysym] = keysym;
|
||||||
|
|
||||||
|
// undo modifiers
|
||||||
|
if (escape) {
|
||||||
|
for (var i = 0; i < escape.length; ++i) {
|
||||||
|
this._sendKeyEvent(escape[i], 'Unidentified', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the character event
|
||||||
|
this._sendKeyEvent(keysym, code, true);
|
||||||
|
|
||||||
|
// redo modifiers
|
||||||
|
if (escape) {
|
||||||
|
for (i = 0; i < escape.length; ++i) {
|
||||||
|
this._sendKeyEvent(escape[i], 'Unidentified', true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_handleKeyUp: function (e) {
|
_handleKeyUp: function (e) {
|
||||||
if (!this._focused) { return; }
|
if (!this._focused) { return; }
|
||||||
|
|
||||||
if (this._handler.keyup(e)) {
|
stopEvent(e);
|
||||||
// Suppress bubbling/default actions
|
|
||||||
stopEvent(e);
|
this._modifierState.keyup(e);
|
||||||
|
|
||||||
|
var code = this._getKeyCode(e);
|
||||||
|
|
||||||
|
if (this._keyDownList.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var idx = null;
|
||||||
|
// do we have a matching key tracked as being down?
|
||||||
|
for (var i = 0; i !== this._keyDownList.length; ++i) {
|
||||||
|
if (this._keyDownList[i].code === code) {
|
||||||
|
idx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if we couldn't find a match (it happens), assume it was the last key pressed
|
||||||
|
if (idx === null) {
|
||||||
|
idx = this._keyDownList.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = this._keyDownList.splice(idx, 1)[0];
|
||||||
|
for (var key in item.keysyms) {
|
||||||
|
this._sendKeyEvent(item.keysyms[key], code, false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_allKeysUp: function () {
|
_allKeysUp: function () {
|
||||||
Log.Debug(">> Keyboard.allKeysUp");
|
Log.Debug(">> Keyboard.allKeysUp");
|
||||||
this._handler.releaseAll();
|
for (var i = 0; i < this._keyDownList.length; i++) {
|
||||||
|
var item = this._keyDownList[i];
|
||||||
|
for (var key in item.keysyms) {
|
||||||
|
this._sendKeyEvent(item.keysyms[key], 'Unidentified', false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._keyDownList = [];
|
||||||
Log.Debug("<< Keyboard.allKeysUp");
|
Log.Debug("<< Keyboard.allKeysUp");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -254,187 +254,3 @@ export function getKeysym(evt){
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Takes a DOM keyboard event and:
|
|
||||||
// - determines which keysym it represents
|
|
||||||
// - determines a code identifying the key that was pressed (corresponding to the code/keyCode properties on the DOM event)
|
|
||||||
// - marks each event with an 'escape' property if a modifier was down which should be "escaped"
|
|
||||||
// This information is collected into an object which is passed to the next() function. (one call per event)
|
|
||||||
export function KeyEventDecoder (modifierState, next) {
|
|
||||||
"use strict";
|
|
||||||
function process(evt, type) {
|
|
||||||
var result = {type: type};
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress?
|
|
||||||
// "special" keys like enter, tab or backspace don't send keypress events,
|
|
||||||
// and some browsers don't send keypresses at all if a modifier is down
|
|
||||||
if (keysym) {
|
|
||||||
result.keysym = keysym;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should we prevent the browser from handling the event?
|
|
||||||
// Doing so on a keydown (in most browsers) prevents keypress from being generated
|
|
||||||
// so only do that if we have to.
|
|
||||||
var suppress = type !== 'keydown' || !!keysym;
|
|
||||||
|
|
||||||
// if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt)
|
|
||||||
var active = modifierState.activeCharModifier();
|
|
||||||
|
|
||||||
// If we have a char modifier down, and we're able to determine a keysym reliably
|
|
||||||
// then (a) we know to treat the modifier as a char modifier,
|
|
||||||
// and (b) we'll have to "escape" the modifier to undo the modifier when sending the char.
|
|
||||||
if (active && keysym) {
|
|
||||||
var isCharModifier = false;
|
|
||||||
for (var i = 0; i < active.length; ++i) {
|
|
||||||
if (active[i] === keysym) {
|
|
||||||
isCharModifier = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type === 'keypress' && !isCharModifier) {
|
|
||||||
result.escape = modifierState.activeCharModifier();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
next(result);
|
|
||||||
|
|
||||||
return suppress;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
keydown: function(evt) {
|
|
||||||
modifierState.keydown(evt);
|
|
||||||
return process(evt, 'keydown');
|
|
||||||
},
|
|
||||||
keypress: function(evt) {
|
|
||||||
return process(evt, 'keypress');
|
|
||||||
},
|
|
||||||
keyup: function(evt) {
|
|
||||||
modifierState.keyup(evt);
|
|
||||||
return process(evt, 'keyup');
|
|
||||||
},
|
|
||||||
releaseAll: function() { next({type: 'releaseall'}); }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Keeps track of which keys we (and the server) believe are down
|
|
||||||
// When a keyup is received, match it against this list, to determine the corresponding keysym(s)
|
|
||||||
// in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars
|
|
||||||
// key repeat events should be merged into a single entry.
|
|
||||||
// Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess
|
|
||||||
export function TrackKeyState (next) {
|
|
||||||
"use strict";
|
|
||||||
var state = [];
|
|
||||||
|
|
||||||
return function (evt) {
|
|
||||||
var last = state.length !== 0 ? state[state.length-1] : null;
|
|
||||||
|
|
||||||
switch (evt.type) {
|
|
||||||
case 'keydown':
|
|
||||||
// insert a new entry if last seen key was different.
|
|
||||||
if (!last || evt.code === 'Unidentified' || last.code !== evt.code) {
|
|
||||||
last = {code: evt.code, keysyms: {}};
|
|
||||||
state.push(last);
|
|
||||||
}
|
|
||||||
if (evt.keysym) {
|
|
||||||
// make sure last event contains this keysym (a single "logical" keyevent
|
|
||||||
// can cause multiple key events to be sent to the VNC server)
|
|
||||||
last.keysyms[evt.keysym] = evt.keysym;
|
|
||||||
last.ignoreKeyPress = true;
|
|
||||||
next(evt);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'keypress':
|
|
||||||
if (!last) {
|
|
||||||
last = {code: evt.code, keysyms: {}};
|
|
||||||
state.push(last);
|
|
||||||
}
|
|
||||||
if (!evt.keysym) {
|
|
||||||
console.log('keypress with no keysym:', evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we didn't expect a keypress, and already sent a keydown to the VNC server
|
|
||||||
// based on the keydown, make sure to skip this event.
|
|
||||||
if (evt.keysym && !last.ignoreKeyPress) {
|
|
||||||
last.keysyms[evt.keysym] = evt.keysym;
|
|
||||||
evt.type = 'keydown';
|
|
||||||
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) {
|
|
||||||
idx = state.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var item = state.splice(idx, 1)[0];
|
|
||||||
// for each keysym tracked by this key entry, clone the current event and override the keysym
|
|
||||||
var clone = (function(){
|
|
||||||
function Clone(){}
|
|
||||||
return function (obj) { Clone.prototype=obj; return new Clone(); };
|
|
||||||
}());
|
|
||||||
for (var key in item.keysyms) {
|
|
||||||
var out = clone(evt);
|
|
||||||
out.keysym = item.keysyms[key];
|
|
||||||
next(out);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'releaseall':
|
|
||||||
/* jshint shadow: true */
|
|
||||||
for (var i = 0; i < state.length; ++i) {
|
|
||||||
for (var key in state[i].keysyms) {
|
|
||||||
var keysym = state[i].keysyms[key];
|
|
||||||
next({code: 'Unidentified', keysym: keysym, type: 'keyup'});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* jshint shadow: false */
|
|
||||||
state = [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @),
|
|
||||||
// then the modifier must be "undone" before sending the @, and "redone" afterwards.
|
|
||||||
export function EscapeModifiers (next) {
|
|
||||||
"use strict";
|
|
||||||
return function(evt) {
|
|
||||||
if (evt.type !== 'keydown' || evt.escape === undefined) {
|
|
||||||
next(evt);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// undo modifiers
|
|
||||||
for (var i = 0; i < evt.escape.length; ++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', code: 'Unidentified', keysym: evt.escape[i]});
|
|
||||||
}
|
|
||||||
/* jshint shadow: false */
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,612 +1,251 @@
|
||||||
var assert = chai.assert;
|
var assert = chai.assert;
|
||||||
var expect = chai.expect;
|
var expect = chai.expect;
|
||||||
|
|
||||||
|
import { Keyboard } from '../core/input/devices.js';
|
||||||
import keysyms from '../core/input/keysymdef.js';
|
import keysyms from '../core/input/keysymdef.js';
|
||||||
import * as KeyboardUtil from '../core/input/util.js';
|
import * as KeyboardUtil from '../core/input/util.js';
|
||||||
|
|
||||||
/* jshint newcap: false, expr: true */
|
/* jshint newcap: false, expr: true */
|
||||||
describe('Key Event Pipeline Stages', function() {
|
describe('Key Event Handling', function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
// The real KeyboardEvent constructor might not work everywhere we
|
||||||
|
// want to run these tests
|
||||||
|
function keyevent(typeArg, KeyboardEventInit) {
|
||||||
|
var e = { type: typeArg };
|
||||||
|
for (var key in KeyboardEventInit) {
|
||||||
|
e[key] = KeyboardEventInit[key];
|
||||||
|
}
|
||||||
|
e.stopPropagation = sinon.spy();
|
||||||
|
e.preventDefault = sinon.spy();
|
||||||
|
return e;
|
||||||
|
};
|
||||||
|
|
||||||
describe('Decode Keyboard Events', function() {
|
describe('Decode Keyboard Events', function() {
|
||||||
it('should pass events to the next stage', function(done) {
|
it('should decode keydown events', function(done) {
|
||||||
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
|
var kbd = new Keyboard({
|
||||||
expect(evt).to.be.an.object;
|
onKeyEvent: function(keysym, code, down) {
|
||||||
|
expect(keysym).to.be.equal(0x61);
|
||||||
|
expect(code).to.be.equal('KeyA');
|
||||||
|
expect(down).to.be.equal(true);
|
||||||
done();
|
done();
|
||||||
}).keydown({code: 'KeyA', key: 'a'});
|
}});
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
|
||||||
});
|
});
|
||||||
it('should pass the right keysym through', function(done) {
|
it('should decode keyup events', function(done) {
|
||||||
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
|
var calls = 0;
|
||||||
expect(evt.keysym).to.be.deep.equal(0x61);
|
var kbd = new Keyboard({
|
||||||
done();
|
onKeyEvent: function(keysym, code, down) {
|
||||||
}).keypress({code: 'KeyA', key: 'a'});
|
expect(keysym).to.be.equal(0x61);
|
||||||
|
expect(code).to.be.equal('KeyA');
|
||||||
|
if (calls++ === 1) {
|
||||||
|
expect(down).to.be.equal(false);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
|
||||||
|
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
|
||||||
});
|
});
|
||||||
it('should pass the right keyid through', function(done) {
|
|
||||||
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
|
describe('Legacy keypress Events', function() {
|
||||||
expect(evt).to.have.property('code', 'KeyA');
|
it('should wait for keypress when needed', function() {
|
||||||
done();
|
var callback = sinon.spy();
|
||||||
}).keydown({code: 'KeyA', key: 'a'});
|
var kbd = new Keyboard({onKeyEvent: callback});
|
||||||
});
|
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
|
||||||
it('should forward keydown events with the right type', function(done) {
|
expect(callback).to.not.have.been.called;
|
||||||
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
|
});
|
||||||
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'});
|
it('should decode keypress events', function(done) {
|
||||||
done();
|
var kbd = new Keyboard({
|
||||||
}).keydown({code: 'KeyA', key: 'a'});
|
onKeyEvent: function(keysym, code, down) {
|
||||||
});
|
expect(keysym).to.be.equal(0x61);
|
||||||
it('should forward keyup events with the right type', function(done) {
|
expect(code).to.be.equal('KeyA');
|
||||||
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
|
expect(down).to.be.equal(true);
|
||||||
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'});
|
done();
|
||||||
done();
|
}});
|
||||||
}).keyup({code: 'KeyA', key: 'a'});
|
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
|
||||||
});
|
kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61}));
|
||||||
it('should forward keypress events with the right type', function(done) {
|
});
|
||||||
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
|
|
||||||
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'});
|
|
||||||
done();
|
|
||||||
}).keypress({code: 'KeyA', key: 'a'});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('suppress the right events at the right time', function() {
|
describe('suppress the right events at the right time', function() {
|
||||||
it('should suppress anything while a shortcut modifier is down', function() {
|
it('should suppress anything with a valid key', function() {
|
||||||
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
|
var kbd = new Keyboard({});
|
||||||
|
var evt = keyevent('keydown', {code: 'KeyA', key: 'a'});
|
||||||
obj.keydown({code: 'ControlLeft'});
|
kbd._handleKeyDown(evt);
|
||||||
expect(obj.keydown({code: 'KeyA', key: 'a'})).to.be.true;
|
expect(evt.preventDefault).to.have.been.called;
|
||||||
expect(obj.keydown({code: 'Space', key: ' '})).to.be.true;
|
evt = keyevent('keyup', {code: 'KeyA', key: 'a'});
|
||||||
expect(obj.keydown({code: 'Digit1', key: '1'})).to.be.true;
|
kbd._handleKeyUp(evt);
|
||||||
expect(obj.keydown({code: 'IntlBackslash', key: '<'})).to.be.true;
|
expect(evt.preventDefault).to.have.been.called;
|
||||||
expect(obj.keydown({code: 'Semicolon', key: 'ø'})).to.be.true;
|
|
||||||
});
|
});
|
||||||
it('should suppress non-character keys', function() {
|
it('should not suppress keys without key', function() {
|
||||||
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
|
var kbd = new Keyboard({});
|
||||||
|
var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
|
||||||
expect(obj.keydown({code: 'Backspace'}), 'a').to.be.true;
|
kbd._handleKeyDown(evt);
|
||||||
expect(obj.keydown({code: 'Tab'}), 'b').to.be.true;
|
expect(evt.preventDefault).to.not.have.been.called;
|
||||||
expect(obj.keydown({code: 'ControlLeft'}), 'd').to.be.true;
|
|
||||||
expect(obj.keydown({code: 'AltLeft'}), 'e').to.be.true;
|
|
||||||
});
|
});
|
||||||
it('should generate event for shift keydown', function() {
|
it('should suppress the following keypress event', function() {
|
||||||
var called = false;
|
var kbd = new Keyboard({});
|
||||||
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
|
var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
|
||||||
expect(evt).to.have.property('keysym');
|
kbd._handleKeyDown(evt);
|
||||||
called = true;
|
var evt = keyevent('keypress', {code: 'KeyA', charCode: 0x41});
|
||||||
}).keydown({code: 'ShiftLeft'});
|
kbd._handleKeyPress(evt);
|
||||||
expect(called).to.be.true;
|
expect(evt.preventDefault).to.have.been.called;
|
||||||
});
|
|
||||||
it('should suppress character keys with key', function() {
|
|
||||||
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
|
|
||||||
|
|
||||||
expect(obj.keydown({code: 'KeyA', key: 'a'})).to.be.true;
|
|
||||||
expect(obj.keydown({code: 'Digit1', key: '1'})).to.be.true;
|
|
||||||
expect(obj.keydown({code: 'IntlBackslash', key: '<'})).to.be.true;
|
|
||||||
expect(obj.keydown({code: 'Semicolon', key: 'ø'})).to.be.true;
|
|
||||||
});
|
|
||||||
it('should not suppress character keys without key', function() {
|
|
||||||
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
|
|
||||||
|
|
||||||
expect(obj.keydown({code: 'KeyA'})).to.be.false;
|
|
||||||
expect(obj.keydown({code: 'Digit1'})).to.be.false;
|
|
||||||
expect(obj.keydown({code: 'IntlBackslash'})).to.be.false;
|
|
||||||
expect(obj.keydown({code: 'Semicolon'})).to.be.false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('Keypress and keyup events', function() {
|
|
||||||
it('should always suppress event propagation', function() {
|
|
||||||
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
|
|
||||||
|
|
||||||
expect(obj.keypress({code: 'KeyA', key: 'a'})).to.be.true;
|
|
||||||
expect(obj.keypress({code: 'IntlBackslash', key: '<'})).to.be.true;
|
|
||||||
expect(obj.keypress({code: 'ControlLeft', key: 'Control'})).to.be.true;
|
|
||||||
|
|
||||||
expect(obj.keyup({code: 'KeyA', key: 'a'})).to.be.true;
|
|
||||||
expect(obj.keyup({code: 'IntlBackslash', key: '<'})).to.be.true;
|
|
||||||
expect(obj.keyup({code: 'ControlLeft', key: 'Control'})).to.be.true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('mark events if a char modifier is down', function() {
|
|
||||||
it('should not mark modifiers on a keydown event', function() {
|
|
||||||
var times_called = 0;
|
|
||||||
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
|
|
||||||
switch (times_called++) {
|
|
||||||
case 0: //altgr
|
|
||||||
break;
|
|
||||||
case 1: // 'a'
|
|
||||||
expect(evt).to.not.have.property('escape');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
obj.keydown({code: 'AltRight', key: 'AltGraph'})
|
|
||||||
obj.keydown({code: 'KeyA', key: 'a'});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should indicate on events if a single-key char modifier is down', function(done) {
|
|
||||||
var times_called = 0;
|
|
||||||
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
|
|
||||||
switch (times_called++) {
|
|
||||||
case 0: //altgr
|
|
||||||
break;
|
|
||||||
case 1: // 'a'
|
|
||||||
expect(evt).to.be.deep.equal({
|
|
||||||
type: 'keypress',
|
|
||||||
code: 'KeyA',
|
|
||||||
keysym: 0x61,
|
|
||||||
escape: [0xfe03]
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
obj.keydown({code: 'AltRight', key: 'AltGraph'})
|
|
||||||
obj.keypress({code: 'KeyA', key: 'a'});
|
|
||||||
});
|
|
||||||
it('should indicate on events if a multi-key char modifier is down', function(done) {
|
|
||||||
var times_called = 0;
|
|
||||||
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xffe9, 0xffe3]), function(evt) {
|
|
||||||
switch (times_called++) {
|
|
||||||
case 0: //ctrl
|
|
||||||
break;
|
|
||||||
case 1: //alt
|
|
||||||
break;
|
|
||||||
case 2: // 'a'
|
|
||||||
expect(evt).to.be.deep.equal({
|
|
||||||
type: 'keypress',
|
|
||||||
code: 'KeyA',
|
|
||||||
keysym: 0x61,
|
|
||||||
escape: [0xffe9, 0xffe3]
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
obj.keydown({code: 'ControlLeft'});
|
|
||||||
obj.keydown({code: 'AltLeft'});
|
|
||||||
obj.keypress({code: 'KeyA', key: 'a'});
|
|
||||||
});
|
|
||||||
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) {
|
|
||||||
expect(evt).to.not.have.property('escape');
|
|
||||||
});
|
|
||||||
|
|
||||||
obj.keydown({code: 'AltRight', key: 'AltGraph'})
|
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Track Key State', function() {
|
describe('Track Key State', function() {
|
||||||
|
it('should send release using the same keysym as the press', function(done) {
|
||||||
|
var kbd = new Keyboard({
|
||||||
|
onKeyEvent: function(keysym, code, down) {
|
||||||
|
expect(keysym).to.be.equal(0x61);
|
||||||
|
expect(code).to.be.equal('KeyA');
|
||||||
|
if (!down) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
|
||||||
|
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'}));
|
||||||
|
});
|
||||||
it('should do nothing on keyup events if no keys are down', function() {
|
it('should do nothing on keyup events if no keys are down', function() {
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
var callback = sinon.spy();
|
||||||
expect(true).to.be.false;
|
var kbd = new Keyboard({onKeyEvent: callback});
|
||||||
});
|
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
|
||||||
obj({type: 'keyup', code: 'KeyA'});
|
expect(callback).to.not.have.been.called;
|
||||||
});
|
});
|
||||||
it('should insert into the queue on keydown if no keys are down', function() {
|
it('should send a key release for each key press with the same code', function() {
|
||||||
var times_called = 0;
|
var callback = sinon.spy();
|
||||||
var elem = null;
|
var kbd = new Keyboard({onKeyEvent: callback});
|
||||||
var keysymsdown = {};
|
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'}));
|
||||||
++times_called;
|
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA'}));
|
||||||
if (elem.type == 'keyup') {
|
expect(callback.callCount).to.be.equal(4);
|
||||||
expect(evt).to.have.property('keysym');
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
delete keysymsdown[evt.keysym];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
expect(evt).to.be.deep.equal(elem);
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
}
|
|
||||||
elem = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keydown', code: 'KeyA', keysym: 0x42};
|
|
||||||
keysymsdown[0x42] = true;
|
|
||||||
obj(elem);
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keyup', code: 'KeyA'};
|
|
||||||
obj(elem);
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
expect(times_called).to.be.equal(2);
|
|
||||||
});
|
});
|
||||||
it('should insert into the queue on keypress if no keys are down', function() {
|
|
||||||
var times_called = 0;
|
|
||||||
var elem = null;
|
|
||||||
var keysymsdown = {};
|
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
|
||||||
++times_called;
|
|
||||||
if (elem.type == 'keyup') {
|
|
||||||
expect(evt).to.have.property('keysym');
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
delete keysymsdown[evt.keysym];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
expect(evt).to.be.deep.equal(elem);
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
}
|
|
||||||
elem = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keypress', code: 'KeyA', keysym: 0x42};
|
|
||||||
keysymsdown[0x42] = true;
|
|
||||||
obj(elem);
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
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 code matches', function() {
|
|
||||||
// this implies that a single keyup will release both keysyms
|
|
||||||
var times_called = 0;
|
|
||||||
var elem = null;
|
|
||||||
var keysymsdown = {};
|
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
|
||||||
++times_called;
|
|
||||||
if (elem.type == 'keyup') {
|
|
||||||
expect(evt).to.have.property('keysym');
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
delete keysymsdown[evt.keysym];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
expect(evt).to.be.deep.equal(elem);
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
elem = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keypress', code: 'KeyA', keysym: 0x42};
|
|
||||||
keysymsdown[0x42] = true;
|
|
||||||
obj(elem);
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keypress', code: 'KeyA', keysym: 0x43};
|
|
||||||
keysymsdown[0x43] = true;
|
|
||||||
obj(elem);
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keyup', code: 'KeyA'};
|
|
||||||
obj(elem);
|
|
||||||
expect(times_called).to.be.equal(4);
|
|
||||||
});
|
|
||||||
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;
|
|
||||||
var keysymsdown = {};
|
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
|
||||||
++times_called;
|
|
||||||
if (elem.type == 'keyup') {
|
|
||||||
expect(evt).to.have.property('keysym');
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
delete keysymsdown[evt.keysym];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
expect(evt).to.be.deep.equal(elem);
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
elem = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
|
|
||||||
keysymsdown[0x42] = true;
|
|
||||||
obj(elem);
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
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', code: 'Unidentified'};
|
|
||||||
obj(elem);
|
|
||||||
expect(times_called).to.be.equal(3);
|
|
||||||
elem = {type: 'keyup', code: 'Unidentified'};
|
|
||||||
obj(elem);
|
|
||||||
expect(times_called).to.be.equal(4);
|
|
||||||
});
|
|
||||||
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;
|
|
||||||
var keysymsdown = {};
|
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
|
||||||
++times_called;
|
|
||||||
if (elem.type == 'keyup') {
|
|
||||||
expect(evt).to.have.property('keysym');
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
delete keysymsdown[evt.keysym];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
expect(evt).to.be.deep.equal(elem);
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
elem = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
|
|
||||||
keysymsdown[0x42] = true;
|
|
||||||
obj(elem);
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
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', code: 'Unidentified'};
|
|
||||||
obj(elem);
|
|
||||||
expect(times_called).to.be.equal(3);
|
|
||||||
});
|
|
||||||
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;
|
|
||||||
var keysymsdown = {};
|
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
|
||||||
++times_called;
|
|
||||||
if (elem.type == 'keyup') {
|
|
||||||
expect(evt).to.have.property('keysym');
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
delete keysymsdown[evt.keysym];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
expect(evt).to.be.deep.equal(elem);
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
elem = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keypress', code: 'KeyA', keysym: 0x42};
|
|
||||||
keysymsdown[0x42] = true;
|
|
||||||
obj(elem);
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keypress', code: 'KeyB', keysym: 0x43};
|
|
||||||
keysymsdown[0x43] = true;
|
|
||||||
obj(elem);
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keyup', code: 'KeyA'};
|
|
||||||
obj(elem);
|
|
||||||
expect(times_called).to.be.equal(4);
|
|
||||||
elem = {type: 'keyup', code: 'KeyB'};
|
|
||||||
obj(elem);
|
|
||||||
expect(times_called).to.be.equal(4);
|
|
||||||
});
|
|
||||||
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;
|
|
||||||
var keysymsdown = {};
|
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
|
||||||
++times_called;
|
|
||||||
if (elem.type == 'keyup') {
|
|
||||||
expect(evt).to.have.property('keysym');
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
delete keysymsdown[evt.keysym];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
expect(evt).to.be.deep.equal(elem);
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
elem = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
|
|
||||||
keysymsdown[0x42] = true;
|
|
||||||
obj(elem);
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
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', code: 'Unidentified'};
|
|
||||||
obj(elem);
|
|
||||||
expect(times_called).to.be.equal(3);
|
|
||||||
elem = {type: 'keyup', code: 'KeyB'};
|
|
||||||
obj(elem);
|
|
||||||
expect(times_called).to.be.equal(4);
|
|
||||||
});
|
|
||||||
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;
|
|
||||||
var keysymsdown = {};
|
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
|
||||||
++times_called;
|
|
||||||
if (elem.type == 'keyup') {
|
|
||||||
expect(evt).to.have.property('keysym');
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
delete keysymsdown[evt.keysym];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
expect(evt).to.be.deep.equal(elem);
|
|
||||||
expect (keysymsdown[evt.keysym]).to.not.be.undefined;
|
|
||||||
elem = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keydown', code: 'KeyA', keysym: 0x42};
|
|
||||||
keysymsdown[0x42] = true;
|
|
||||||
obj(elem);
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keydown', code: 'Unidentified', keysym: 0x43};
|
|
||||||
keysymsdown[0x43] = true;
|
|
||||||
obj(elem);
|
|
||||||
expect(elem).to.be.null;
|
|
||||||
elem = {type: 'keyup', code: 'KeyA'};
|
|
||||||
obj(elem);
|
|
||||||
expect(times_called).to.be.equal(3);
|
|
||||||
elem = {type: 'keyup', code: 'Unidentified'};
|
|
||||||
obj(elem);
|
|
||||||
expect(times_called).to.be.equal(4);
|
|
||||||
});
|
|
||||||
it('should pop matching key event on keyup', function() {
|
|
||||||
var times_called = 0;
|
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
|
||||||
switch (times_called++) {
|
|
||||||
case 0:
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
expect(evt.type).to.be.equal('keydown');
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyB', keysym: 0x62});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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 code', function() {
|
|
||||||
var times_called = 0;
|
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
|
||||||
switch (times_called++) {
|
|
||||||
case 0:
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
expect(evt.type).to.be.equal('keydown');
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x61});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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 code', function() {
|
|
||||||
var times_called = 0;
|
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
|
||||||
switch (times_called++) {
|
|
||||||
case 0:
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
expect(evt.type).to.be.equal('keydown');
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyD', keysym: 0x63});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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() {
|
|
||||||
it('should discard the keypress', function() {
|
|
||||||
var times_called = 0;
|
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
|
||||||
expect(times_called).to.be.equal(0);
|
|
||||||
++times_called;
|
|
||||||
});
|
|
||||||
|
|
||||||
obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
|
|
||||||
expect(times_called).to.be.equal(1);
|
|
||||||
obj({type: 'keypress', code: 'KeyA', keysym: 0x43});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('releaseAll', function() {
|
|
||||||
it('should do nothing if no keys have been pressed', function() {
|
|
||||||
var times_called = 0;
|
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
|
||||||
++times_called;
|
|
||||||
});
|
|
||||||
obj({type: 'releaseall'});
|
|
||||||
expect(times_called).to.be.equal(0);
|
|
||||||
});
|
|
||||||
it('should release the keys that have been pressed', function() {
|
|
||||||
var times_called = 0;
|
|
||||||
var obj = KeyboardUtil.TrackKeyState(function(evt) {
|
|
||||||
switch (times_called++) {
|
|
||||||
case 2:
|
|
||||||
expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x41});
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x42});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
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);
|
|
||||||
obj({type: 'releaseall'});
|
|
||||||
expect(times_called).to.be.equal(4);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Escape Modifiers', function() {
|
describe('Escape Modifiers', function() {
|
||||||
describe('Keydown', function() {
|
var origNavigator;
|
||||||
it('should pass through when a char modifier is not down', function() {
|
beforeEach(function () {
|
||||||
var times_called = 0;
|
// window.navigator is a protected read-only property in many
|
||||||
KeyboardUtil.EscapeModifiers(function(evt) {
|
// environments, so we need to redefine it whilst running these
|
||||||
expect(times_called).to.be.equal(0);
|
// tests.
|
||||||
++times_called;
|
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||||
expect(evt).to.be.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42});
|
if (origNavigator === undefined) {
|
||||||
})({type: 'keydown', code: 'KeyA', keysym: 0x42});
|
// Object.getOwnPropertyDescriptor() doesn't work
|
||||||
expect(times_called).to.be.equal(1);
|
// properly in any version of IE
|
||||||
});
|
this.skip();
|
||||||
it('should generate fake undo/redo events when a char modifier is down', function() {
|
}
|
||||||
var times_called = 0;
|
|
||||||
KeyboardUtil.EscapeModifiers(function(evt) {
|
Object.defineProperty(window, "navigator", {value: {}});
|
||||||
switch(times_called++) {
|
if (window.navigator.platform !== undefined) {
|
||||||
case 0:
|
// Object.defineProperty() doesn't work properly in old
|
||||||
expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0xffe9});
|
// versions of Chrome
|
||||||
break;
|
this.skip();
|
||||||
case 1:
|
}
|
||||||
expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0xffe3});
|
|
||||||
break;
|
window.navigator.platform = "Windows x86_64";
|
||||||
case 2:
|
|
||||||
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', code: 'Unidentified', keysym: 0xffe9});
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
expect(evt).to.be.deep.equal({type: 'keydown', code: 'Unidentified', keysym: 0xffe3});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
})({type: 'keydown', code: 'KeyA', keysym: 0x42, escape: [0xffe9, 0xffe3]});
|
|
||||||
expect(times_called).to.be.equal(5);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe('Keyup', function() {
|
afterEach(function () {
|
||||||
it('should pass through when a char modifier is down', function() {
|
Object.defineProperty(window, "navigator", origNavigator);
|
||||||
var times_called = 0;
|
});
|
||||||
KeyboardUtil.EscapeModifiers(function(evt) {
|
|
||||||
expect(times_called).to.be.equal(0);
|
it('should generate fake undo/redo events on press when a char modifier is down', function() {
|
||||||
++times_called;
|
var times_called = 0;
|
||||||
expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x42, escape: [0xfe03]});
|
var kbd = new Keyboard({
|
||||||
})({type: 'keyup', code: 'KeyA', keysym: 0x42, escape: [0xfe03]});
|
onKeyEvent: function(keysym, code, down) {
|
||||||
expect(times_called).to.be.equal(1);
|
switch(times_called++) {
|
||||||
});
|
case 0:
|
||||||
it('should pass through when a char modifier is not down', function() {
|
expect(keysym).to.be.equal(0xFFE3);
|
||||||
var times_called = 0;
|
expect(code).to.be.equal('ControlLeft');
|
||||||
KeyboardUtil.EscapeModifiers(function(evt) {
|
expect(down).to.be.equal(true);
|
||||||
expect(times_called).to.be.equal(0);
|
break;
|
||||||
++times_called;
|
case 1:
|
||||||
expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x42});
|
expect(keysym).to.be.equal(0xFFE9);
|
||||||
})({type: 'keyup', code: 'KeyA', keysym: 0x42});
|
expect(code).to.be.equal('AltLeft');
|
||||||
expect(times_called).to.be.equal(1);
|
expect(down).to.be.equal(true);
|
||||||
});
|
break;
|
||||||
|
case 2:
|
||||||
|
expect(keysym).to.be.equal(0xFFE9);
|
||||||
|
expect(code).to.be.equal('Unidentified');
|
||||||
|
expect(down).to.be.equal(false);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
expect(keysym).to.be.equal(0xFFE3);
|
||||||
|
expect(code).to.be.equal('Unidentified');
|
||||||
|
expect(down).to.be.equal(false);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
expect(keysym).to.be.equal(0x61);
|
||||||
|
expect(code).to.be.equal('KeyA');
|
||||||
|
expect(down).to.be.equal(true);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
expect(keysym).to.be.equal(0xFFE9);
|
||||||
|
expect(code).to.be.equal('Unidentified');
|
||||||
|
expect(down).to.be.equal(true);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
expect(keysym).to.be.equal(0xFFE3);
|
||||||
|
expect(code).to.be.equal('Unidentified');
|
||||||
|
expect(down).to.be.equal(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
// First the modifier combo
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'}));
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'}));
|
||||||
|
// Next a normal character
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
|
||||||
|
expect(times_called).to.be.equal(7);
|
||||||
|
});
|
||||||
|
it('should no do anything on key release', function() {
|
||||||
|
var times_called = 0;
|
||||||
|
var kbd = new Keyboard({
|
||||||
|
onKeyEvent: function(keysym, code, down) {
|
||||||
|
switch(times_called++) {
|
||||||
|
case 7:
|
||||||
|
expect(keysym).to.be.equal(0x61);
|
||||||
|
expect(code).to.be.equal('KeyA');
|
||||||
|
expect(down).to.be.equal(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
// First the modifier combo
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'}));
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'}));
|
||||||
|
// Next a normal character
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
|
||||||
|
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
|
||||||
|
expect(times_called).to.be.equal(8);
|
||||||
|
});
|
||||||
|
it('should not consider a char modifier to be down on the modifier key itself', function() {
|
||||||
|
var times_called = 0;
|
||||||
|
var kbd = new Keyboard({
|
||||||
|
onKeyEvent: function(keysym, code, down) {
|
||||||
|
switch(times_called++) {
|
||||||
|
case 0:
|
||||||
|
expect(keysym).to.be.equal(0xFFE3);
|
||||||
|
expect(code).to.be.equal('ControlLeft');
|
||||||
|
expect(down).to.be.equal(true);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
expect(keysym).to.be.equal(0xFFE9);
|
||||||
|
expect(code).to.be.equal('AltLeft');
|
||||||
|
expect(down).to.be.equal(true);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
expect(keysym).to.be.equal(0xFFE3);
|
||||||
|
expect(code).to.be.equal('ControlLeft');
|
||||||
|
expect(down).to.be.equal(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
// First the modifier combo
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'}));
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'}));
|
||||||
|
// Then one of the keys again
|
||||||
|
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'}));
|
||||||
|
expect(times_called).to.be.equal(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue