1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * @fileoverview A collection of JavaScript utilities used to simplify working
7 * with keyboard events.
11 goog
.provide('cvox.KeyUtil');
12 goog
.provide('cvox.SimpleKeyEvent');
14 goog
.require('cvox.ChromeVox');
15 goog
.require('cvox.KeySequence');
18 * @typedef {{ctrlKey: (boolean|undefined),
19 * altKey: (boolean|undefined),
20 * shiftKey: (boolean|undefined),
21 * keyCode: (number|undefined)}}
26 * Create the namespace
29 cvox
.KeyUtil = function() {
33 * The time in ms at which the ChromeVox Sticky Mode key was pressed.
36 cvox
.KeyUtil
.modeKeyPressTime
= 0;
39 * Indicates if sequencing is currently active for building a keyboard shortcut.
42 cvox
.KeyUtil
.sequencing
= false;
45 * The previous KeySequence when sequencing is ON.
46 * @type {cvox.KeySequence}
48 cvox
.KeyUtil
.prevKeySequence
= null;
52 * The sticky key sequence.
53 * @type {cvox.KeySequence}
55 cvox
.KeyUtil
.stickyKeySequence
= null;
58 * Maximum number of key codes the sequence buffer may hold. This is the max
59 * length of a sequential keyboard shortcut, i.e. the number of key that can be
60 * pressed one after the other while modifier keys (Cros+Shift) are held down.
64 cvox
.KeyUtil
.maxSeqLength
= 2;
68 * Convert a key event into a Key Sequence representation.
70 * @param {Event|cvox.SimpleKeyEvent} keyEvent The keyEvent to convert.
71 * @return {cvox.KeySequence} A key sequence representation of the key event.
73 cvox
.KeyUtil
.keyEventToKeySequence = function(keyEvent
) {
74 var util
= cvox
.KeyUtil
;
75 if (util
.prevKeySequence
&&
76 (util
.maxSeqLength
== util
.prevKeySequence
.length())) {
77 // Reset the sequence buffer if max sequence length is reached.
78 util
.sequencing
= false;
79 util
.prevKeySequence
= null;
81 // Either we are in the middle of a key sequence (N > H), or the key prefix
82 // was pressed before (Ctrl+Z), or sticky mode is enabled
83 var keyIsPrefixed
= util
.sequencing
|| keyEvent
['keyPrefix'] ||
84 keyEvent
['stickyMode'];
86 // Create key sequence.
87 var keySequence
= new cvox
.KeySequence(keyEvent
);
89 // Check if the Cvox key should be considered as pressed because the
90 // modifier key combination is active.
91 var keyWasCvox
= keySequence
.cvoxModifier
;
93 if (keyIsPrefixed
|| keyWasCvox
) {
94 if (!util
.sequencing
&& util
.isSequenceSwitchKeyCode(keySequence
)) {
95 // If this is the beginning of a sequence.
96 util
.sequencing
= true;
97 util
.prevKeySequence
= keySequence
;
99 } else if (util
.sequencing
) {
100 if (util
.prevKeySequence
.addKeyEvent(keyEvent
)) {
101 keySequence
= util
.prevKeySequence
;
102 util
.prevKeySequence
= null;
103 util
.sequencing
= false;
106 throw 'Think sequencing is enabled, yet util.prevKeySequence already' +
107 'has two key codes' + util
.prevKeySequence
;
111 util
.sequencing
= false;
114 // Repeated keys pressed.
115 var currTime
= new Date().getTime();
116 if (cvox
.KeyUtil
.isDoubleTapKey(keySequence
) &&
117 util
.prevKeySequence
&&
118 keySequence
.equals(util
.prevKeySequence
)) {
119 var prevTime
= util
.modeKeyPressTime
;
120 if (prevTime
> 0 && currTime
- prevTime
< 300) { // Double tap
121 keySequence
= util
.prevKeySequence
;
122 keySequence
.doubleTap
= true;
123 util
.prevKeySequence
= null;
124 util
.sequencing
= false;
125 // Resets the search key state tracked for ChromeOS because in OOBE,
126 // we never get a key up for the key down (keyCode 91).
127 if (cvox
.ChromeVox
.isChromeOS
&&
128 keyEvent
.keyCode
== cvox
.KeyUtil
.getStickyKeyCode()) {
129 cvox
.ChromeVox
.searchKeyHeld
= false;
133 // The user double tapped the sticky key but didn't do it within the
134 // required time. It's possible they will try again, so keep track of the
135 // time the sticky key was pressed and keep track of the corresponding
138 util
.prevKeySequence
= keySequence
;
139 util
.modeKeyPressTime
= currTime
;
144 * Returns the string representation of the specified key code.
146 * @param {number} keyCode key code.
147 * @return {string} A string representation of the key event.
149 cvox
.KeyUtil
.keyCodeToString = function(keyCode
) {
159 if ((keyCode
== 91) || (keyCode
== 93)) {
160 if (cvox
.ChromeVox
.isChromeOS
) {
162 } else if (cvox
.ChromeVox
.isMac
) {
168 // TODO(rshearer): This is a hack to work around the special casing of the
169 // sticky mode string that used to happen in keyEventToString. We won't need
170 // it once we move away from strings completely.
174 if (keyCode
>= 65 && keyCode
<= 90) {
176 return String
.fromCharCode(keyCode
);
177 } else if (keyCode
>= 48 && keyCode
<= 57) {
179 return String
.fromCharCode(keyCode
);
182 return '#' + keyCode
;
187 * Returns the keycode of a string representation of the specified modifier.
189 * @param {string} keyString Modifier key.
190 * @return {number} Key code.
192 cvox
.KeyUtil
.modStringToKeyCode = function(keyString
) {
208 * Returns the key codes of a string respresentation of the ChromeVox modifiers.
210 * @return {Array<number>} Array of key codes.
212 cvox
.KeyUtil
.cvoxModKeyCodes = function() {
213 var modKeyCombo
= cvox
.ChromeVox
.modKeyStr
.split(/\+/g);
214 var modKeyCodes
= modKeyCombo
.map(function(keyString
) {
215 return cvox
.KeyUtil
.modStringToKeyCode(keyString
);
221 * Checks if the specified key code is a key used for switching into a sequence
222 * mode. Sequence switch keys are specified in
223 * cvox.KeyUtil.sequenceSwitchKeyCodes
225 * @param {!cvox.KeySequence} rhKeySeq The key sequence to check.
226 * @return {boolean} true if it is a sequence switch keycode, false otherwise.
228 cvox
.KeyUtil
.isSequenceSwitchKeyCode = function(rhKeySeq
) {
229 for (var i
= 0; i
< cvox
.ChromeVox
.sequenceSwitchKeyCodes
.length
; i
++) {
230 var lhKeySeq
= cvox
.ChromeVox
.sequenceSwitchKeyCodes
[i
];
231 if (lhKeySeq
.equals(rhKeySeq
)) {
240 * Get readable string description of the specified keycode.
242 * @param {number} keyCode The key code.
243 * @return {string} Returns a string description.
245 cvox
.KeyUtil
.getReadableNameForKeyCode = function(keyCode
) {
247 return 'Power button';
248 } else if (keyCode
== 17) {
250 } else if (keyCode
== 18) {
252 } else if (keyCode
== 16) {
254 } else if (keyCode
== 9) {
256 } else if ((keyCode
== 91) || (keyCode
== 93)) {
257 if (cvox
.ChromeVox
.isChromeOS
) {
259 } else if (cvox
.ChromeVox
.isMac
) {
264 } else if (keyCode
== 8) {
266 } else if (keyCode
== 32) {
268 } else if (keyCode
== 35) {
270 } else if (keyCode
== 36) {
272 } else if (keyCode
== 37) {
274 } else if (keyCode
== 38) {
276 } else if (keyCode
== 39) {
277 return 'Right arrow';
278 } else if (keyCode
== 40) {
280 } else if (keyCode
== 45) {
282 } else if (keyCode
== 13) {
284 } else if (keyCode
== 27) {
286 } else if (keyCode
== 112) {
287 return cvox
.ChromeVox
.isChromeOS
? 'Back' : 'F1';
288 } else if (keyCode
== 113) {
289 return cvox
.ChromeVox
.isChromeOS
? 'Forward' : 'F2';
290 } else if (keyCode
== 114) {
291 return cvox
.ChromeVox
.isChromeOS
? 'Refresh' : 'F3';
292 } else if (keyCode
== 115) {
293 return cvox
.ChromeVox
.isChromeOS
? 'Toggle full screen' : 'F4';
294 } else if (keyCode
== 116) {
296 } else if (keyCode
== 117) {
298 } else if (keyCode
== 118) {
300 } else if (keyCode
== 119) {
302 } else if (keyCode
== 120) {
304 } else if (keyCode
== 121) {
306 } else if (keyCode
== 122) {
308 } else if (keyCode
== 123) {
310 } else if (keyCode
== 186) {
312 } else if (keyCode
== 187) {
314 } else if (keyCode
== 188) {
316 } else if (keyCode
== 189) {
318 } else if (keyCode
== 190) {
320 } else if (keyCode
== 191) {
321 return 'Forward slash';
322 } else if (keyCode
== 192) {
323 return 'Grave accent';
324 } else if (keyCode
== 219) {
325 return 'Open bracket';
326 } else if (keyCode
== 220) {
328 } else if (keyCode
== 221) {
329 return 'Close bracket';
330 } else if (keyCode
== 222) {
331 return 'Single quote';
332 } else if (keyCode
== 115) {
333 return 'Toggle full screen';
334 } else if (keyCode
>= 48 && keyCode
<= 90) {
335 return String
.fromCharCode(keyCode
);
340 * Get the platform specific sticky key keycode.
342 * @return {number} The platform specific sticky key keycode.
344 cvox
.KeyUtil
.getStickyKeyCode = function() {
345 // TODO (rshearer): This should not be hard-coded here.
346 var stickyKeyCode
= 45; // Insert for Linux and Windows
347 if (cvox
.ChromeVox
.isChromeOS
|| cvox
.ChromeVox
.isMac
) {
348 stickyKeyCode
= 91; // GUI key (Search/Cmd) for ChromeOs and Mac
350 return stickyKeyCode
;
355 * Get readable string description for an internal string representation of a
356 * key or a keyboard shortcut.
358 * @param {string} keyStr The internal string repsentation of a key or
359 * a keyboard shortcut.
360 * @return {?string} Readable string representation of the input.
362 cvox
.KeyUtil
.getReadableNameForStr = function(keyStr
) {
363 // TODO (clchen): Refactor this function away since it is no longer used.
369 * Creates a string representation of a KeySequence.
370 * A KeySequence with a keyCode of 76 ('L') and the control and alt keys down
371 * would return the string 'Ctrl+Alt+L', for example. A key code that doesn't
372 * correspond to a letter or number will typically return a string with a
373 * pound and then its keyCode, like '#39' for Right Arrow. However,
374 * if the opt_readableKeyCode option is specified, the key code will return a
375 * readable string description like 'Right Arrow' instead of '#39'.
377 * The modifiers always come in this order:
384 * @param {cvox.KeySequence} keySequence The KeySequence object.
385 * @param {boolean=} opt_readableKeyCode Whether or not to return a readable
386 * string description instead of a string with a pound symbol and a keycode.
388 * @param {boolean=} opt_modifiers Restrict printout to only modifiers. Defaults
390 * @return {string} Readable string representation of the KeySequence object.
392 cvox
.KeyUtil
.keySequenceToString = function(
393 keySequence
, opt_readableKeyCode
, opt_modifiers
) {
394 // TODO(rshearer): Move this method and the getReadableNameForKeyCode and the
395 // method to KeySequence after we refactor isModifierActive (when the modifie
396 // key becomes customizable and isn't stored as a string). We can't do it
397 // earlier because isModifierActive uses KeyUtil.getReadableNameForKeyCode,
398 // and I don't want KeySequence to depend on KeyUtil.
401 var numKeys
= keySequence
.length();
403 for (var index
= 0; index
< numKeys
; index
++) {
404 if (str
!= '' && !opt_modifiers
) {
406 } else if (str
!= '') {
410 // This iterates through the sequence. Either we're on the first key
411 // pressed or the second
413 for (var keyPressed
in keySequence
.keys
) {
414 // This iterates through the actual key, taking into account any
416 if (!keySequence
.keys
[keyPressed
][index
]) {
420 switch (keyPressed
) {
422 // TODO(rshearer): This is a hack to work around the special casing
423 // of the Ctrl key that used to happen in keyEventToString. We won't
424 // need it once we move away from strings completely.
427 case 'searchKeyHeld':
428 var searchKey
= cvox
.KeyUtil
.getReadableNameForKeyCode(91);
429 modifier
= searchKey
;
435 modifier
= 'AltGraph';
441 var metaKey
= cvox
.KeyUtil
.getReadableNameForKeyCode(91);
445 var keyCode
= keySequence
.keys
[keyPressed
][index
];
446 // We make sure the keyCode isn't for a modifier key. If it is, then
447 // we've already added that into the string above.
448 if (!keySequence
.isModifierKey(keyCode
) && !opt_modifiers
) {
449 if (opt_readableKeyCode
) {
450 tempStr
+= cvox
.KeyUtil
.getReadableNameForKeyCode(keyCode
);
452 tempStr
+= cvox
.KeyUtil
.keyCodeToString(keyCode
);
456 if (str
.indexOf(modifier
) == -1) {
457 tempStr
+= modifier
+ '+';
463 if (str
[str
.length
- 1] == '+') {
464 str
= str
.slice(0, -1);
468 if (keySequence
.cvoxModifier
|| keySequence
.prefixKey
) {
474 } else if (keySequence
.stickyMode
) {
475 if (str
[str
.length
- 1] == '>') {
476 str
= str
.slice(0, -1);
478 str
= str
+ '+' + str
;
484 * Looks up if the given key sequence is triggered via double tap.
485 * @param {cvox.KeySequence} key The key.
486 * @return {boolean} True if key is triggered via double tap.
488 cvox
.KeyUtil
.isDoubleTapKey = function(key
) {
490 var originalState
= key
.doubleTap
;
491 key
.doubleTap
= true;
492 for (var i
= 0, keySeq
; keySeq
= cvox
.KeySequence
.doubleTapCache
[i
]; i
++) {
493 if (keySeq
.equals(key
)) {
498 key
.doubleTap
= originalState
;