Improve character keysym lookup

Use the more modern 'key' field, and remove some legacy fallbacks
that are no longer required. This also removes the "stall" mechanism
as it is not needed with current browsers.
This commit is contained in:
Pierre Ossman 2017-01-25 11:29:08 +01:00
parent 80cb8ffddd
commit bfa1b237b9
4 changed files with 53 additions and 286 deletions

View File

@ -29,12 +29,10 @@ const Keyboard = function (defaults) {
// create the keyboard handler // create the keyboard handler
this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(),
KeyboardUtil.VerifyCharModifier( /* jshint newcap: false */ KeyboardUtil.TrackKeyState(
KeyboardUtil.TrackKeyState( KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this))
KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this))
)
) )
); /* jshint newcap: true */ );
// keep these here so we can refer to them later // keep these here so we can refer to them later
this._eventHandlers = { this._eventHandlers = {

View File

@ -196,23 +196,20 @@ export function getKeycode(evt){
// if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which // if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which
export function getKeysym(evt){ export function getKeysym(evt){
var codepoint; var codepoint;
if (evt.char && evt.char.length === 1) {
codepoint = evt.char.charCodeAt(); if ('key' in evt) {
} // Ignore special keys
else if (evt.charCode) { if (evt.key.length === 1) {
codepoint = evt.key.charCodeAt();
}
} else if ('charCode' in evt) {
codepoint = evt.charCode; codepoint = evt.charCode;
} }
else if (evt.keyCode && evt.type === 'keypress') {
// IE10 stores the char code as keyCode, and has no other useful properties
codepoint = evt.keyCode;
}
if (codepoint) { if (codepoint) {
return keysyms.lookup(codepoint); return keysyms.lookup(codepoint);
} }
// we could check evt.key here.
// Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list,
// so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key
// so we don't *need* it yet
if (evt.keyCode) { if (evt.keyCode) {
return keysymFromKeyCode(evt.keyCode, evt.shiftKey); return keysymFromKeyCode(evt.keyCode, evt.shiftKey);
} }
@ -437,7 +434,6 @@ export function TrackQEMUKeyState (next) {
// - determines a code identifying the key that was pressed (corresponding to the code/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 // - 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" // - 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
// This information is collected into an object which is passed to the next() function. (one call per event) // This information is collected into an object which is passed to the next() function. (one call per event)
export function KeyEventDecoder (modifierState, next) { export function KeyEventDecoder (modifierState, next) {
"use strict"; "use strict";
@ -476,10 +472,6 @@ export function KeyEventDecoder (modifierState, next) {
// so only do that if we have to. // so only do that if we have to.
var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt)); var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt));
// If a char modifier is down on a keydown, we need to insert a stall,
// so VerifyCharModifier knows to wait and see if a keypress is comnig
var stall = type === 'keydown' && modifierState.activeCharModifier() && !nonCharacterKey(evt);
// if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt) // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt)
var active = modifierState.activeCharModifier(); var active = modifierState.activeCharModifier();
@ -498,10 +490,6 @@ export function KeyEventDecoder (modifierState, next) {
} }
} }
if (stall) {
// insert a fake "stall" event
next({type: 'stall'});
}
next(result); next(result);
return suppress; return suppress;
@ -526,64 +514,6 @@ export function KeyEventDecoder (modifierState, next) {
}; };
}; };
// Combines keydown and keypress events where necessary to handle char modifiers.
// On some OS'es, a char modifier is sometimes used as a shortcut modifier.
// For example, on Windows, AltGr is synonymous with Ctrl-Alt. On a Danish keyboard layout, AltGr-2 yields a @, but Ctrl-Alt-D does nothing
// so when used with the '2' key, Ctrl-Alt counts as a char modifier (and should be escaped), but when used with 'D', it does not.
// The only way we can distinguish these cases is to wait and see if a keypress event arrives
// When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two
export function VerifyCharModifier (next) {
"use strict";
var queue = [];
var timer = null;
function process() {
if (timer) {
return;
}
var delayProcess = function () {
clearTimeout(timer);
timer = null;
process();
};
while (queue.length !== 0) {
var cur = queue[0];
queue = queue.splice(1);
switch (cur.type) {
case 'stall':
// insert a delay before processing available events.
/* jshint loopfunc: true */
timer = setTimeout(delayProcess, 5);
/* jshint loopfunc: false */
return;
case 'keydown':
// is the next element a keypress? Then we should merge the two
if (queue.length !== 0 && queue[0].type === 'keypress') {
// Firefox sends keypress even when no char is generated.
// so, if keypress keysym is the same as we'd have guessed from keydown,
// the modifier didn't have any effect, and should not be escaped
if (queue[0].escape && (!cur.keysym || cur.keysym !== queue[0].keysym)) {
cur.escape = queue[0].escape;
}
cur.keysym = queue[0].keysym;
queue = queue.splice(1);
}
break;
}
// swallow stall events, and pass all others to the next stage
if (cur.type !== 'stall') {
next(cur);
}
}
}
return function(evt) {
queue.push(evt);
process();
};
};
// Keeps track of which keys we (and the server) believe are down // 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) // 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 // in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars

