1 // Copyright (c) 2012 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.
5 <include src="keyboard_overlay_data.js"/>
6 <include src="keyboard_overlay_accessibility_helper.js"/>
15 var BASE_INSTRUCTIONS = {
22 var MODIFIER_TO_CLASS = {
23 'SHIFT': 'modifier-shift',
24 'CTRL': 'modifier-ctrl',
25 'ALT': 'modifier-alt',
26 'SEARCH': 'modifier-search'
29 var IDENTIFIER_TO_CLASS = {
36 var LABEL_TO_IDENTIFIER = {
42 'disabled': 'DISABLED'
45 var KEYCODE_TO_LABEL = {
100 var keyboardOverlayId = 'en_US';
101 var identifierMap = {};
104 * Returns the layout name.
105 * @return {string} layout name.
107 function getLayoutName() {
108 return getKeyboardGlyphData().layoutName;
112 * Returns layout data.
113 * @return {Array} Keyboard layout data.
115 function getLayout() {
116 return keyboardOverlayData['layouts'][getLayoutName()];
119 // Cache the shortcut data after it is constructed.
120 var shortcutDataCache;
123 * Returns shortcut data.
124 * @return {Object} Keyboard shortcut data.
126 function getShortcutData() {
127 if (shortcutDataCache)
128 return shortcutDataCache;
130 shortcutDataCache = keyboardOverlayData['shortcut'];
132 if (!isDisplayUIScalingEnabled()) {
134 delete shortcutDataCache['+<>CTRL<>SHIFT'];
136 delete shortcutDataCache['-<>CTRL<>SHIFT'];
138 delete shortcutDataCache['0<>CTRL<>SHIFT'];
141 return shortcutDataCache;
145 * Returns the keyboard overlay ID.
146 * @return {string} Keyboard overlay ID.
148 function getKeyboardOverlayId() {
149 return keyboardOverlayId;
153 * Returns keyboard glyph data.
154 * @return {Object} Keyboard glyph data.
156 function getKeyboardGlyphData() {
157 return keyboardOverlayData['keyboardGlyph'][getKeyboardOverlayId()];
161 * Converts a single hex number to a character.
162 * @param {string} hex Hexadecimal string.
163 * @return {string} Unicode values of hexadecimal string.
165 function hex2char(hex) {
170 var n = parseInt(hex, 16);
172 result += String.fromCharCode(n);
173 } else if (n <= 0x10FFFF) {
175 result += (String.fromCharCode(0xD800 | (n >> 10)) +
176 String.fromCharCode(0xDC00 | (n & 0x3FF)));
178 console.error('hex2Char error: Code point out of range :' + hex);
183 var searchIsPressed = false;
186 * Returns a list of modifiers from the key event.
187 * @param {Event} e The key event.
188 * @return {Array} List of modifiers based on key event.
190 function getModifiers(e) {
194 var isKeyDown = (e.type == 'keydown');
195 var keyCodeToModifier = {
201 var modifierWithKeyCode = keyCodeToModifier[e.keyCode];
206 'SEARCH': searchIsPressed
208 if (modifierWithKeyCode)
209 isPressed[modifierWithKeyCode] = isKeyDown;
211 searchIsPressed = isPressed['SEARCH'];
213 // make the result array
214 return ['SHIFT', 'CTRL', 'ALT', 'SEARCH'].filter(
216 return isPressed[modifier];
221 * Returns an ID of the key.
222 * @param {string} identifier Key identifier.
223 * @param {number} i Key number.
224 * @return {string} Key ID.
226 function keyId(identifier, i) {
227 return identifier + '-key-' + i;
231 * Returns an ID of the text on the key.
232 * @param {string} identifier Key identifier.
233 * @param {number} i Key number.
234 * @return {string} Key text ID.
236 function keyTextId(identifier, i) {
237 return identifier + '-key-text-' + i;
241 * Returns an ID of the shortcut text.
242 * @param {string} identifier Key identifier.
243 * @param {number} i Key number.
244 * @return {string} Key shortcut text ID.
246 function shortcutTextId(identifier, i) {
247 return identifier + '-shortcut-text-' + i;
251 * Returns true if |list| contains |e|.
252 * @param {Array} list Container list.
253 * @param {string} e Element string.
254 * @return {boolean} Returns true if the list contains the element.
256 function contains(list, e) {
257 return list.indexOf(e) != -1;
261 * Returns a list of the class names corresponding to the identifier and
263 * @param {string} identifier Key identifier.
264 * @param {Array} modifiers List of key modifiers.
265 * @return {Array} List of class names corresponding to specified params.
267 function getKeyClasses(identifier, modifiers) {
268 var classes = ['keyboard-overlay-key'];
269 for (var i = 0; i < modifiers.length; ++i) {
270 classes.push(MODIFIER_TO_CLASS[modifiers[i]]);
273 if ((identifier == '2A' && contains(modifiers, 'SHIFT')) ||
274 (identifier == '1D' && contains(modifiers, 'CTRL')) ||
275 (identifier == '38' && contains(modifiers, 'ALT')) ||
276 (identifier == 'E0 5B' && contains(modifiers, 'SEARCH'))) {
277 classes.push('pressed');
278 classes.push(IDENTIFIER_TO_CLASS[identifier]);
284 * Returns true if a character is a ASCII character.
285 * @param {string} c A character to be checked.
286 * @return {boolean} True if the character is an ASCII character.
288 function isAscii(c) {
289 var charCode = c.charCodeAt(0);
290 return 0x00 <= charCode && charCode <= 0x7F;
294 * Returns a remapped identiifer based on the preference.
295 * @param {string} identifier Key identifier.
296 * @return {string} Remapped identifier.
298 function remapIdentifier(identifier) {
299 return identifierMap[identifier] || identifier;
303 * Returns a label of the key.
304 * @param {string} keyData Key glyph data.
305 * @param {Array} modifiers Key Modifier list.
306 * @return {string} Label of the key.
308 function getKeyLabel(keyData, modifiers) {
313 return keyData.label;
316 for (var j = 1; j <= 9; j++) {
317 var pos = keyData['p' + j];
321 keyLabel = hex2char(pos);
325 if (isAscii(keyLabel) &&
326 getShortcutData()[getAction(keyLabel, modifiers)]) {
334 * Returns a normalized string used for a key of shortcutData.
337 * keyCode: 'd', modifiers: ['CTRL', 'SHIFT'] => 'd<>CTRL<>SHIFT'
338 * keyCode: 'alt', modifiers: ['ALT', 'SHIFT'] => 'ALT<>SHIFT'
340 * @param {string} keyCode Key code.
341 * @param {Array} modifiers Key Modifier list.
342 * @return {string} Normalized key shortcut data string.
344 function getAction(keyCode, modifiers) {
345 /** @const */ var separatorStr = '<>';
346 if (keyCode.toUpperCase() in MODIFIER_TO_CLASS) {
347 keyCode = keyCode.toUpperCase();
348 if (keyCode in modifiers) {
349 return modifiers.join(separatorStr);
351 var action = [keyCode].concat(modifiers);
353 return action.join(separatorStr);
356 return [keyCode].concat(modifiers).join(separatorStr);
360 * Returns a text which displayed on a key.
361 * @param {string} keyData Key glyph data.
362 * @return {string} Key text value.
364 function getKeyTextValue(keyData) {
366 // Do not show text on the space key.
367 if (keyData.label == 'space') {
370 return keyData.label;
374 for (var j = 1; j <= 9; ++j) {
375 var pos = keyData['p' + j];
376 if (pos && pos.length > 0) {
377 chars.push(hex2char(pos));
380 return chars.join(' ');
384 * Updates the whole keyboard.
385 * @param {Array} modifiers Key Modifier list.
387 function update(modifiers) {
388 var instructions = $('instructions');
389 if (modifiers.length == 0) {
390 instructions.style.visibility = 'visible';
392 instructions.style.visibility = 'hidden';
395 var keyboardGlyphData = getKeyboardGlyphData();
396 var shortcutData = getShortcutData();
397 var layout = getLayout();
398 for (var i = 0; i < layout.length; ++i) {
399 var identifier = remapIdentifier(layout[i][0]);
400 var keyData = keyboardGlyphData.keys[identifier];
401 var classes = getKeyClasses(identifier, modifiers, keyData);
402 var keyLabel = getKeyLabel(keyData, modifiers);
403 var shortcutId = shortcutData[getAction(keyLabel, modifiers)];
404 if (modifiers.length == 1 && modifiers[0] == 'SHIFT' &&
405 identifier == '2A') {
406 // Currently there is no way to identify whether the left shift or the
407 // right shift is preesed from the key event, so I assume the left shift
408 // key is pressed here and do not show keyboard shortcut description for
409 // 'Shift - Shift' (Toggle caps lock) on the left shift key, the
410 // identifier of which is '2A'.
411 // TODO(mazda): Remove this workaround (http://crosbug.com/18047)
415 classes.push('is-shortcut');
418 var key = $(keyId(identifier, i));
419 key.className = classes.join(' ');
425 var keyText = $(keyTextId(identifier, i));
426 var keyTextValue = getKeyTextValue(keyData);
428 keyText.style.visibility = 'visible';
430 keyText.style.visibility = 'hidden';
432 keyText.textContent = keyTextValue;
434 var shortcutText = $(shortcutTextId(identifier, i));
436 shortcutText.style.visibility = 'visible';
437 shortcutText.textContent = loadTimeData.getString(shortcutId);
439 shortcutText.style.visibility = 'hidden';
442 var format = keyboardGlyphData.keys[layout[i][0]].format;
444 if (format == 'left' || format == 'right') {
445 shortcutText.style.textAlign = format;
446 keyText.style.textAlign = format;
453 * A callback function for onkeydown and onkeyup events.
454 * @param {Event} e Key event.
456 function handleKeyEvent(e) {
457 if (!getKeyboardOverlayId()) {
460 var modifiers = getModifiers(e);
462 KeyboardOverlayAccessibilityHelper.maybeSpeakAllShortcuts(modifiers);
467 * Initializes the layout of the keys.
469 function initLayout() {
470 // Add data for the caps lock key
471 var keys = getKeyboardGlyphData().keys;
472 if (!('3A' in keys)) {
473 keys['3A'] = {label: 'caps lock', format: 'left'};
475 // Add data for the special key representing a disabled key
476 keys['DISABLED'] = {label: 'disabled', format: 'left'};
478 var layout = getLayout();
479 var keyboard = document.body;
480 var minX = window.innerWidth;
482 var minY = window.innerHeight;
484 var multiplier = 1.38 * window.innerWidth / BASE_KEYBOARD.width;
488 for (var i = 0; i < layout.length; i++) {
489 var array = layout[i];
490 var identifier = remapIdentifier(array[0]);
491 var x = Math.round((array[1] + offsetX) * multiplier);
492 var y = Math.round((array[2] + offsetY) * multiplier);
493 var w = Math.round((array[3] - keyMargin) * multiplier);
494 var h = Math.round((array[4] - keyMargin) * multiplier);
496 var key = document.createElement('div');
497 key.id = keyId(identifier, i);
498 key.className = 'keyboard-overlay-key';
499 key.style.left = x + 'px';
500 key.style.top = y + 'px';
501 key.style.width = w + 'px';
502 key.style.height = h + 'px';
504 var keyText = document.createElement('div');
505 keyText.id = keyTextId(identifier, i);
506 keyText.className = 'keyboard-overlay-key-text';
507 keyText.style.visibility = 'hidden';
508 key.appendChild(keyText);
510 var shortcutText = document.createElement('div');
511 shortcutText.id = shortcutTextId(identifier, i);
512 shortcutText.className = 'keyboard-overlay-shortcut-text';
513 shortcutText.style.visilibity = 'hidden';
514 key.appendChild(shortcutText);
515 keyboard.appendChild(key);
517 minX = Math.min(minX, x);
518 maxX = Math.max(maxX, x + w);
519 minY = Math.min(minY, y);
520 maxY = Math.max(maxY, y + h);
523 var width = maxX - minX + 1;
524 var height = maxY - minY + 1;
525 keyboard.style.width = (width + 2 * (minX + 1)) + 'px';
526 keyboard.style.height = (height + 2 * (minY + 1)) + 'px';
528 var instructions = document.createElement('div');
529 instructions.id = 'instructions';
530 instructions.className = 'keyboard-overlay-instructions';
531 instructions.style.left = ((BASE_INSTRUCTIONS.left - BASE_KEYBOARD.left) *
532 width / BASE_KEYBOARD.width + minX) + 'px';
533 instructions.style.top = ((BASE_INSTRUCTIONS.top - BASE_KEYBOARD.top) *
534 height / BASE_KEYBOARD.height + minY) + 'px';
535 instructions.style.width = (width * BASE_INSTRUCTIONS.width /
536 BASE_KEYBOARD.width) + 'px';
537 instructions.style.height = (height * BASE_INSTRUCTIONS.height /
538 BASE_KEYBOARD.height) + 'px';
540 var instructionsText = document.createElement('div');
541 instructionsText.id = 'instructions-text';
542 instructionsText.className = 'keyboard-overlay-instructions-text';
543 instructionsText.innerHTML =
544 loadTimeData.getString('keyboardOverlayInstructions');
545 instructions.appendChild(instructionsText);
546 var instructionsHideText = document.createElement('div');
547 instructionsHideText.id = 'instructions-hide-text';
548 instructionsHideText.className = 'keyboard-overlay-instructions-hide-text';
549 instructionsHideText.innerHTML =
550 loadTimeData.getString('keyboardOverlayInstructionsHide');
551 instructions.appendChild(instructionsHideText);
552 var learnMoreLinkText = document.createElement('div');
553 learnMoreLinkText.id = 'learn-more-text';
554 learnMoreLinkText.className = 'keyboard-overlay-learn-more-text';
555 learnMoreLinkText.addEventListener('click', learnMoreClicked);
556 var learnMoreLinkAnchor = document.createElement('a');
557 learnMoreLinkAnchor.href =
558 loadTimeData.getString('keyboardOverlayLearnMoreURL');
559 learnMoreLinkAnchor.textContent =
560 loadTimeData.getString('keyboardOverlayLearnMore');
561 learnMoreLinkText.appendChild(learnMoreLinkAnchor);
562 instructions.appendChild(learnMoreLinkText);
563 keyboard.appendChild(instructions);
567 * Returns true if the device has a diamond key.
568 * @return {boolean} Returns true if the device has a diamond key.
570 function hasDiamondKey() {
571 return loadTimeData.getBoolean('keyboardOverlayHasChromeOSDiamondKey');
575 * Returns true if display scaling feature is enabled.
576 * @return {boolean} True if display scaling feature is enabled.
578 function isDisplayUIScalingEnabled() {
579 return loadTimeData.getBoolean('keyboardOverlayIsDisplayUIScalingEnabled');
583 * Initializes the layout and the key labels for the keyboard that has a diamond
586 function initDiamondKey() {
587 var newLayoutData = {
588 '1D': [65.0, 287.0, 60.0, 60.0], // left Ctrl
589 '38': [185.0, 287.0, 60.0, 60.0], // left Alt
590 'E0 5B': [125.0, 287.0, 60.0, 60.0], // search
591 '3A': [5.0, 167.0, 105.0, 60.0], // caps lock
592 '5B': [803.0, 6.0, 72.0, 35.0], // lock key
593 '5D': [5.0, 287.0, 60.0, 60.0] // diamond key
596 var layout = getLayout();
597 var powerKeyIndex = -1;
598 var powerKeyId = '00';
599 for (var i = 0; i < layout.length; i++) {
600 var keyId = layout[i][0];
601 if (keyId in newLayoutData) {
602 layout[i] = [keyId].concat(newLayoutData[keyId]);
603 delete newLayoutData[keyId];
605 if (keyId == powerKeyId)
608 for (var keyId in newLayoutData)
609 layout.push([keyId].concat(newLayoutData[keyId]));
611 // Remove the power key.
612 if (powerKeyIndex != -1)
613 layout.splice(powerKeyIndex, 1);
615 var keyData = getKeyboardGlyphData()['keys'];
617 '3A': {'label': 'caps lock', 'format': 'left'},
618 '5B': {'label': 'lock'},
619 '5D': {'label': 'diamond', 'format': 'left'}
621 for (var keyId in newKeyData)
622 keyData[keyId] = newKeyData[keyId];
626 * A callback function for the onload event of the body element.
629 document.addEventListener('keydown', handleKeyEvent);
630 document.addEventListener('keyup', handleKeyEvent);
631 chrome.send('getLabelMap');
635 * Initializes the global map for remapping identifiers of modifier keys based
637 * Called after sending the 'getLabelMap' message.
638 * @param {Object} remap Identifier map.
640 function initIdentifierMap(remap) {
641 for (var key in remap) {
642 var val = remap[key];
643 if ((key in LABEL_TO_IDENTIFIER) &&
644 (val in LABEL_TO_IDENTIFIER)) {
645 identifierMap[LABEL_TO_IDENTIFIER[key]] =
646 LABEL_TO_IDENTIFIER[val];
648 console.error('Invalid label map element: ' + key + ', ' + val);
651 chrome.send('getInputMethodId');
655 * Initializes the global keyboad overlay ID and the layout of keys.
656 * Called after sending the 'getInputMethodId' message.
657 * @param {inputMethodId} inputMethodId Input Method Identifier.
659 function initKeyboardOverlayId(inputMethodId) {
660 // Libcros returns an empty string when it cannot find the keyboard overlay ID
661 // corresponding to the current input method.
662 // In such a case, fallback to the default ID (en_US).
663 var inputMethodIdToOverlayId =
664 keyboardOverlayData['inputMethodIdToOverlayId'];
666 keyboardOverlayId = inputMethodIdToOverlayId[inputMethodId];
668 if (!keyboardOverlayId) {
669 console.error('No keyboard overlay ID for ' + inputMethodId);
670 keyboardOverlayId = 'en_US';
672 while (document.body.firstChild) {
673 document.body.removeChild(document.body.firstChild);
675 // We show Japanese layout as-is because the user has chosen the layout
676 // that is quite diffrent from the physical layout that has a diamond key.
677 if (hasDiamondKey() && getLayoutName() != 'J')
681 window.webkitRequestAnimationFrame(function() {
682 chrome.send('didPaint');
687 * Handles click events of the learn more link.
688 * @param {Event} e Mouse click event.
690 function learnMoreClicked(e) {
691 chrome.send('openLearnMorePage');
692 chrome.send('DialogClose');
696 document.addEventListener('DOMContentLoaded', init);