]> git.proxmox.com Git - mirror_novnc.git/blame - core/input/util.js
Remove character substitution
[mirror_novnc.git] / core / input / util.js
CommitLineData
3ae0bb09
SR
1import KeyTable from "./keysym.js";
2import keysyms from "./keysymdef.js";
3
6d6f0db0
SR
4function isMac() {
5 return navigator && !!(/mac/i).exec(navigator.platform);
6}
7function isWindows() {
8 return navigator && !!(/win/i).exec(navigator.platform);
9}
10function isLinux() {
11 return navigator && !!(/linux/i).exec(navigator.platform);
12}
13
14// Return true if a modifier which is not the specified char modifier (and is not shift) is down
15export function hasShortcutModifier(charModifier, currentModifiers) {
16 var mods = {};
17 for (var key in currentModifiers) {
18 if (parseInt(key) !== KeyTable.XK_Shift_L) {
19 mods[key] = currentModifiers[key];
20 }
31f169e8 21 }
466a09f0 22
6d6f0db0
SR
23 var sum = 0;
24 for (var k in currentModifiers) {
25 if (mods[k]) {
26 ++sum;
27 }
4ef7566b 28 }
6d6f0db0
SR
29 if (hasCharModifier(charModifier, mods)) {
30 return sum > charModifier.length;
4ef7566b 31 }
6d6f0db0
SR
32 else {
33 return sum > 0;
4ef7566b 34 }
6d6f0db0 35}
4ef7566b 36
6d6f0db0
SR
37// Return true if the specified char modifier is currently down
38export function hasCharModifier(charModifier, currentModifiers) {
39 if (charModifier.length === 0) { return false; }
40
41 for (var i = 0; i < charModifier.length; ++i) {
42 if (!currentModifiers[charModifier[i]]) {
43 return false;
4ef7566b 44 }
6d6f0db0
SR
45 }
46 return true;
47}
4ef7566b 48
6d6f0db0
SR
49// Helper object tracking modifier key state
50// and generates fake key events to compensate if it gets out of sync
51export function ModifierSync(charModifier) {
52 if (!charModifier) {
53 if (isMac()) {
54 // on Mac, Option (AKA Alt) is used as a char modifier
55 charModifier = [KeyTable.XK_Alt_L];
56 }
57 else if (isWindows()) {
58 // on Windows, Ctrl+Alt is used as a char modifier
59 charModifier = [KeyTable.XK_Alt_L, KeyTable.XK_Control_L];
4ef7566b 60 }
6d6f0db0
SR
61 else if (isLinux()) {
62 // on Linux, ISO Level 3 Shift (AltGr) is used as a char modifier
63 charModifier = [KeyTable.XK_ISO_Level3_Shift];
4ef7566b 64 }
65 else {
6d6f0db0 66 charModifier = [];
4ef7566b 67 }
68 }
69
6d6f0db0
SR
70 var state = {};
71 state[KeyTable.XK_Control_L] = false;
72 state[KeyTable.XK_Alt_L] = false;
73 state[KeyTable.XK_ISO_Level3_Shift] = false;
74 state[KeyTable.XK_Shift_L] = false;
75 state[KeyTable.XK_Meta_L] = false;
76
77 function sync(evt, keysym) {
78 var result = [];
79 function syncKey(keysym) {
524d67f2 80 return {keysym: keysym, type: state[keysym] ? 'keydown' : 'keyup'};
6d6f0db0
SR
81 }
82
83 if (evt.ctrlKey !== undefined &&
84 evt.ctrlKey !== state[KeyTable.XK_Control_L] && keysym !== KeyTable.XK_Control_L) {
85 state[KeyTable.XK_Control_L] = evt.ctrlKey;
86 result.push(syncKey(KeyTable.XK_Control_L));
87 }
88 if (evt.altKey !== undefined &&
89 evt.altKey !== state[KeyTable.XK_Alt_L] && keysym !== KeyTable.XK_Alt_L) {
90 state[KeyTable.XK_Alt_L] = evt.altKey;
91 result.push(syncKey(KeyTable.XK_Alt_L));
92 }
93 if (evt.altGraphKey !== undefined &&
94 evt.altGraphKey !== state[KeyTable.XK_ISO_Level3_Shift] && keysym !== KeyTable.XK_ISO_Level3_Shift) {
95 state[KeyTable.XK_ISO_Level3_Shift] = evt.altGraphKey;
96 result.push(syncKey(KeyTable.XK_ISO_Level3_Shift));
97 }
98 if (evt.shiftKey !== undefined &&
99 evt.shiftKey !== state[KeyTable.XK_Shift_L] && keysym !== KeyTable.XK_Shift_L) {
100 state[KeyTable.XK_Shift_L] = evt.shiftKey;
101 result.push(syncKey(KeyTable.XK_Shift_L));
102 }
103 if (evt.metaKey !== undefined &&
104 evt.metaKey !== state[KeyTable.XK_Meta_L] && keysym !== KeyTable.XK_Meta_L) {
105 state[KeyTable.XK_Meta_L] = evt.metaKey;
106 result.push(syncKey(KeyTable.XK_Meta_L));
107 }
108 return result;
4ef7566b 109 }
6d6f0db0 110 function syncKeyEvent(evt, down) {
524d67f2 111 var keysym = getKeysym(evt);
4ef7566b 112
6d6f0db0
SR
113 // first, apply the event itself, if relevant
114 if (keysym !== null && state[keysym] !== undefined) {
115 state[keysym] = down;
4ef7566b 116 }
6d6f0db0
SR
117 return sync(evt, keysym);
118 }
4ef7566b 119
6d6f0db0
SR
120 return {
121 // sync on the appropriate keyboard event
122 keydown: function(evt) { return syncKeyEvent(evt, true);},
123 keyup: function(evt) { return syncKeyEvent(evt, false);},
124 // Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway
125 syncAny: function(evt) { return sync(evt);},
126
127 // is a shortcut modifier down?
128 hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); },
129 // if a char modifier is down, return the keys it consists of, otherwise return null
130 activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; }
131 };
132}
4ef7566b 133
6d6f0db0
SR
134// Get a key ID from a keyboard event
135// May be a string or an integer depending on the available properties
136export function getKey(evt){
137 if ('keyCode' in evt && 'key' in evt) {
138 return evt.key + ':' + evt.keyCode;
4ef7566b 139 }
6d6f0db0
SR
140 else if ('keyCode' in evt) {
141 return evt.keyCode;
142 }
143 else {
144 return evt.key;
145 }
146}
147
148// Get the most reliable keysym value we can get from a key event
149// if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which
150export function getKeysym(evt){
151 var codepoint;
152 if (evt.char && evt.char.length === 1) {
153 codepoint = evt.char.charCodeAt();
154 }
155 else if (evt.charCode) {
156 codepoint = evt.charCode;
4ef7566b 157 }
6d6f0db0
SR
158 else if (evt.keyCode && evt.type === 'keypress') {
159 // IE10 stores the char code as keyCode, and has no other useful properties
160 codepoint = evt.keyCode;
161 }
162 if (codepoint) {
0a865e15 163 return keysyms.lookup(codepoint);
6d6f0db0
SR
164 }
165 // we could check evt.key here.
166 // Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list,
167 // so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key
168 // so we don't *need* it yet
169 if (evt.keyCode) {
524d67f2 170 return keysymFromKeyCode(evt.keyCode, evt.shiftKey);
6d6f0db0
SR
171 }
172 if (evt.which) {
524d67f2 173 return keysymFromKeyCode(evt.which, evt.shiftKey);
6d6f0db0
SR
174 }
175 return null;
176}
4ef7566b 177
6d6f0db0
SR
178// Given a keycode, try to predict which keysym it might be.
179// If the keycode is unknown, null is returned.
180export function keysymFromKeyCode(keycode, shiftPressed) {
181 if (typeof(keycode) !== 'number') {
4ef7566b 182 return null;
183 }
6d6f0db0
SR
184 // won't be accurate for azerty
185 if (keycode >= 0x30 && keycode <= 0x39) {
186 return keycode; // digit
187 }
188 if (keycode >= 0x41 && keycode <= 0x5a) {
189 // remap to lowercase unless shift is down
190 return shiftPressed ? keycode : keycode + 32; // A-Z
191 }
192 if (keycode >= 0x60 && keycode <= 0x69) {
193 return KeyTable.XK_KP_0 + (keycode - 0x60); // numpad 0-9
194 }
4ef7566b 195
6d6f0db0
SR
196 switch(keycode) {
197 case 0x20: return KeyTable.XK_space;
198 case 0x6a: return KeyTable.XK_KP_Multiply;
199 case 0x6b: return KeyTable.XK_KP_Add;
200 case 0x6c: return KeyTable.XK_KP_Separator;
201 case 0x6d: return KeyTable.XK_KP_Subtract;
202 case 0x6e: return KeyTable.XK_KP_Decimal;
203 case 0x6f: return KeyTable.XK_KP_Divide;
204 case 0xbb: return KeyTable.XK_plus;
205 case 0xbc: return KeyTable.XK_comma;
206 case 0xbd: return KeyTable.XK_minus;
207 case 0xbe: return KeyTable.XK_period;
4ef7566b 208 }
209
6d6f0db0
SR
210 return nonCharacterKey({keyCode: keycode});
211}
4ef7566b 212
6d6f0db0
SR
213// if the key is a known non-character key (any key which doesn't generate character data)
214// return its keysym value. Otherwise return null
215export function nonCharacterKey(evt) {
216 // evt.key not implemented yet
217 if (!evt.keyCode) { return null; }
218 var keycode = evt.keyCode;
4ef7566b 219
6d6f0db0
SR
220 if (keycode >= 0x70 && keycode <= 0x87) {
221 return KeyTable.XK_F1 + keycode - 0x70; // F1-F24
222 }
223 switch (keycode) {
282834ca 224
6d6f0db0
SR
225 case 8 : return KeyTable.XK_BackSpace;
226 case 13 : return KeyTable.XK_Return;
4ef7566b 227
6d6f0db0 228 case 9 : return KeyTable.XK_Tab;
4ef7566b 229
6d6f0db0
SR
230 case 27 : return KeyTable.XK_Escape;
231 case 46 : return KeyTable.XK_Delete;
4ef7566b 232
6d6f0db0
SR
233 case 36 : return KeyTable.XK_Home;
234 case 35 : return KeyTable.XK_End;
235 case 33 : return KeyTable.XK_Page_Up;
236 case 34 : return KeyTable.XK_Page_Down;
237 case 45 : return KeyTable.XK_Insert;
4ef7566b 238
6d6f0db0
SR
239 case 37 : return KeyTable.XK_Left;
240 case 38 : return KeyTable.XK_Up;
241 case 39 : return KeyTable.XK_Right;
242 case 40 : return KeyTable.XK_Down;
4ef7566b 243
6d6f0db0
SR
244 case 16 : return KeyTable.XK_Shift_L;
245 case 17 : return KeyTable.XK_Control_L;
246 case 18 : return KeyTable.XK_Alt_L; // also: Option-key on Mac
247
248 case 224 : return KeyTable.XK_Meta_L;
249 case 225 : return KeyTable.XK_ISO_Level3_Shift; // AltGr
250 case 91 : return KeyTable.XK_Super_L; // also: Windows-key
251 case 92 : return KeyTable.XK_Super_R; // also: Windows-key
252 case 93 : return KeyTable.XK_Menu; // also: Windows-Menu, Command on Mac
253 default: return null;
4ef7566b 254 }
6d6f0db0 255}
ae510306 256
6d6f0db0 257export function QEMUKeyEventDecoder (modifierState, next) {
49637e43
DHB
258 "use strict";
259
260 function sendAll(evts) {
261 for (var i = 0; i < evts.length; ++i) {
262 next(evts[i]);
263 }
264 }
265
266 var numPadCodes = ["Numpad0", "Numpad1", "Numpad2",
267 "Numpad3", "Numpad4", "Numpad5", "Numpad6",
268 "Numpad7", "Numpad8", "Numpad9", "NumpadDecimal"];
269
270 var numLockOnKeySyms = {
271 "Numpad0": 0xffb0, "Numpad1": 0xffb1, "Numpad2": 0xffb2,
272 "Numpad3": 0xffb3, "Numpad4": 0xffb4, "Numpad5": 0xffb5,
273 "Numpad6": 0xffb6, "Numpad7": 0xffb7, "Numpad8": 0xffb8,
274 "Numpad9": 0xffb9, "NumpadDecimal": 0xffac
275 };
276
277 var numLockOnKeyCodes = [96, 97, 98, 99, 100, 101, 102,
278 103, 104, 105, 108, 110];
279
280 function isNumPadMultiKey(evt) {
281 return (numPadCodes.indexOf(evt.code) !== -1);
282 }
283
284 function getNumPadKeySym(evt) {
285 if (numLockOnKeyCodes.indexOf(evt.keyCode) !== -1) {
286 return numLockOnKeySyms[evt.code];
287 }
288 return 0;
289 }
290
291 function process(evt, type) {
292 var result = {type: type};
293 result.code = evt.code;
294 result.keysym = 0;
295
296 if (isNumPadMultiKey(evt)) {
297 result.keysym = getNumPadKeySym(evt);
298 }
299
300 var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
301 var isShift = evt.keyCode === 0x10 || evt.key === 'Shift';
302
6d6f0db0 303 var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt));
49637e43
DHB
304
305 next(result);
306 return suppress;
307 }
308 return {
309 keydown: function(evt) {
310 sendAll(modifierState.keydown(evt));
311 return process(evt, 'keydown');
312 },
313 keypress: function(evt) {
314 return true;
315 },
316 keyup: function(evt) {
317 sendAll(modifierState.keyup(evt));
318 return process(evt, 'keyup');
319 },
320 syncModifiers: function(evt) {
321 sendAll(modifierState.syncAny(evt));
322 },
323 releaseAll: function() { next({type: 'releaseall'}); }
324 };
ae510306 325};
49637e43 326
6d6f0db0 327export function TrackQEMUKeyState (next) {
49637e43
DHB
328 "use strict";
329 var state = [];
330
331 return function (evt) {
332 var last = state.length !== 0 ? state[state.length-1] : null;
333
334 switch (evt.type) {
335 case 'keydown':
336
337 if (!last || last.code !== evt.code) {
338 last = {code: evt.code};
339
340 if (state.length > 0 && state[state.length-1].code == 'ControlLeft') {
341 if (evt.code !== 'AltRight') {
342 next({code: 'ControlLeft', type: 'keydown', keysym: 0});
343 } else {
344 state.pop();
345 }
346 }
347 state.push(last);
348 }
349 if (evt.code !== 'ControlLeft') {
350 next(evt);
351 }
352 break;
353
354 case 'keyup':
355 if (state.length === 0) {
356 return;
357 }
358 var idx = null;
359 // do we have a matching key tracked as being down?
360 for (var i = 0; i !== state.length; ++i) {
361 if (state[i].code === evt.code) {
362 idx = i;
363 break;
364 }
365 }
366 // if we couldn't find a match (it happens), assume it was the last key pressed
367 if (idx === null) {
368 if (evt.code === 'ControlLeft') {
369 return;
370 }
371 idx = state.length - 1;
372 }
373
374 state.splice(idx, 1);
375 next(evt);
376 break;
377 case 'releaseall':
378 /* jshint shadow: true */
379 for (var i = 0; i < state.length; ++i) {
380 next({code: state[i].code, keysym: 0, type: 'keyup'});
381 }
382 /* jshint shadow: false */
383 state = [];
384 }
385 };
ae510306 386};
49637e43 387
4ef7566b 388// Takes a DOM keyboard event and:
389// - determines which keysym it represents
390// - determines a keyId identifying the key that was pressed (corresponding to the key/keyCode properties on the DOM event)
391// - synthesizes events to synchronize modifier key state between which modifiers are actually down, and which we thought were down
392// - marks each event with an 'escape' property if a modifier was down which should be "escaped"
393// - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown
394// This information is collected into an object which is passed to the next() function. (one call per event)
6d6f0db0 395export function KeyEventDecoder (modifierState, next) {
4ef7566b 396 "use strict";
397 function sendAll(evts) {
398 for (var i = 0; i < evts.length; ++i) {
399 next(evts[i]);
400 }
401 }
402 function process(evt, type) {
403 var result = {type: type};
6d6f0db0 404 var keyId = getKey(evt);
4ef7566b 405 if (keyId) {
406 result.keyId = keyId;
407 }
408
6d6f0db0 409 var keysym = getKeysym(evt);
4ef7566b 410
411 var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
412 // Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress?
413 // "special" keys like enter, tab or backspace don't send keypress events,
414 // and some browsers don't send keypresses at all if a modifier is down
6d6f0db0 415 if (keysym && (type !== 'keydown' || nonCharacterKey(evt) || hasModifier)) {
4ef7566b 416 result.keysym = keysym;
417 }
418
419 var isShift = evt.keyCode === 0x10 || evt.key === 'Shift';
420
421 // Should we prevent the browser from handling the event?
422 // Doing so on a keydown (in most browsers) prevents keypress from being generated
423 // so only do that if we have to.
6d6f0db0 424 var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt));
4ef7566b 425
426 // If a char modifier is down on a keydown, we need to insert a stall,
427 // so VerifyCharModifier knows to wait and see if a keypress is comnig
6d6f0db0 428 var stall = type === 'keydown' && modifierState.activeCharModifier() && !nonCharacterKey(evt);
4ef7566b 429
430 // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt)
431 var active = modifierState.activeCharModifier();
432
433 // If we have a char modifier down, and we're able to determine a keysym reliably
434 // then (a) we know to treat the modifier as a char modifier,
435 // and (b) we'll have to "escape" the modifier to undo the modifier when sending the char.
436 if (active && keysym) {
437 var isCharModifier = false;
438 for (var i = 0; i < active.length; ++i) {
524d67f2 439 if (active[i] === keysym) {
4ef7566b 440 isCharModifier = true;
441 }
442 }
443 if (type === 'keypress' && !isCharModifier) {
444 result.escape = modifierState.activeCharModifier();
445 }
446 }
447
448 if (stall) {
449 // insert a fake "stall" event
450 next({type: 'stall'});
451 }
452 next(result);
453
454 return suppress;
455 }
456
457 return {
458 keydown: function(evt) {
459 sendAll(modifierState.keydown(evt));
460 return process(evt, 'keydown');
461 },
462 keypress: function(evt) {
463 return process(evt, 'keypress');
464 },
465 keyup: function(evt) {
466 sendAll(modifierState.keyup(evt));
467 return process(evt, 'keyup');
468 },
469 syncModifiers: function(evt) {
470 sendAll(modifierState.syncAny(evt));
471 },
472 releaseAll: function() { next({type: 'releaseall'}); }
473 };
ae510306 474};
4ef7566b 475
476// Combines keydown and keypress events where necessary to handle char modifiers.
477// On some OS'es, a char modifier is sometimes used as a shortcut modifier.
478// 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
479// 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.
480// The only way we can distinguish these cases is to wait and see if a keypress event arrives
481// When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two
6d6f0db0 482export function VerifyCharModifier (next) {
4ef7566b 483 "use strict";
484 var queue = [];
485 var timer = null;
486 function process() {
487 if (timer) {
488 return;
489 }
31f169e8
SR
490
491 var delayProcess = function () {
492 clearTimeout(timer);
493 timer = null;
494 process();
495 };
496
4ef7566b 497 while (queue.length !== 0) {
498 var cur = queue[0];
499 queue = queue.splice(1);
500 switch (cur.type) {
501 case 'stall':
502 // insert a delay before processing available events.
31f169e8
SR
503 /* jshint loopfunc: true */
504 timer = setTimeout(delayProcess, 5);
505 /* jshint loopfunc: false */
4ef7566b 506 return;
507 case 'keydown':
508 // is the next element a keypress? Then we should merge the two
509 if (queue.length !== 0 && queue[0].type === 'keypress') {
510 // Firefox sends keypress even when no char is generated.
511 // so, if keypress keysym is the same as we'd have guessed from keydown,
512 // the modifier didn't have any effect, and should not be escaped
524d67f2 513 if (queue[0].escape && (!cur.keysym || cur.keysym !== queue[0].keysym)) {
4ef7566b 514 cur.escape = queue[0].escape;
515 }
516 cur.keysym = queue[0].keysym;
517 queue = queue.splice(1);
518 }
519 break;
520 }
521
522 // swallow stall events, and pass all others to the next stage
523 if (cur.type !== 'stall') {
524 next(cur);
525 }
526 }
527 }
528 return function(evt) {
529 queue.push(evt);
530 process();
531 };
ae510306 532};
4ef7566b 533
534// Keeps track of which keys we (and the server) believe are down
535// When a keyup is received, match it against this list, to determine the corresponding keysym(s)
536// in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars
537// key repeat events should be merged into a single entry.
538// Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess
6d6f0db0 539export function TrackKeyState (next) {
4ef7566b 540 "use strict";
541 var state = [];
542
543 return function (evt) {
544 var last = state.length !== 0 ? state[state.length-1] : null;
545
546 switch (evt.type) {
547 case 'keydown':
548 // insert a new entry if last seen key was different.
549 if (!last || !evt.keyId || last.keyId !== evt.keyId) {
550 last = {keyId: evt.keyId, keysyms: {}};
551 state.push(last);
552 }
553 if (evt.keysym) {
554 // make sure last event contains this keysym (a single "logical" keyevent
555 // can cause multiple key events to be sent to the VNC server)
524d67f2 556 last.keysyms[evt.keysym] = evt.keysym;
4ef7566b 557 last.ignoreKeyPress = true;
558 next(evt);
559 }
560 break;
561 case 'keypress':
562 if (!last) {
563 last = {keyId: evt.keyId, keysyms: {}};
564 state.push(last);
565 }
566 if (!evt.keysym) {
567 console.log('keypress with no keysym:', evt);
568 }
569
570 // If we didn't expect a keypress, and already sent a keydown to the VNC server
571 // based on the keydown, make sure to skip this event.
572 if (evt.keysym && !last.ignoreKeyPress) {
524d67f2 573 last.keysyms[evt.keysym] = evt.keysym;
4ef7566b 574 evt.type = 'keydown';
575 next(evt);
576 }
577 break;
578 case 'keyup':
579 if (state.length === 0) {
580 return;
581 }
582 var idx = null;
583 // do we have a matching key tracked as being down?
584 for (var i = 0; i !== state.length; ++i) {
585 if (state[i].keyId === evt.keyId) {
586 idx = i;
587 break;
588 }
589 }
590 // if we couldn't find a match (it happens), assume it was the last key pressed
591 if (idx === null) {
592 idx = state.length - 1;
593 }
594
595 var item = state.splice(idx, 1)[0];
596 // for each keysym tracked by this key entry, clone the current event and override the keysym
31f169e8
SR
597 var clone = (function(){
598 function Clone(){}
599 return function (obj) { Clone.prototype=obj; return new Clone(); };
600 }());
4ef7566b 601 for (var key in item.keysyms) {
4ef7566b 602 var out = clone(evt);
603 out.keysym = item.keysyms[key];
604 next(out);
605 }
606 break;
607 case 'releaseall':
31f169e8 608 /* jshint shadow: true */
4ef7566b 609 for (var i = 0; i < state.length; ++i) {
610 for (var key in state[i].keysyms) {
611 var keysym = state[i].keysyms[key];
612 next({keyId: 0, keysym: keysym, type: 'keyup'});
613 }
614 }
31f169e8 615 /* jshint shadow: false */
4ef7566b 616 state = [];
617 }
618 };
ae510306 619};
4ef7566b 620
621// Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @),
622// then the modifier must be "undone" before sending the @, and "redone" afterwards.
6d6f0db0 623export function EscapeModifiers (next) {
4ef7566b 624 "use strict";
625 return function(evt) {
626 if (evt.type !== 'keydown' || evt.escape === undefined) {
627 next(evt);
628 return;
629 }
630 // undo modifiers
631 for (var i = 0; i < evt.escape.length; ++i) {
524d67f2 632 next({type: 'keyup', keyId: 0, keysym: evt.escape[i]});
4ef7566b 633 }
634 // send the character event
635 next(evt);
636 // redo modifiers
31f169e8 637 /* jshint shadow: true */
4ef7566b 638 for (var i = 0; i < evt.escape.length; ++i) {
524d67f2 639 next({type: 'keydown', keyId: 0, keysym: evt.escape[i]});
4ef7566b 640 }
31f169e8 641 /* jshint shadow: false */
4ef7566b 642 };
ae510306 643};