View File

@ -99,25 +99,11 @@ describe('Helpers', function() {
}); });
describe('getKeysym', function() { describe('getKeysym', function() {
it('should prefer char', function() { it('should prefer key', function() {
expect(KeyboardUtil.getKeysym({char : 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x61); expect(KeyboardUtil.getKeysym({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x61);
}); });
it('should use charCode if no char', function() { it('should use charCode if no key', function() {
expect(KeyboardUtil.getKeysym({char : '', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9);
expect(KeyboardUtil.getKeysym({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9); expect(KeyboardUtil.getKeysym({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9);
expect(KeyboardUtil.getKeysym({char : 'hello', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9);
});
it('should use keyCode if no charCode', function() {
expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: false})).to.be.equal(0x62);
expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: true})).to.be.equal(0x42);
});
it('should return null for unknown keycodes', function() {
expect(KeyboardUtil.getKeysym({keyCode: 0xc0, which: 0xc1, shiftKey:false})).to.be.null;
expect(KeyboardUtil.getKeysym({keyCode: 0xde, which: 0xdf, shiftKey:false})).to.be.null;
});
it('should use which if no keyCode', function() {
expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: false})).to.be.equal(0x63);
expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: true})).to.be.equal(0x43);
}); });
describe('Non-character keys', function() { describe('Non-character keys', function() {
@ -133,6 +119,10 @@ describe('Helpers', function() {
expect(KeyboardUtil.getKeysym({keyCode: 0x1b})).to.be.equal(0xFF1B); expect(KeyboardUtil.getKeysym({keyCode: 0x1b})).to.be.equal(0xFF1B);
expect(KeyboardUtil.getKeysym({keyCode: 0x26})).to.be.equal(0xFF52); expect(KeyboardUtil.getKeysym({keyCode: 0x26})).to.be.equal(0xFF52);
}); });
it('should return null for unknown keycodes', function() {
expect(KeyboardUtil.getKeysym({keyCode: 0xc0})).to.be.null;
expect(KeyboardUtil.getKeysym({keyCode: 0xde})).to.be.null;
});
it('should not recognize character keys', function() { it('should not recognize character keys', function() {
expect(KeyboardUtil.getKeysym({keyCode: 'A'})).to.be.null; expect(KeyboardUtil.getKeysym({keyCode: 'A'})).to.be.null;
expect(KeyboardUtil.getKeysym({keyCode: '1'})).to.be.null; expect(KeyboardUtil.getKeysym({keyCode: '1'})).to.be.null;

View File

@ -12,26 +12,26 @@ describe('Key Event Pipeline Stages', function() {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.an.object; expect(evt).to.be.an.object;
done(); done();
}).keydown({code: 'KeyA', keyCode: 0x41}); }).keydown({code: 'KeyA', key: 'a'});
}); });
it('should pass the right keysym through', function(done) { it('should pass the right keysym through', function(done) {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt.keysym).to.be.deep.equal(0x61); expect(evt.keysym).to.be.deep.equal(0x61);
done(); done();
}).keypress({code: 'KeyA', keyCode: 0x41}); }).keypress({code: 'KeyA', key: 'a'});
}); });
it('should pass the right keyid through', function(done) { it('should pass the right keyid through', function(done) {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.have.property('code', 'KeyA'); expect(evt).to.have.property('code', 'KeyA');
done(); done();
}).keydown({code: 'KeyA', keyCode: 0x41}); }).keydown({code: 'KeyA', key: 'a'});
}); });
it('should not sync modifiers on a keypress', function() { it('should not sync modifiers on a keypress', function() {
// Firefox provides unreliable modifier state on keypress events // Firefox provides unreliable modifier state on keypress events
var count = 0; var count = 0;
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
++count; ++count;
}).keypress({code: 'KeyA', keyCode: 0x41, ctrlKey: true}); }).keypress({code: 'KeyA', key: 'a', ctrlKey: true});
expect(count).to.be.equal(1); expect(count).to.be.equal(1);
}); });
it('should sync modifiers if necessary', function(done) { it('should sync modifiers if necessary', function(done) {
@ -47,61 +47,36 @@ describe('Key Event Pipeline Stages', function() {
done(); done();
break; break;
} }
}).keydown({code: 'KeyA', keyCode: 0x41, ctrlKey: true}); }).keydown({code: 'KeyA', key: 'a', ctrlKey: true});
}); });
it('should forward keydown events with the right type', function(done) { it('should forward keydown events with the right type', function(done) {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'}); expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'});
done(); done();
}).keydown({code: 'KeyA', keyCode: 0x41}); }).keydown({code: 'KeyA', key: 'a'});
}); });
it('should forward keyup events with the right type', function(done) { it('should forward keyup events with the right type', function(done) {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'}); expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'});
done(); done();
}).keyup({code: 'KeyA', keyCode: 0x41}); }).keyup({code: 'KeyA', key: 'a'});
}); });
it('should forward keypress events with the right type', function(done) { it('should forward keypress events with the right type', function(done) {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'}); expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'});
done(); done();
}).keypress({code: 'KeyA', keyCode: 0x41}); }).keypress({code: 'KeyA', key: 'a'});
});
it('should generate stalls if a char modifier is down while a key is pressed', function(done) {
var count = 0;
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
switch (count) {
case 0: // fake altgr
expect(evt).to.be.deep.equal({keysym: 0xfe03, type: 'keydown'});
++count;
break;
case 1: // stall before processing the 'a' keydown
expect(evt).to.be.deep.equal({type: 'stall'});
++count;
break;
case 2: // 'a'
expect(evt).to.be.deep.equal({
type: 'keydown',
code: 'KeyA',
keysym: 0x61
});
done();
break;
}
}).keydown({code: 'KeyA', keyCode: 0x41, altGraphKey: true});
}); });
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 while a shortcut modifier is down', function() {
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
obj.keydown({keyCode: 0x11}); // press ctrl obj.keydown({keyCode: 0x11}); // press ctrl
expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.true; expect(obj.keydown({key: 'A'})).to.be.true;
expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.true; expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.true;
expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.true; expect(obj.keydown({key: '1'})).to.be.true;
expect(obj.keydown({keyCode: 0x3c})).to.be.true; // < key on DK Windows expect(obj.keydown({key: '<'})).to.be.true;
expect(obj.keydown({keyCode: 0xde})).to.be.true; // Ø key on DK expect(obj.keydown({key: 'ø'})).to.be.true;
}); });
it('should suppress non-character keys', function() { it('should suppress non-character keys', function() {
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
@ -127,48 +102,35 @@ describe('Key Event Pipeline Stages', function() {
it('should not suppress character keys', function() { it('should not suppress character keys', function() {
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false; expect(obj.keydown({key: 'A'})).to.be.false;
expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false; expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false; expect(obj.keydown({key: '1'})).to.be.false;
expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows expect(obj.keydown({key: '<'})).to.be.false; // < key on DK Windows
expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK expect(obj.keydown({key: 'ø'})).to.be.false; // Ø key on DK
}); });
it('should not suppress if a char modifier is down', function() { it('should not suppress if a char modifier is down', function() {
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {}); var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {});
obj.keydown({keyCode: 0xe1}); // press altgr obj.keydown({keyCode: 0xe1}); // press altgr
expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false; expect(obj.keydown({key: 'A'})).to.be.false;
expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false; expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false; expect(obj.keydown({key: '1'})).to.be.false;
expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows expect(obj.keydown({key: '<'})).to.be.false;
expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK expect(obj.keydown({key: 'ø'})).to.be.false;
}); });
}); });
describe('Keypress and keyup events', function() { describe('Keypress and keyup events', function() {
it('should always suppress event propagation', function() { it('should always suppress event propagation', function() {
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
expect(obj.keypress({keyCode: 'A'.charCodeAt()})).to.be.true; expect(obj.keypress({key: 'A'})).to.be.true;
expect(obj.keypress({keyCode: 0x3c})).to.be.true; // < key on DK Windows expect(obj.keypress({key: '<'})).to.be.true;
expect(obj.keypress({keyCode: 0x11})).to.be.true; expect(obj.keypress({keyCode: 0x11})).to.be.true;
expect(obj.keyup({keyCode: 'A'.charCodeAt()})).to.be.true; expect(obj.keyup({key: 'A'})).to.be.true;
expect(obj.keyup({keyCode: 0x3c})).to.be.true; // < key on DK Windows expect(obj.keyup({key: '<'})).to.be.true;
expect(obj.keyup({keyCode: 0x11})).to.be.true; expect(obj.keyup({keyCode: 0x11})).to.be.true;
}); });
it('should never generate stalls', function() {
var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt.type).to.not.be.equal('stall');
});
obj.keypress({keyCode: 'A'.charCodeAt()});
obj.keypress({keyCode: 0x3c});
obj.keypress({keyCode: 0x11});
obj.keyup({keyCode: 'A'.charCodeAt()});
obj.keyup({keyCode: 0x3c});
obj.keyup({keyCode: 0x11});
});
}); });
describe('mark events if a char modifier is down', function() { describe('mark events if a char modifier is down', function() {
it('should not mark modifiers on a keydown event', function() { it('should not mark modifiers on a keydown event', function() {
@ -184,7 +146,7 @@ describe('Key Event Pipeline Stages', function() {
}); });
obj.keydown({keyCode: 0xe1}); // press altgr obj.keydown({keyCode: 0xe1}); // press altgr
obj.keydown({code: 'KeyA', keyCode: 0x41}); obj.keydown({code: 'KeyA', key: 'a'});
}); });
it('should indicate on events if a single-key char modifier is down', function(done) { it('should indicate on events if a single-key char modifier is down', function(done) {
@ -206,7 +168,7 @@ describe('Key Event Pipeline Stages', function() {
}); });
obj.keydown({keyCode: 0xe1}); // press altgr obj.keydown({keyCode: 0xe1}); // press altgr
obj.keypress({code: 'KeyA', keyCode: 0x41}); obj.keypress({code: 'KeyA', key: 'a'});
}); });
it('should indicate on events if a multi-key char modifier is down', function(done) { it('should indicate on events if a multi-key char modifier is down', function(done) {
var times_called = 0; var times_called = 0;
@ -230,7 +192,7 @@ describe('Key Event Pipeline Stages', function() {
obj.keydown({keyCode: 0x11}); // press ctrl obj.keydown({keyCode: 0x11}); // press ctrl
obj.keydown({keyCode: 0x12}); // press alt obj.keydown({keyCode: 0x12}); // press alt
obj.keypress({code: 'KeyA', keyCode: 0x41}); obj.keypress({code: 'KeyA', key: 'a'});
}); });
it('should not consider a char modifier to be down on the modifier key itself', function() { 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) { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
@ -245,7 +207,7 @@ describe('Key Event Pipeline Stages', function() {
it('should remove keysym from keydown if a char key and no modifier', function() { it('should remove keysym from keydown if a char key and no modifier', function() {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'}); expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'});
}).keydown({code: 'KeyA', keyCode: 0x41}); }).keydown({code: 'KeyA', key: 'a'});
}); });
it('should not remove keysym from keydown if a shortcut modifier is down', function() { it('should not remove keysym from keydown if a shortcut modifier is down', function() {
var times_called = 0; var times_called = 0;
@ -255,19 +217,19 @@ describe('Key Event Pipeline Stages', function() {
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'});
break; break;
} }
}).keydown({code: 'KeyA', keyCode: 0x41, ctrlKey: true}); }).keydown({code: 'KeyA', key: 'a', ctrlKey: true});
expect(times_called).to.be.equal(2); expect(times_called).to.be.equal(2);
}); });
it('should not remove keysym from keydown if a char modifier is down', function() { it('should not remove keysym from keydown if a char modifier is down', function() {
var times_called = 0; var times_called = 0;
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
switch (times_called++) { switch (times_called++) {
case 2: case 1:
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'});
break; break;
} }
}).keydown({code: 'KeyA', keyCode: 0x41, altGraphKey: true}); }).keydown({code: 'KeyA', key: 'a', altGraphKey: true});
expect(times_called).to.be.equal(3); expect(times_called).to.be.equal(2);
}); });
it('should not remove keysym from keydown if key is noncharacter', function() { it('should not remove keysym from keydown if key is noncharacter', function() {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
@ -281,131 +243,18 @@ describe('Key Event Pipeline Stages', function() {
it('should never remove keysym from keypress', function() { it('should never remove keysym from keypress', function() {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'}); expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'});
}).keypress({code: 'KeyA', keyCode: 0x41}); }).keypress({code: 'KeyA', key: 'a'});
}); });
it('should never remove keysym from keyup', function() { it('should never remove keysym from keyup', function() {
KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'}); expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'});
}).keyup({code: 'KeyA', keyCode: 0x41}); }).keyup({code: 'KeyA', key: 'a'});
}); });
}); });
// on keypress, keyup(?), always set keysym // on keypress, keyup(?), always set keysym
// on keydown, only do it if we don't expect a keypress: if noncharacter OR modifier is down // on keydown, only do it if we don't expect a keypress: if noncharacter OR modifier is down
}); });
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', code: 'KeyA', keysym: 0x41});
done();
})({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', code: 'KeyA', keysym: 0x41});
done();
})({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', code: 'KeyA', keysym: 0x41});
done();
})({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', code: 'KeyA', keysym: 0x41});
done();
});
obj({type: 'stall'});
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;
var obj = KeyboardUtil.VerifyCharModifier(function(evt){
// should only be called once, for the keydown
expect(next_called).to.be.false;
next_called = true;
expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x44});
done();
});
obj({type: 'stall'});
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) {
var next_called = false;
var obj = KeyboardUtil.VerifyCharModifier(function(evt){
// should only be called once, for the keydown
expect(next_called).to.be.false;
next_called = true;
expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x44, escape: [0xffe3]});
done();
});
obj({type: 'stall'});
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() {
var obj = KeyboardUtil.VerifyCharModifier(function(evt){
expect(evt).to.not.have.property('escape');
});
obj({type: 'stall'});
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', code: 'KeyA', keysym: 0x42});
break;
case 1:
expect(evt).to.deep.equal({type: 'keypress', code: 'KeyC', keysym: 0x44});
done();
break;
}
++times_called;
});
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', code: 'KeyA', keysym: 0x42});
break;
case 1:
expect(evt,2).to.deep.equal({type: 'keyup', code: 'KeyC', keysym: 0x44});
break;
case 2:
expect(evt,3).to.deep.equal({type: 'keypress', code: 'KeyE', keysym: 0x46});
done();
break;
}
++times_called;
});
obj({type: 'stall'});
obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
obj({type: 'keyup', code: 'KeyC', keysym: 0x44});
obj({type: 'keypress', code: 'KeyE', keysym: 0x46});
});
});
describe('Track Key State', function() { describe('Track Key State', function() {
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 obj = KeyboardUtil.TrackKeyState(function(evt) {