// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var BASE_KEYBOARD = {
top: 0,
left: 0,
width: 1237,
height: 514
};
var BASE_INSTRUCTIONS = {
top: 194,
left: 370,
width: 498,
height: 112
};
var LABEL_TO_KEY_TEXT = {
alt: 'alt',
backspace: 'backspace',
ctrl: 'ctrl',
enter: 'enter',
esc: 'esc',
glyph_arrow_down: 'down',
glyph_arrow_left: 'left',
glyph_arrow_right: 'right',
glyph_arrow_up: 'up',
glyph_back: 'back',
glyph_backspace: 'backspace',
glyph_brightness_down: 'bright down',
glyph_brightness_up: 'bright up',
glyph_enter: 'enter',
glyph_forward: 'forward',
glyph_fullscreen: 'full screen',
glyph_ime: 'ime',
glyph_lock: 'lock',
glyph_overview: 'switch window',
glyph_power: 'power',
glyph_right: 'right',
glyph_reload: 'reload',
glyph_search: 'search',
glyph_shift: 'shift',
glyph_tab: 'tab',
glyph_tools: 'tools',
glyph_volume_down: 'vol. down',
glyph_volume_mute: 'mute',
glyph_volume_up: 'vol. up',
shift: 'shift',
tab: 'tab'
};
var MODIFIER_TO_CLASS = {
'SHIFT': 'modifier-shift',
'CTRL': 'modifier-ctrl',
'ALT': 'modifier-alt'
};
var IDENTIFIER_TO_CLASS = {
'2A': 'is-shift',
'1D': 'is-ctrl',
'38': 'is-alt'
};
var keyboardOverlayId = 'en_US';
/**
* Returns layouts data.
*/
function getLayouts() {
return keyboardOverlayData['layouts'];
}
/**
* Returns shortcut data.
*/
function getShortcutData() {
return keyboardOverlayData['shortcut'];
}
/**
* Returns the keyboard overlay ID.
*/
function getKeyboardOverlayId() {
return keyboardOverlayId
}
/**
* Returns keyboard glyph data.
*/
function getKeyboardGlyphData() {
return keyboardOverlayData['keyboardGlyph'][getKeyboardOverlayId()];
}
/**
* Converts a single hex number to a character.
*/
function hex2char(hex) {
if (!hex) {
return '';
}
var result = '';
var n = parseInt(hex, 16);
if (n <= 0xFFFF) {
result += String.fromCharCode(n);
} else if (n <= 0x10FFFF) {
n -= 0x10000;
result += (String.fromCharCode(0xD800 | (n >> 10)) +
String.fromCharCode(0xDC00 | (n & 0x3FF)));
} else {
console.error('hex2Char error: Code point out of range :' + hex);
}
return result;
}
/**
* Returns a list of modifiers from the key event.
*/
function getModifiers(e) {
if (!e) {
return [];
}
var isKeyDown = (e.type == 'keydown');
var keyCodeToModifier = {
16: 'SHIFT',
17: 'CTRL',
18: 'ALT',
91: 'ALT', // left ALT pressed with SHIFT
92: 'ALT', // right ALT pressed with SHIFT
};
var modifierWithKeyCode = keyCodeToModifier[e.keyCode];
var isPressed = {'SHIFT': e.shiftKey, 'CTRL': e.ctrlKey, 'ALT': e.altKey};
// if e.keyCode is one of Shift, Ctrl and Alt, isPressed should
// be changed because the key currently pressed
// does not affect the values of e.shiftKey, e.ctrlKey and e.altKey
if(modifierWithKeyCode){
isPressed[modifierWithKeyCode] = isKeyDown;
}
// make the result array
return ['SHIFT', 'CTRL', 'ALT'].filter(
function(modifier) {
return isPressed[modifier];
}).sort();
}
/**
* Returns an ID of the key.
*/
function keyId(identifier, i) {
return identifier + '-key-' + i;
}
/**
* Returns an ID of the text on the key.
*/
function keyTextId(identifier, i) {
return identifier + '-key-text-' + i;
}
/**
* Returns an ID of the shortcut text.
*/
function shortcutTextId(identifier, i) {
return identifier + '-shortcut-text-' + i;
}
/**
* Returns true if |list| contains |e|.
*/
function contains(list, e) {
return list.indexOf(e) != -1;
}
/**
* Returns a list of the class names corresponding to the identifier and
* modifiers.
*/
function getKeyClasses(identifier, modifiers) {
var classes = ['keyboard-overlay-key'];
for (var i = 0; i < modifiers.length; ++i) {
classes.push(MODIFIER_TO_CLASS[modifiers[i]]);
}
if ((identifier == '2A' && contains(modifiers, 'SHIFT')) ||
(identifier == '1D' && contains(modifiers, 'CTRL')) ||
(identifier == '38' && contains(modifiers, 'ALT'))) {
classes.push('pressed');
classes.push(IDENTIFIER_TO_CLASS[identifier]);
}
return classes;
}
/**
* Returns true if a character is a ASCII character.
*/
function isAscii(c) {
var charCode = c.charCodeAt(0);
return 0x00 <= charCode && charCode <= 0x7F;
}
/**
* Returns a label of the key.
*/
function getKeyLabel(keyData, modifiers) {
if (!keyData) {
return '';
}
if (keyData.label in LABEL_TO_KEY_TEXT) {
return LABEL_TO_KEY_TEXT[keyData.label];
}
var keyLabel = '';
for (var j = 1; j <= 9; j++) {
var pos = keyData['p' + j];
if (!pos) {
continue;
}
if (LABEL_TO_KEY_TEXT[pos]) {
return LABEL_TO_KEY_TEXT[pos];
}
keyLabel = hex2char(pos);
if (!keyLabel) {
continue;
}
if (isAscii(keyLabel) &&
getShortcutData()[getAction(keyLabel, modifiers)]) {
break;
}
}
return keyLabel;
}
/**
* Returns a normalized string used for a key of shortcutData.
*
* Examples:
* keycode: 'd', modifiers: ['CTRL', 'SHIFT'] => 'd<>CTRL<>SHIFT'
* keycode: 'alt', modifiers: ['ALT', 'SHIFT'] => 'ALT<>SHIFT'
*/
function getAction(keycode, modifiers) {
const SEPARATOR = '<>';
if (keycode.toUpperCase() in MODIFIER_TO_CLASS) {
keycode = keycode.toUpperCase();
if (keycode in modifiers) {
return modifiers.join(SEPARATOR);
} else {
var action = [keycode].concat(modifiers)
action.sort();
return action.join(SEPARATOR);
}
}
return [keycode].concat(modifiers).join(SEPARATOR);
}
/**
* Returns a text which displayed on a key.
*/
function getKeyTextValue(keyData) {
if (LABEL_TO_KEY_TEXT[keyData.label]) {
return LABEL_TO_KEY_TEXT[keyData.label];
}
var chars = [];
for (var j = 1; j <= 9; ++j) {
var pos = keyData['p' + j];
if (LABEL_TO_KEY_TEXT[pos]) {
return LABEL_TO_KEY_TEXT[pos];
}
if (pos && pos.length > 0) {
chars.push(hex2char(pos));
}
}
return chars.join(' ');
}
/**
* Updates the whole keyboard.
*/
function update(modifiers) {
var instructions = document.getElementById('instructions');
if (modifiers.length == 0) {
instructions.style.visibility = 'visible';
} else {
instructions.style.visibility = 'hidden';
}
var keyboardGlyphData = getKeyboardGlyphData();
var shortcutData = getShortcutData();
var layout = getLayouts()[keyboardGlyphData.layoutName];
for (var i = 0; i < layout.length; ++i) {
var identifier = layout[i][0];
var keyData = keyboardGlyphData.keys[identifier];
var classes = getKeyClasses(identifier, modifiers, keyData);
var keyLabel = getKeyLabel(keyData, modifiers);
var shortcutId = shortcutData[getAction(keyLabel, modifiers)];
if (shortcutId) {
classes.push('is-shortcut');
}
var key = document.getElementById(keyId(identifier, i));
key.className = classes.join(' ');
if (!keyData) {
continue;
}
var keyText = document.getElementById(keyTextId(identifier, i));
var keyTextValue = getKeyTextValue(keyData);
if (keyTextValue) {
keyText.style.visibility = 'visible';
} else {
keyText.style.visibility = 'hidden';
}
keyText.textContent = keyTextValue;
var shortcutText = document.getElementById(shortcutTextId(identifier, i));
if (shortcutId) {
shortcutText.style.visibility = 'visible';
shortcutText.textContent = templateData[shortcutId];
} else {
shortcutText.style.visibility = 'hidden';
}
if (keyData.format) {
var format = keyData.format;
if (format == 'left' || format == 'right') {
shortcutText.style.textAlign = format;
keyText.style.textAlign = format;
}
}
}
}
/**
* A callback function for onkeydown and onkeyup events.
*/
function handleKeyEvent(e){
var modifiers = getModifiers(e);
if (!getKeyboardOverlayId()) {
return;
}
update(modifiers);
}
/**
* Initializes the layout of the keys.
*/
function initLayout() {
var layout = getLayouts()[getKeyboardGlyphData().layoutName];
var keyboard = document.body;
var minX = window.innerWidth;
var maxX = 0;
var minY = window.innerHeight;
var maxY = 0;
var multiplier = 1.38 * window.innerWidth / BASE_KEYBOARD.width;
var keyMargin = 7;
var offsetX = 10;
var offsetY = 7;
for (var i = 0; i < layout.length; i++) {
var array = layout[i];
var identifier = array[0];
var x = Math.round((array[1] + offsetX) * multiplier);
var y = Math.round((array[2] + offsetY) * multiplier);
var w = Math.round((array[3] - keyMargin) * multiplier);
var h = Math.round((array[4] - keyMargin) * multiplier);
var key = document.createElement('div');
key.id = keyId(identifier, i);
key.className = 'keyboard-overlay-key';
key.style.left = x + 'px';
key.style.top = y + 'px';
key.style.width = w + 'px';
key.style.height = h + 'px';
var keyText = document.createElement('div');
keyText.id = keyTextId(identifier, i);
keyText.className = 'keyboard-overlay-key-text';
keyText.style.visibility = 'hidden';
key.appendChild(keyText);
var shortcutText = document.createElement('div');
shortcutText.id = shortcutTextId(identifier, i);
shortcutText.className = 'keyboard-overlay-shortcut-text';
shortcutText.style.visilibity = 'hidden';
key.appendChild(shortcutText);
keyboard.appendChild(key);
minX = Math.min(minX, x);
maxX = Math.max(maxX, x + w);
minY = Math.min(minY, y);
maxY = Math.max(maxY, y + h);
}
var width = maxX - minX + 1;
var height = maxY - minY + 1;
keyboard.style.width = (width + 2 * (minX + 1)) + 'px';
keyboard.style.height = (height + 2 * (minY + 1)) + 'px';
var instructions = document.createElement('div');
instructions.id = 'instructions';
instructions.className = 'keyboard-overlay-instructions';
instructions.style.left = ((BASE_INSTRUCTIONS.left - BASE_KEYBOARD.left) *
width / BASE_KEYBOARD.width + minX) + 'px';
instructions.style.top = ((BASE_INSTRUCTIONS.top - BASE_KEYBOARD.top) *
height / BASE_KEYBOARD.height + minY) + 'px';
instructions.style.width = (width * BASE_INSTRUCTIONS.width /
BASE_KEYBOARD.width) + 'px';
instructions.style.height = (height * BASE_INSTRUCTIONS.height /
BASE_KEYBOARD.height) + 'px';
var instructionsText = document.createElement('div');
instructionsText.id = 'instructions-text';
instructionsText.className = 'keyboard-overlay-instructions-text';
instructionsText.innerHTML = templateData.keyboardOverlayInstructions;
instructions.appendChild(instructionsText);
var instructionsHideText = document.createElement('div');
instructionsHideText.id = 'instructions-hide-text';
instructionsHideText.className = 'keyboard-overlay-instructions-hide-text';
instructionsHideText.innerHTML = templateData.keyboardOverlayInstructionsHide;
instructions.appendChild(instructionsHideText);
keyboard.appendChild(instructions);
}
/**
* A callback function for the onload event of the body element.
*/
function init() {
document.addEventListener('keydown', handleKeyEvent);
document.addEventListener('keyup', handleKeyEvent);
chrome.send('getKeyboardOverlayId');
}
/**
* Initializes the global keyboad overlay ID and the layout of keys.
* Called after sending the 'getKeyboardOverlayId' message.
*/
function initKeyboardOverlayId(overlayId) {
// Libcros returns an empty string when it cannot find the keyboard overlay ID
// corresponding to the current input method.
// In such a case, fallback to the default ID (en_US).
if (overlayId) {
keyboardOverlayId = overlayId;
}
while(document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
initLayout();
update();
}
document.addEventListener('DOMContentLoaded', init);