1// Copyright (c) 2011 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. 4 5var BASE_KEYBOARD = { 6 top: 0, 7 left: 0, 8 width: 1237, 9 height: 514 10}; 11 12var BASE_INSTRUCTIONS = { 13 top: 194, 14 left: 370, 15 width: 498, 16 height: 112 17}; 18 19var LABEL_TO_KEY_TEXT = { 20 alt: 'alt', 21 backspace: 'backspace', 22 ctrl: 'ctrl', 23 enter: 'enter', 24 esc: 'esc', 25 glyph_arrow_down: 'down', 26 glyph_arrow_left: 'left', 27 glyph_arrow_right: 'right', 28 glyph_arrow_up: 'up', 29 glyph_back: 'back', 30 glyph_backspace: 'backspace', 31 glyph_brightness_down: 'bright down', 32 glyph_brightness_up: 'bright up', 33 glyph_enter: 'enter', 34 glyph_forward: 'forward', 35 glyph_fullscreen: 'full screen', 36 glyph_ime: 'ime', 37 glyph_lock: 'lock', 38 glyph_overview: 'switch window', 39 glyph_power: 'power', 40 glyph_right: 'right', 41 glyph_reload: 'reload', 42 glyph_search: 'search', 43 glyph_shift: 'shift', 44 glyph_tab: 'tab', 45 glyph_tools: 'tools', 46 glyph_volume_down: 'vol. down', 47 glyph_volume_mute: 'mute', 48 glyph_volume_up: 'vol. up', 49 shift: 'shift', 50 tab: 'tab' 51}; 52 53var MODIFIER_TO_CLASS = { 54 'SHIFT': 'modifier-shift', 55 'CTRL': 'modifier-ctrl', 56 'ALT': 'modifier-alt' 57}; 58 59var IDENTIFIER_TO_CLASS = { 60 '2A': 'is-shift', 61 '1D': 'is-ctrl', 62 '38': 'is-alt' 63}; 64 65var keyboardOverlayId = 'en_US'; 66 67/** 68 * Returns layouts data. 69 */ 70function getLayouts() { 71 return keyboardOverlayData['layouts']; 72} 73 74/** 75 * Returns shortcut data. 76 */ 77function getShortcutData() { 78 return keyboardOverlayData['shortcut']; 79} 80 81/** 82 * Returns the keyboard overlay ID. 83 */ 84function getKeyboardOverlayId() { 85 return keyboardOverlayId 86} 87 88/** 89 * Returns keyboard glyph data. 90 */ 91function getKeyboardGlyphData() { 92 return keyboardOverlayData['keyboardGlyph'][getKeyboardOverlayId()]; 93} 94 95/** 96 * Converts a single hex number to a character. 97 */ 98function hex2char(hex) { 99 if (!hex) { 100 return ''; 101 } 102 var result = ''; 103 var n = parseInt(hex, 16); 104 if (n <= 0xFFFF) { 105 result += String.fromCharCode(n); 106 } else if (n <= 0x10FFFF) { 107 n -= 0x10000; 108 result += (String.fromCharCode(0xD800 | (n >> 10)) + 109 String.fromCharCode(0xDC00 | (n & 0x3FF))); 110 } else { 111 console.error('hex2Char error: Code point out of range :' + hex); 112 } 113 return result; 114} 115 116/** 117 * Returns a list of modifiers from the key event. 118 */ 119function getModifiers(e) { 120 if (!e) { 121 return []; 122 } 123 var isKeyDown = (e.type == 'keydown'); 124 var keyCodeToModifier = { 125 16: 'SHIFT', 126 17: 'CTRL', 127 18: 'ALT', 128 91: 'ALT', // left ALT pressed with SHIFT 129 92: 'ALT', // right ALT pressed with SHIFT 130 }; 131 var modifierWithKeyCode = keyCodeToModifier[e.keyCode]; 132 var isPressed = {'SHIFT': e.shiftKey, 'CTRL': e.ctrlKey, 'ALT': e.altKey}; 133 // if e.keyCode is one of Shift, Ctrl and Alt, isPressed should 134 // be changed because the key currently pressed 135 // does not affect the values of e.shiftKey, e.ctrlKey and e.altKey 136 if(modifierWithKeyCode){ 137 isPressed[modifierWithKeyCode] = isKeyDown; 138 } 139 // make the result array 140 return ['SHIFT', 'CTRL', 'ALT'].filter( 141 function(modifier) { 142 return isPressed[modifier]; 143 }).sort(); 144} 145 146/** 147 * Returns an ID of the key. 148 */ 149function keyId(identifier, i) { 150 return identifier + '-key-' + i; 151} 152 153/** 154 * Returns an ID of the text on the key. 155 */ 156function keyTextId(identifier, i) { 157 return identifier + '-key-text-' + i; 158} 159 160/** 161 * Returns an ID of the shortcut text. 162 */ 163function shortcutTextId(identifier, i) { 164 return identifier + '-shortcut-text-' + i; 165} 166 167/** 168 * Returns true if |list| contains |e|. 169 */ 170function contains(list, e) { 171 return list.indexOf(e) != -1; 172} 173 174/** 175 * Returns a list of the class names corresponding to the identifier and 176 * modifiers. 177 */ 178function getKeyClasses(identifier, modifiers) { 179 var classes = ['keyboard-overlay-key']; 180 for (var i = 0; i < modifiers.length; ++i) { 181 classes.push(MODIFIER_TO_CLASS[modifiers[i]]); 182 } 183 184 if ((identifier == '2A' && contains(modifiers, 'SHIFT')) || 185 (identifier == '1D' && contains(modifiers, 'CTRL')) || 186 (identifier == '38' && contains(modifiers, 'ALT'))) { 187 classes.push('pressed'); 188 classes.push(IDENTIFIER_TO_CLASS[identifier]); 189 } 190 return classes; 191} 192 193/** 194 * Returns true if a character is a ASCII character. 195 */ 196function isAscii(c) { 197 var charCode = c.charCodeAt(0); 198 return 0x00 <= charCode && charCode <= 0x7F; 199} 200 201/** 202 * Returns a label of the key. 203 */ 204function getKeyLabel(keyData, modifiers) { 205 if (!keyData) { 206 return ''; 207 } 208 if (keyData.label in LABEL_TO_KEY_TEXT) { 209 return LABEL_TO_KEY_TEXT[keyData.label]; 210 } 211 var keyLabel = ''; 212 for (var j = 1; j <= 9; j++) { 213 var pos = keyData['p' + j]; 214 if (!pos) { 215 continue; 216 } 217 if (LABEL_TO_KEY_TEXT[pos]) { 218 return LABEL_TO_KEY_TEXT[pos]; 219 } 220 keyLabel = hex2char(pos); 221 if (!keyLabel) { 222 continue; 223 } 224 if (isAscii(keyLabel) && 225 getShortcutData()[getAction(keyLabel, modifiers)]) { 226 break; 227 } 228 } 229 return keyLabel; 230} 231 232/** 233 * Returns a normalized string used for a key of shortcutData. 234 * 235 * Examples: 236 * keycode: 'd', modifiers: ['CTRL', 'SHIFT'] => 'd<>CTRL<>SHIFT' 237 * keycode: 'alt', modifiers: ['ALT', 'SHIFT'] => 'ALT<>SHIFT' 238 */ 239function getAction(keycode, modifiers) { 240 const SEPARATOR = '<>'; 241 if (keycode.toUpperCase() in MODIFIER_TO_CLASS) { 242 keycode = keycode.toUpperCase(); 243 if (keycode in modifiers) { 244 return modifiers.join(SEPARATOR); 245 } else { 246 var action = [keycode].concat(modifiers) 247 action.sort(); 248 return action.join(SEPARATOR); 249 } 250 } 251 return [keycode].concat(modifiers).join(SEPARATOR); 252} 253 254/** 255 * Returns a text which displayed on a key. 256 */ 257function getKeyTextValue(keyData) { 258 if (LABEL_TO_KEY_TEXT[keyData.label]) { 259 return LABEL_TO_KEY_TEXT[keyData.label]; 260 } 261 262 var chars = []; 263 for (var j = 1; j <= 9; ++j) { 264 var pos = keyData['p' + j]; 265 if (LABEL_TO_KEY_TEXT[pos]) { 266 return LABEL_TO_KEY_TEXT[pos]; 267 } 268 if (pos && pos.length > 0) { 269 chars.push(hex2char(pos)); 270 } 271 } 272 return chars.join(' '); 273} 274 275/** 276 * Updates the whole keyboard. 277 */ 278function update(modifiers) { 279 var instructions = document.getElementById('instructions'); 280 if (modifiers.length == 0) { 281 instructions.style.visibility = 'visible'; 282 } else { 283 instructions.style.visibility = 'hidden'; 284 } 285 286 var keyboardGlyphData = getKeyboardGlyphData(); 287 var shortcutData = getShortcutData(); 288 var layout = getLayouts()[keyboardGlyphData.layoutName]; 289 for (var i = 0; i < layout.length; ++i) { 290 var identifier = layout[i][0]; 291 var keyData = keyboardGlyphData.keys[identifier]; 292 var classes = getKeyClasses(identifier, modifiers, keyData); 293 var keyLabel = getKeyLabel(keyData, modifiers); 294 var shortcutId = shortcutData[getAction(keyLabel, modifiers)]; 295 if (shortcutId) { 296 classes.push('is-shortcut'); 297 } 298 299 var key = document.getElementById(keyId(identifier, i)); 300 key.className = classes.join(' '); 301 302 if (!keyData) { 303 continue; 304 } 305 306 var keyText = document.getElementById(keyTextId(identifier, i)); 307 var keyTextValue = getKeyTextValue(keyData); 308 if (keyTextValue) { 309 keyText.style.visibility = 'visible'; 310 } else { 311 keyText.style.visibility = 'hidden'; 312 } 313 keyText.textContent = keyTextValue; 314 315 var shortcutText = document.getElementById(shortcutTextId(identifier, i)); 316 if (shortcutId) { 317 shortcutText.style.visibility = 'visible'; 318 shortcutText.textContent = templateData[shortcutId]; 319 } else { 320 shortcutText.style.visibility = 'hidden'; 321 } 322 323 if (keyData.format) { 324 var format = keyData.format; 325 if (format == 'left' || format == 'right') { 326 shortcutText.style.textAlign = format; 327 keyText.style.textAlign = format; 328 } 329 } 330 } 331} 332 333/** 334 * A callback function for onkeydown and onkeyup events. 335 */ 336function handleKeyEvent(e){ 337 var modifiers = getModifiers(e); 338 if (!getKeyboardOverlayId()) { 339 return; 340 } 341 update(modifiers); 342} 343 344/** 345 * Initializes the layout of the keys. 346 */ 347function initLayout() { 348 var layout = getLayouts()[getKeyboardGlyphData().layoutName]; 349 var keyboard = document.body; 350 var minX = window.innerWidth; 351 var maxX = 0; 352 var minY = window.innerHeight; 353 var maxY = 0; 354 var multiplier = 1.38 * window.innerWidth / BASE_KEYBOARD.width; 355 var keyMargin = 7; 356 var offsetX = 10; 357 var offsetY = 7; 358 for (var i = 0; i < layout.length; i++) { 359 var array = layout[i]; 360 var identifier = array[0]; 361 var x = Math.round((array[1] + offsetX) * multiplier); 362 var y = Math.round((array[2] + offsetY) * multiplier); 363 var w = Math.round((array[3] - keyMargin) * multiplier); 364 var h = Math.round((array[4] - keyMargin) * multiplier); 365 366 var key = document.createElement('div'); 367 key.id = keyId(identifier, i); 368 key.className = 'keyboard-overlay-key'; 369 key.style.left = x + 'px'; 370 key.style.top = y + 'px'; 371 key.style.width = w + 'px'; 372 key.style.height = h + 'px'; 373 374 var keyText = document.createElement('div'); 375 keyText.id = keyTextId(identifier, i); 376 keyText.className = 'keyboard-overlay-key-text'; 377 keyText.style.visibility = 'hidden'; 378 key.appendChild(keyText); 379 380 var shortcutText = document.createElement('div'); 381 shortcutText.id = shortcutTextId(identifier, i); 382 shortcutText.className = 'keyboard-overlay-shortcut-text'; 383 shortcutText.style.visilibity = 'hidden'; 384 key.appendChild(shortcutText); 385 keyboard.appendChild(key); 386 387 minX = Math.min(minX, x); 388 maxX = Math.max(maxX, x + w); 389 minY = Math.min(minY, y); 390 maxY = Math.max(maxY, y + h); 391 } 392 393 var width = maxX - minX + 1; 394 var height = maxY - minY + 1; 395 keyboard.style.width = (width + 2 * (minX + 1)) + 'px'; 396 keyboard.style.height = (height + 2 * (minY + 1)) + 'px'; 397 398 var instructions = document.createElement('div'); 399 instructions.id = 'instructions'; 400 instructions.className = 'keyboard-overlay-instructions'; 401 instructions.style.left = ((BASE_INSTRUCTIONS.left - BASE_KEYBOARD.left) * 402 width / BASE_KEYBOARD.width + minX) + 'px'; 403 instructions.style.top = ((BASE_INSTRUCTIONS.top - BASE_KEYBOARD.top) * 404 height / BASE_KEYBOARD.height + minY) + 'px'; 405 instructions.style.width = (width * BASE_INSTRUCTIONS.width / 406 BASE_KEYBOARD.width) + 'px'; 407 instructions.style.height = (height * BASE_INSTRUCTIONS.height / 408 BASE_KEYBOARD.height) + 'px'; 409 410 var instructionsText = document.createElement('div'); 411 instructionsText.id = 'instructions-text'; 412 instructionsText.className = 'keyboard-overlay-instructions-text'; 413 instructionsText.innerHTML = templateData.keyboardOverlayInstructions; 414 instructions.appendChild(instructionsText); 415 var instructionsHideText = document.createElement('div'); 416 instructionsHideText.id = 'instructions-hide-text'; 417 instructionsHideText.className = 'keyboard-overlay-instructions-hide-text'; 418 instructionsHideText.innerHTML = templateData.keyboardOverlayInstructionsHide; 419 instructions.appendChild(instructionsHideText); 420 keyboard.appendChild(instructions); 421} 422 423/** 424 * A callback function for the onload event of the body element. 425 */ 426function init() { 427 document.addEventListener('keydown', handleKeyEvent); 428 document.addEventListener('keyup', handleKeyEvent); 429 chrome.send('getKeyboardOverlayId'); 430} 431 432/** 433 * Initializes the global keyboad overlay ID and the layout of keys. 434 * Called after sending the 'getKeyboardOverlayId' message. 435 */ 436function initKeyboardOverlayId(overlayId) { 437 // Libcros returns an empty string when it cannot find the keyboard overlay ID 438 // corresponding to the current input method. 439 // In such a case, fallback to the default ID (en_US). 440 if (overlayId) { 441 keyboardOverlayId = overlayId; 442 } 443 while(document.body.firstChild) { 444 document.body.removeChild(document.body.firstChild); 445 } 446 initLayout(); 447 update(); 448} 449 450document.addEventListener('DOMContentLoaded', init); 451