/** * @fileoverview Implements the PCjs Keyboard component. * @author Jeff Parsons * @version 1.0 * Created 2012-Jun-20 * * Copyright © 2012-2014 Jeff Parsons * * This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines) * at and . * * PCjs is free software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCjs. If not, * see . * * You are required to include the above copyright notice in every source code file of every * copy or modified version of this work, and to display that copyright notice on every screen * that loads or runs any version of this software (see Computer.sCopyright). * * Some PCjs files also attempt to load external resource files, such as character-image files, * ROM files, and disk image files. Those external resource files are not considered part of the * PCjs program for purposes of the GNU General Public License, and the author does not claim * any copyright as to their contents. */ "use strict"; if (typeof module !== 'undefined') { var str = require("../../shared/lib/strlib"); var web = require("../../shared/lib/weblib"); var Component = require("../../shared/lib/component"); var ChipSet = require("./chipset"); var State = require("./state"); var CPU = require("./cpu"); var Debugger = require("./debugger"); } /** * Keyboard(parmsKbd) * * The Keyboard component can be configured with the following (parmsKbd) properties: * * model: model string; should be one of: * * us83 (default) * us84 (TODO: awaiting implementation) * us101 (TODO: awaiting implementation) * * Its main purpose is to receive binding requests for various keyboard events, and to use those events * to simulate the PC's keyboard hardware. * * @constructor * @extends Component * @param {Object} parmsKbd */ function Keyboard(parmsKbd) { Component.call(this, "Keyboard", parmsKbd, Keyboard); this.nDefaultModel = parmsKbd['model']; /* * There are multiple ways that scan codes can be injected into the machine: button events, * soft-key events, keyDown/keyUp/keyPress events, and the injectKeys() interface. Currently, * there's no attempt to provide any coordination among those mechanisms, except at the lowest * level, where scan code generation takes place. addScanCode() insures that a key (scan code) * that's already in the "make" state will not trigger another "make" (unless a "repeat" has * been explicitly requested), and it insures that a key (scan code) already in the "break" * state will not trigger another "break". * * TODO: While it might seem sensible to save/restore this state data, I would argue that the * best thing to do on a save() is force a "break" of every key still active and NOT save this * data. This relieves restore() from doing any extra work; it can simply assume -- as it has * always assumed -- that the keyboard is free of any active "makes". That does mean saving * and restoring abScanBuffer, however, since the machine may not have been able to act upon * any of those forced "breaks" yet. * * TODO: addScanCode() should also provide a "fail-safe" mechanism that attempts to ensure that * a key cannot get stuck in the "make" state for more than a few seconds if the user did something * odd (eg, switched away from the current page or the entire browser mid-keystroke). For some browser * and key combinations (eg, Ctrl-Tab), this may be essential, to avoid stuck shift/modifier keys. */ this.aScanCodesActive = {}; /* * TODO: Make these delays configurable */ this.msReleaseDelay = 250; // number of milliseconds before a down key is "forced" up (unless we see it go up) this.msReleaseRepeat = 100; // number of milliseconds before a held key is "forced" up (assuming auto-repeat) this.msInjectDelay = 300; // number of milliseconds between injected keystrokes this.setReady(); } Component.subclass(Component, Keyboard); /** * Alphanumeric and other common (printable) ASCII codes. * * TODO: Determine what we can do to get ALL constants like these inlined (enum doesn't seem to * get the job done); the problem seems to be limited to property references that use quotes, which * is why I've 'unquoted' as many of them as possible. * * @enum {number} */ Keyboard.ASCII = { CTRLA: 1, CTRLZ: 26, ' ': 32, '!': 33, '"': 34, '#': 35, '$': 36, '%': 37, '&': 38, "'": 39, '(': 40, ')': 41, '*': 42, '+': 43, ',': 44, '-': 45, '.': 46, '/': 47, '0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55, '8': 56, '9': 57, ':': 58, ';': 59, '<': 60, '=': 61, '>': 62, '?': 63, '@': 64, A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90, '[': 91, '\\':92, ']': 93, '^': 94, '_': 95, '`': 96, a: 97, b: 98, c: 99, d: 100, e: 101, f: 102, g: 103, h: 104, i: 105, j: 106, k: 107, l: 108, m: 109, n: 110, o: 111, p: 112, q: 113, r: 114, s: 115, t: 116, u: 117, v: 118, w: 119, x: 120, y: 121, z: 122, '{':123, '|':124, '}':125, '~':126 }; /** * Browser keyCodes we must pay particular attention to. For the most part, these are * non-alphanumeric or function keys, some which may require special treatment (eg, * preventDefault() if returning false on the initial keyDown event is insufficient). * * keyCodes for most common ASCII keys can simply use the appropriate ASCII code above. * * Most of these represent non-ASCII keys (eg, the LEFT arrow key), yet for some reason, * browsers defined them using ASCII codes (eg, the LEFT arrow key uses the ASCII code * for '%' or 37). This conflict is discussed further in the definition of aButtonCodes below. * * @enum {number} */ Keyboard.KEYCODE = { /* 0x08 */ BS: 8, /* 0x09 */ TAB: 9, /* 0x0A */ LF: 10, /* 0x0D */ CR: 13, /* 0x10 */ SHIFT: 16, /* 0x11 */ CTRL: 17, /* 0x12 */ ALT: 18, /* 0x14 */ CAPS_LOCK: 20, /* 0x1B */ ESC: 27, /* 0x21 */ PGUP: 33, /* 0x22 */ PGDN: 34, /* 0x23 */ END: 35, /* 0x24 */ HOME: 36, /* 0x25 */ LEFT: 37, /* 0x26 */ UP: 38, /* 0x27 */ RIGHT: 39, /* 0x28 */ DOWN: 40, /* 0x2D */ INS: 45, /* 0x2E */ DEL: 46, /* 0x5B */ COMMAND: 91, // TODO: Treat this like the 'Windows' key /* 0x70 */ F1: 112, /* 0x71 */ F2: 113, /* 0x72 */ F3: 114, /* 0x73 */ F4: 115, /* 0x74 */ F5: 116, /* 0x75 */ F6: 117, /* 0x76 */ F7: 118, /* 0x77 */ F8: 119, /* 0x78 */ F9: 120, /* 0x79 */ F10: 121, /* 0x7A */ F11: 122, /* 0x7B */ F12: 123, // // ONDOWN is a bias we add to browser keyCodes that we want to handle on "down" rather than "press". // // Note that these biases use what I'll call "Decimal Coded Binary" or DCB (the reverse of BCD), // where decimal digits are used to represent binary bit values, which can be added together without // affecting neighboring digits as long as you stick to 1, 2 or 4 in any given column. // ONDOWN: 1000, // // ONRIGHT is a bias we add to browser keyCodes that need to check for a "right" location (default is "left") // ONRIGHT: 2000, // // FAKE is a bias we add to signal these are fake keyCodes corresponding to internal keystroke combinations. // The actual values are for internal use only and merely need to be unique and used consistently. // FAKE: 4000, FAKE_CTRLC: 4003, FAKE_CTRLBREAK: 4063, FAKE_CTRLALTDEL: 4081 }; /** * The set of values that a browser may store in the 'location' property of a keyboard event object * which we also support. * * @enum {number} */ Keyboard.LOCATION = { LEFT: 1, RIGHT: 2, NUMPAD: 3 }; /** * These internal "shift key" states are stored in bitsShift, as well as in aKeyCodes; note that the * right-hand versions of selected shift bits are shifted 1 bit right. * * @enum {number} */ Keyboard.STATE = { RSHIFT: 0x0001, SHIFT: 0x0002, RCTRL: 0x0004, // 101-key keyboard only CTRL: 0x0008, RALT: 0x0010, // 101-key keyboard only ALT: 0x0020, COMMAND: 0x0040, // 101-key keyboard only (TODO: Treat this like the 'Windows' key) INSERT: 0x0080, // TODO: Placeholder CAPS_LOCK: 0x0100, NUM_LOCK: 0x0200, // TODO: Placeholder SCROLL_LOCK: 0x0400, // TODO: Placeholder SIMULATE: 0x003f // STATE.RSHIFT | STATE.SHIFT | STATE.RCTRL | STATE.CTRL | STATE.RALT | STATE.ALT }; /** * Maps the KEYCODE of a "shift key" to its corresponding (default) STATE bit above * * @enum {number} */ Keyboard.STATEKEYS = { 16: Keyboard.STATE.SHIFT, 17: Keyboard.STATE.CTRL, 18: Keyboard.STATE.ALT, 20: Keyboard.STATE.CAPS_LOCK }; /** * In a perfect world, each one of our "button" codes would map to a unique browser keyCode. * * However, because most of these codes are for non-ASCII keys, which browsers brilliantly * map to ASCII keyCodes that conflict with *actual* ASCII keys, we must add an ONDOWN bias * to all these particular keyCodes, and make sure we store these keyCodes in our aKeyCodes * lookup table with the same bias. * * The good news is that ONDOWN also serves as a signal to our keyCode handlers that the key * in question should be handled during keyDown (not keyPress), since most if not all of these * non-alphanumeric keys don't generate a keyPress event anyway. * * @enum {number} */ Keyboard.aButtonCodes = { 'tab': Keyboard.KEYCODE.TAB + Keyboard.KEYCODE.ONDOWN, 'esc': Keyboard.KEYCODE.ESC + Keyboard.KEYCODE.ONDOWN, 'right-shift': Keyboard.KEYCODE.SHIFT + Keyboard.KEYCODE.ONDOWN + Keyboard.KEYCODE.ONRIGHT, 'shift': Keyboard.KEYCODE.SHIFT + Keyboard.KEYCODE.ONDOWN, 'ctrl': Keyboard.KEYCODE.CTRL + Keyboard.KEYCODE.ONDOWN, 'alt': Keyboard.KEYCODE.ALT + Keyboard.KEYCODE.ONDOWN, 'caps-lock': Keyboard.KEYCODE.CAPS_LOCK + Keyboard.KEYCODE.ONDOWN, 'f1': Keyboard.KEYCODE.F1 + Keyboard.KEYCODE.ONDOWN, 'f2': Keyboard.KEYCODE.F2 + Keyboard.KEYCODE.ONDOWN, 'f3': Keyboard.KEYCODE.F3 + Keyboard.KEYCODE.ONDOWN, 'f4': Keyboard.KEYCODE.F4 + Keyboard.KEYCODE.ONDOWN, 'f5': Keyboard.KEYCODE.F5 + Keyboard.KEYCODE.ONDOWN, 'f6': Keyboard.KEYCODE.F6 + Keyboard.KEYCODE.ONDOWN, 'f7': Keyboard.KEYCODE.F7 + Keyboard.KEYCODE.ONDOWN, 'f8': Keyboard.KEYCODE.F8 + Keyboard.KEYCODE.ONDOWN, 'f9': Keyboard.KEYCODE.F9 + Keyboard.KEYCODE.ONDOWN, 'f10': Keyboard.KEYCODE.F10 + Keyboard.KEYCODE.ONDOWN, 'left': Keyboard.KEYCODE.LEFT + Keyboard.KEYCODE.ONDOWN, // formerly "left-arrow" 'up': Keyboard.KEYCODE.UP + Keyboard.KEYCODE.ONDOWN, // formerly "up-arrow" 'right': Keyboard.KEYCODE.RIGHT + Keyboard.KEYCODE.ONDOWN, // formerly "right-arrow" 'down': Keyboard.KEYCODE.DOWN + Keyboard.KEYCODE.ONDOWN, // formerly "down-arrow" /* * These bindings are for convenience (common key combinations that can be bound to a single control) */ 'ctrl-c': Keyboard.KEYCODE.FAKE_CTRLC, 'ctrl-break': Keyboard.KEYCODE.FAKE_CTRLBREAK, 'ctrl-alt-del': Keyboard.KEYCODE.FAKE_CTRLALTDEL }; /* * Scan code constants */ Keyboard.SCANCODE = { ESC: 0x01, ONE: 0x02, TWO: 0x03, THREE: 0x04, FOUR: 0x05, FIVE: 0x06, SIX: 0x07, SEVEN: 0x08, EIGHT: 0x09, NINE: 0x0a, ZERO: 0x0b, DASH: 0x0c, EQUALS: 0x0d, BS: 0x0e, TAB: 0x0f, Q: 0x10, W: 0x11, E: 0x12, R: 0x13, T: 0x14, Y: 0x15, U: 0x16, I: 0x17, O: 0x18, P: 0x19, LBRACK: 0x1a, RBRACK: 0x1b, ENTER: 0x1c, CTRL: 0x1d, A: 0x1e, S: 0x1f, D: 0x20, F: 0x21, G: 0x22, H: 0x23, J: 0x24, K: 0x25, L: 0x26, SEMI: 0x27, QUOTE: 0x28, BQUOTE: 0x29, SHIFT: 0x2a, BSLASH: 0x2b, Z: 0x2c, X: 0x2d, C: 0x2e, V: 0x2f, B: 0x30, N: 0x31, M: 0x32, COMMA: 0x33, PERIOD: 0x34, SLASH: 0x35, RSHIFT: 0x36, PRTSC: 0x37, // unshifted '*'; becomes dedicated 'Print Screen' key on 101-key keyboards ALT: 0x38, SPACE: 0x39, CAPS_LOCK: 0x3a, F1: 0x3b, F2: 0x3c, F3: 0x3d, F4: 0x3e, F5: 0x3f, F6: 0x40, F7: 0x41, F8: 0x42, F9: 0x43, F10: 0x44, NUM_LOCK: 0x45, SCROLL_LOCK: 0x46, NUM_HOME: 0x47, NUM_UP: 0x48, NUM_PGUP: 0x49, NUM_SUB: 0x4a, NUM_LEFT: 0x4b, NUM_CENTER: 0x4c, NUM_RIGHT: 0x4d, NUM_ADD: 0x4e, NUM_END: 0x4f, NUM_DOWN: 0x50, NUM_PGDN: 0x51, NUM_INS: 0x52, NUM_DEL: 0x53, SYSREQ: 0x54, // 84-key keyboard only (simulated with 'alt'+'prtsc' on 101-key keyboards) PAUSE: 0x54, // 101-key keyboard only F11: 0x57, F12: 0x58, WIN: 0x5b, RWIN: 0x5c, MENU: 0x5d, MAKE: 0x7f, BREAK: 0x80, EXTEND1: 0xe0, EXTEND2: 0xe1 }; /** * Define identifiers for all possible keys, based on their primary (unshifted) character or function. * This also serves as a definition of all supported scan codes, making it possible to create full-featured * "soft keyboards". * * One exception to the (unshifted) rule above is 'prtsc': on the original IBM 83-key and 84-key keyboards, * its primary (unshifted) character was '*', but on 101-key keyboards, it became a separate key ('prtsc', * now labeled "Print Screen"), as did the num-pad '*' ('num-mul'), so 'prtsc' seems worthy of an exception * to the rule. * * On 83-key and 84-key keyboards, 'ctrl'+'num-lock' triggered a "pause" operation and 'ctrl'+'scroll-lock' * triggered a "break" operation. * * On 101-key keyboards, IBM decided to move both those special operations to a new 'pause' ("Pause/Break") * key, near the new dedicated 'prtsc' ("Print Screen/SysRq") key -- and to drop the "e" from "SysReq". * Those keys behave as follows: * * When 'pause' is pressed alone, it generates 0xe1 0x1d 0x45 0xe1 0x9d 0xc5 on make (nothing on break), * which essentially simulates the make-and-break of the 'ctrl' and 'num-lock' keys (ignoring the 0xe1), * triggering a "pause" operation. * * When 'pause' is pressed with 'ctrl', it generates 0xe0 0x46 0xe0 0xc6 on make (nothing on break) and * does not repeat, which essentially simulates the make-and-break of 'scroll-lock', which, in conjunction * with the separate make-and-break of 'ctrl', triggers a "break" operation. * * When 'prtsc' is pressed alone, it generates 0xe0 0x2a 0xe0 0x37, simulating the make of both 'shift' * and 'prtsc'; when pressed with 'shift' or 'ctrl', it generates only 0xe0 0x37; and when pressed with * 'alt', it generates only 0x54 (to simulate 'sysreq'). * * TODO: Implement the above behaviors. * * All key identifiers must be quotable using single-quotes, because that's how components.xsl will encode them * in the "data-value" attribute of the corresponding HTML control. Which, in turn, is why the single-quote * key is defined as 'quote' rather than "'". Similarly, if there was unshifted "double-quote" key, it could * not be called '"', because XML files must quote all their bindings using double-quotes. * * In the (informal) numbering of keys below, two keys are deliberately numbered 84, reflecting the fact that * the 'sysreq' key was added to the 84-key keyboard but then dropped from the 101-key keyboard. * * @enum {number} */ Keyboard.aSoftCodes = { /* 1 */ 'esc': Keyboard.SCANCODE.ESC, /* 2 */ '1': Keyboard.SCANCODE.ONE, /* 3 */ '2': Keyboard.SCANCODE.TWO, /* 4 */ '3': Keyboard.SCANCODE.THREE, /* 5 */ '4': Keyboard.SCANCODE.FOUR, /* 6 */ '5': Keyboard.SCANCODE.FIVE, /* 7 */ '6': Keyboard.SCANCODE.SIX, /* 8 */ '7': Keyboard.SCANCODE.SEVEN, /* 9 */ '8': Keyboard.SCANCODE.EIGHT, /* 10 */ '9': Keyboard.SCANCODE.NINE, /* 11 */ '0': Keyboard.SCANCODE.ZERO, /* 12 */ '-': Keyboard.SCANCODE.DASH, /* 13 */ '=': Keyboard.SCANCODE.EQUALS, /* 14 */ 'bs': Keyboard.SCANCODE.BS, /* 15 */ 'tab': Keyboard.SCANCODE.TAB, /* 16 */ 'q': Keyboard.SCANCODE.Q, /* 17 */ 'w': Keyboard.SCANCODE.W, /* 18 */ 'e': Keyboard.SCANCODE.E, /* 19 */ 'r': Keyboard.SCANCODE.R, /* 20 */ 't': Keyboard.SCANCODE.T, /* 21 */ 'y': Keyboard.SCANCODE.Y, /* 22 */ 'u': Keyboard.SCANCODE.U, /* 23 */ 'i': Keyboard.SCANCODE.I, /* 24 */ 'o': Keyboard.SCANCODE.O, /* 25 */ 'p': Keyboard.SCANCODE.P, /* 26 */ '[': Keyboard.SCANCODE.LBRACK, /* 27 */ ']': Keyboard.SCANCODE.RBRACK, /* 28 */ 'enter': Keyboard.SCANCODE.ENTER, /* 29 */ 'ctrl': Keyboard.SCANCODE.CTRL, /* 30 */ 'a': Keyboard.SCANCODE.A, /* 31 */ 's': Keyboard.SCANCODE.S, /* 32 */ 'd': Keyboard.SCANCODE.D, /* 33 */ 'f': Keyboard.SCANCODE.F, /* 34 */ 'g': Keyboard.SCANCODE.G, /* 35 */ 'h': Keyboard.SCANCODE.H, /* 36 */ 'j': Keyboard.SCANCODE.J, /* 37 */ 'k': Keyboard.SCANCODE.K, /* 38 */ 'l': Keyboard.SCANCODE.L, /* 39 */ ';': Keyboard.SCANCODE.SEMI, /* 40 */ 'quote': Keyboard.SCANCODE.QUOTE, // formerly "squote" /* 41 */ '`': Keyboard.SCANCODE.BQUOTE, // formerly "bquote" /* 42 */ 'shift': Keyboard.SCANCODE.SHIFT, // formerly "lshift" /* 43 */ '\\': Keyboard.SCANCODE.BSLASH, // formerly "bslash" /* 44 */ 'z': Keyboard.SCANCODE.Z, /* 45 */ 'x': Keyboard.SCANCODE.X, /* 46 */ 'c': Keyboard.SCANCODE.C, /* 47 */ 'v': Keyboard.SCANCODE.V, /* 48 */ 'b': Keyboard.SCANCODE.B, /* 49 */ 'n': Keyboard.SCANCODE.N, /* 50 */ 'm': Keyboard.SCANCODE.M, /* 51 */ ',': Keyboard.SCANCODE.COMMA, /* 52 */ '.': Keyboard.SCANCODE.PERIOD, /* 53 */ '/': Keyboard.SCANCODE.SLASH, /* 54 */ 'right-shift': Keyboard.SCANCODE.RSHIFT, // formerly "rshift" /* 55 */ 'prtsc': Keyboard.SCANCODE.PRTSC, // unshifted '*'; becomes dedicated 'Print Screen' key on 101-key keyboards /* 56 */ 'alt': Keyboard.SCANCODE.ALT, /* 57 */ 'space': Keyboard.SCANCODE.SPACE, /* 58 */ 'caps-lock': Keyboard.SCANCODE.CAPS_LOCK, /* 59 */ 'f1': Keyboard.SCANCODE.F1, /* 60 */ 'f2': Keyboard.SCANCODE.F2, /* 61 */ 'f3': Keyboard.SCANCODE.F3, /* 62 */ 'f4': Keyboard.SCANCODE.F4, /* 63 */ 'f5': Keyboard.SCANCODE.F5, /* 64 */ 'f6': Keyboard.SCANCODE.F6, /* 65 */ 'f7': Keyboard.SCANCODE.F7, /* 66 */ 'f8': Keyboard.SCANCODE.F8, /* 67 */ 'f9': Keyboard.SCANCODE.F9, /* 68 */ 'f10': Keyboard.SCANCODE.F10, /* 69 */ 'num-lock': Keyboard.SCANCODE.NUM_LOCK, /* 70 */ 'scroll-lock': Keyboard.SCANCODE.SCROLL_LOCK, // TODO: 0xe046 on 101-key keyboards? /* 71 */ 'num-home': Keyboard.SCANCODE.NUM_HOME, // formerly "home" /* 72 */ 'num-up': Keyboard.SCANCODE.NUM_UP, // formerly "up-arrow" /* 73 */ 'num-pgup': Keyboard.SCANCODE.NUM_PGUP, // formerly "page-up" /* 74 */ 'num-sub': Keyboard.SCANCODE.NUM_SUB, // formerly "num-minus" /* 75 */ 'num-left': Keyboard.SCANCODE.NUM_LEFT, // formerly "left-arrow" /* 76 */ 'num-center': Keyboard.SCANCODE.NUM_CENTER, // formerly "center" /* 77 */ 'num-right': Keyboard.SCANCODE.NUM_RIGHT, // formerly "right-arrow" /* 78 */ 'num-add': Keyboard.SCANCODE.NUM_ADD, // formerly "num-plus" /* 79 */ 'num-end': Keyboard.SCANCODE.NUM_END, // formerly "end" /* 80 */ 'num-down': Keyboard.SCANCODE.NUM_DOWN, // formerly "down-arrow" /* 81 */ 'num-pgdn': Keyboard.SCANCODE.NUM_PGDN, // formerly "page-down" /* 82 */ 'num-ins': Keyboard.SCANCODE.NUM_INS, // formerly "ins" /* 83 */ 'num-del': Keyboard.SCANCODE.NUM_DEL, // formerly "del" /* 84 */ 'sysreq': Keyboard.SCANCODE.SYSREQ, // 84-key keyboard only (simulated with 'alt'+'prtsc' on 101-key keyboards) /* 84 */ 'pause': Keyboard.SCANCODE.PAUSE, // 101-key keyboard only /* 85 */ 'f11': Keyboard.SCANCODE.F11, /* 86 */ 'f12': Keyboard.SCANCODE.F12, /* 87 */ 'num-enter': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.ENTER << 8), /* 88 */ 'right-ctrl': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.CTRL << 8), /* 89 */ 'num-div': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.SLASH << 8), /* 90 */ 'num-mul': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.PRTSC << 8), /* 91 */ 'right-alt': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.ALT << 8), /* 92 */ 'home': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.NUM_HOME << 8), /* 93 */ 'up': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.NUM_UP << 8), /* 94 */ 'pgup': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.NUM_PGUP << 8), /* 95 */ 'left': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.NUM_LEFT << 8), /* 96 */ 'right': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.NUM_RIGHT << 8), /* 97 */ 'end': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.NUM_END << 8), /* 98 */ 'down': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.NUM_DOWN << 8), /* 99 */ 'pgdn': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.NUM_PGDN << 8), /* 100 */ 'ins': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.NUM_INS << 8), /* 101 */ 'del': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.NUM_DEL << 8), 'win': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.WIN << 8), 'right-win': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.RWIN << 8), 'menu': Keyboard.SCANCODE.EXTEND1 | (Keyboard.SCANCODE.MENU << 8) }; /** * This array is used by keySimulateUpDown() to lookup a given keyCode and convert it to a scan code * (lower byte) plus any required shift key states (upper bytes). * * Using keyCodes from keyPress events proved to be more robust than using keyCodes from keyDown and * keyUp events, in part because of differences in the way browsers generate the keyDown and keyUp events. * For example, Safari on iOS devices will not generate up/down events for shift keys, and for other keys, * the up/down events are usually generated after the actual press is complete, and in rapid succession, * which doesn't always give the simulation enough time to detect the key. * * The other problem (which is more of a problem with keyboards like the C1P than any IBM keyboards) is * that the shift/modifier state for a character on the "source" keyboard may not match the shift/modifier * state for the same character on the "target" keyboard. And since this code is inherited from C1Pjs, * we've inherited the same solution: keySimulateUpDown() has the ability to "undo" any states in bitsShift * that conflict with the state(s) required for the character in question. * * @enum {number} */ Keyboard.aKeyCodes = {}; Keyboard.aKeyCodes[Keyboard.KEYCODE.ESC + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.ONE; Keyboard.aKeyCodes[Keyboard.ASCII['1']] = Keyboard.SCANCODE.ONE; Keyboard.aKeyCodes[Keyboard.ASCII['!']] = Keyboard.SCANCODE.ONE | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['2']] = Keyboard.SCANCODE.TWO; Keyboard.aKeyCodes[Keyboard.ASCII['@']] = Keyboard.SCANCODE.TWO | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['3']] = Keyboard.SCANCODE.THREE; Keyboard.aKeyCodes[Keyboard.ASCII['#']] = Keyboard.SCANCODE.THREE | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['4']] = Keyboard.SCANCODE.FOUR; Keyboard.aKeyCodes[Keyboard.ASCII['$']] = Keyboard.SCANCODE.FOUR | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['5']] = Keyboard.SCANCODE.FIVE; Keyboard.aKeyCodes[Keyboard.ASCII['%']] = Keyboard.SCANCODE.FIVE | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['6']] = Keyboard.SCANCODE.SIX; Keyboard.aKeyCodes[Keyboard.ASCII['^']] = Keyboard.SCANCODE.SIX | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['7']] = Keyboard.SCANCODE.SEVEN; Keyboard.aKeyCodes[Keyboard.ASCII['&']] = Keyboard.SCANCODE.SEVEN | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['8']] = Keyboard.SCANCODE.EIGHT; Keyboard.aKeyCodes[Keyboard.ASCII['*']] = Keyboard.SCANCODE.EIGHT | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['9']] = Keyboard.SCANCODE.NINE; Keyboard.aKeyCodes[Keyboard.ASCII['(']] = Keyboard.SCANCODE.NINE | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['0']] = Keyboard.SCANCODE.ZERO; Keyboard.aKeyCodes[Keyboard.ASCII[')']] = Keyboard.SCANCODE.ZERO | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['-']] = Keyboard.SCANCODE.DASH; Keyboard.aKeyCodes[Keyboard.ASCII['_']] = Keyboard.SCANCODE.DASH | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['=']] = Keyboard.SCANCODE.EQUALS; Keyboard.aKeyCodes[Keyboard.ASCII['+']] = Keyboard.SCANCODE.EQUALS | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.KEYCODE.BS + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.BS; Keyboard.aKeyCodes[Keyboard.KEYCODE.TAB + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.TAB; Keyboard.aKeyCodes[Keyboard.ASCII.q] = Keyboard.SCANCODE.Q; Keyboard.aKeyCodes[Keyboard.ASCII.Q] = Keyboard.SCANCODE.Q | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.w] = Keyboard.SCANCODE.W; Keyboard.aKeyCodes[Keyboard.ASCII.W] = Keyboard.SCANCODE.W | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.e] = Keyboard.SCANCODE.E; Keyboard.aKeyCodes[Keyboard.ASCII.E] = Keyboard.SCANCODE.E | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.r] = Keyboard.SCANCODE.R; Keyboard.aKeyCodes[Keyboard.ASCII.R] = Keyboard.SCANCODE.R | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.t] = Keyboard.SCANCODE.T; Keyboard.aKeyCodes[Keyboard.ASCII.T] = Keyboard.SCANCODE.T | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.y] = Keyboard.SCANCODE.Y; Keyboard.aKeyCodes[Keyboard.ASCII.Y] = Keyboard.SCANCODE.Y | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.u] = Keyboard.SCANCODE.U; Keyboard.aKeyCodes[Keyboard.ASCII.U] = Keyboard.SCANCODE.U | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.i] = Keyboard.SCANCODE.I; Keyboard.aKeyCodes[Keyboard.ASCII.I] = Keyboard.SCANCODE.I | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.o] = Keyboard.SCANCODE.O; Keyboard.aKeyCodes[Keyboard.ASCII.O] = Keyboard.SCANCODE.O | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.p] = Keyboard.SCANCODE.P; Keyboard.aKeyCodes[Keyboard.ASCII.P] = Keyboard.SCANCODE.P | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['[']] = Keyboard.SCANCODE.LBRACK; Keyboard.aKeyCodes[Keyboard.ASCII['{']] = Keyboard.SCANCODE.LBRACK | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII[']']] = Keyboard.SCANCODE.RBRACK; Keyboard.aKeyCodes[Keyboard.ASCII['}']] = Keyboard.SCANCODE.RBRACK | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.KEYCODE.CR] = Keyboard.SCANCODE.ENTER; Keyboard.aKeyCodes[Keyboard.KEYCODE.CTRL + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.CTRL; Keyboard.aKeyCodes[Keyboard.ASCII.a] = Keyboard.SCANCODE.A; Keyboard.aKeyCodes[Keyboard.ASCII.A] = Keyboard.SCANCODE.A | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.s] = Keyboard.SCANCODE.S; Keyboard.aKeyCodes[Keyboard.ASCII.S] = Keyboard.SCANCODE.S | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.d] = Keyboard.SCANCODE.D; Keyboard.aKeyCodes[Keyboard.ASCII.D] = Keyboard.SCANCODE.D | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.f] = Keyboard.SCANCODE.F; Keyboard.aKeyCodes[Keyboard.ASCII.F] = Keyboard.SCANCODE.F | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.g] = Keyboard.SCANCODE.G; Keyboard.aKeyCodes[Keyboard.ASCII.G] = Keyboard.SCANCODE.G | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.h] = Keyboard.SCANCODE.H; Keyboard.aKeyCodes[Keyboard.ASCII.H] = Keyboard.SCANCODE.H | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.j] = Keyboard.SCANCODE.J; Keyboard.aKeyCodes[Keyboard.ASCII.J] = Keyboard.SCANCODE.J | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.k] = Keyboard.SCANCODE.K; Keyboard.aKeyCodes[Keyboard.ASCII.K] = Keyboard.SCANCODE.K | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.l] = Keyboard.SCANCODE.L; Keyboard.aKeyCodes[Keyboard.ASCII.L] = Keyboard.SCANCODE.L | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII[';']] = Keyboard.SCANCODE.SEMI; Keyboard.aKeyCodes[Keyboard.ASCII[':']] = Keyboard.SCANCODE.SEMI | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII["'"]] = Keyboard.SCANCODE.QUOTE; Keyboard.aKeyCodes[Keyboard.ASCII['"']] = Keyboard.SCANCODE.QUOTE | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['`']] = Keyboard.SCANCODE.BQUOTE; Keyboard.aKeyCodes[Keyboard.ASCII['~']] = Keyboard.SCANCODE.BQUOTE | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.KEYCODE.SHIFT + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.SHIFT; Keyboard.aKeyCodes[Keyboard.ASCII['\\']] = Keyboard.SCANCODE.BSLASH; Keyboard.aKeyCodes[Keyboard.ASCII['|']] = Keyboard.SCANCODE.BSLASH | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.z] = Keyboard.SCANCODE.Z; Keyboard.aKeyCodes[Keyboard.ASCII.Z] = Keyboard.SCANCODE.Z | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.x] = Keyboard.SCANCODE.X; Keyboard.aKeyCodes[Keyboard.ASCII.X] = Keyboard.SCANCODE.X | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.c] = Keyboard.SCANCODE.C; Keyboard.aKeyCodes[Keyboard.ASCII.C] = Keyboard.SCANCODE.C | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.v] = Keyboard.SCANCODE.V; Keyboard.aKeyCodes[Keyboard.ASCII.V] = Keyboard.SCANCODE.V | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.b] = Keyboard.SCANCODE.B; Keyboard.aKeyCodes[Keyboard.ASCII.B] = Keyboard.SCANCODE.B | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.n] = Keyboard.SCANCODE.N; Keyboard.aKeyCodes[Keyboard.ASCII.N] = Keyboard.SCANCODE.N | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII.m] = Keyboard.SCANCODE.M; Keyboard.aKeyCodes[Keyboard.ASCII.M] = Keyboard.SCANCODE.M | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII[',']] = Keyboard.SCANCODE.COMMA; Keyboard.aKeyCodes[Keyboard.ASCII['<']] = Keyboard.SCANCODE.COMMA | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['.']] = Keyboard.SCANCODE.PERIOD; Keyboard.aKeyCodes[Keyboard.ASCII['>']] = Keyboard.SCANCODE.PERIOD | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.ASCII['/']] = Keyboard.SCANCODE.SLASH; Keyboard.aKeyCodes[Keyboard.ASCII['?']] = Keyboard.SCANCODE.SLASH | (Keyboard.SCANCODE.SHIFT << 8); Keyboard.aKeyCodes[Keyboard.KEYCODE.SHIFT + Keyboard.KEYCODE.ONDOWN + Keyboard.KEYCODE.ONRIGHT] = Keyboard.SCANCODE.RSHIFT; // TODO: 0x37 ('prtsc') Keyboard.aKeyCodes[Keyboard.KEYCODE.ALT + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.ALT; Keyboard.aKeyCodes[Keyboard.ASCII[' ']] = Keyboard.SCANCODE.SPACE; Keyboard.aKeyCodes[Keyboard.KEYCODE.CAPS_LOCK + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.CAPS_LOCK; Keyboard.aKeyCodes[Keyboard.KEYCODE.F1 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F1; Keyboard.aKeyCodes[Keyboard.KEYCODE.F2 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F2; Keyboard.aKeyCodes[Keyboard.KEYCODE.F3 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F3; Keyboard.aKeyCodes[Keyboard.KEYCODE.F4 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F4; Keyboard.aKeyCodes[Keyboard.KEYCODE.F5 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F5; Keyboard.aKeyCodes[Keyboard.KEYCODE.F6 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F6; Keyboard.aKeyCodes[Keyboard.KEYCODE.F7 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F7; Keyboard.aKeyCodes[Keyboard.KEYCODE.F8 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F8; Keyboard.aKeyCodes[Keyboard.KEYCODE.F9 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F9; Keyboard.aKeyCodes[Keyboard.KEYCODE.F10 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F10; // TODO: 0x45 ('num-lock'), 0x46 ('scroll-lock'), 0x4a ('num-sub') and 0x4e ('num-add') Keyboard.aKeyCodes[Keyboard.KEYCODE.HOME + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_HOME; Keyboard.aKeyCodes[Keyboard.KEYCODE.UP + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_UP; Keyboard.aKeyCodes[Keyboard.KEYCODE.PGUP + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_PGUP; Keyboard.aKeyCodes[Keyboard.KEYCODE.LEFT + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_LEFT; Keyboard.aKeyCodes[Keyboard.KEYCODE.RIGHT + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_RIGHT; Keyboard.aKeyCodes[Keyboard.KEYCODE.END + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_END; Keyboard.aKeyCodes[Keyboard.KEYCODE.DOWN + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_DOWN; Keyboard.aKeyCodes[Keyboard.KEYCODE.PGDN + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_PGDN; Keyboard.aKeyCodes[Keyboard.KEYCODE.INS + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_INS; Keyboard.aKeyCodes[Keyboard.KEYCODE.DEL + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_DEL; /* * Entries beyond this point are for keys that existed only on 101-key keyboards (well, except for 'sysreq', * which also existed on the 84-key keyboard), which ALSO means that these keys essentially did not exist * for a MODEL_5150 or MODEL_5160 machine, because those machines could use only 83-key keyboards. Remember * that IBM machines and IBM keyboards are our reference point here, so while there were undoubtedly 5150/5160 * clones that could use newer keyboards, as well as 3rd-party keyboards that could work with older machines, * support for non-IBM configurations is left for another day. * * TODO: The only relevance of newer keyboards to older machines is the fact that you're likely using a newer * keyboard with your browser, which raises the question of what to do with newer keys that older machines * wouldn't understand. I don't attempt to filter out any of the entries below based on machine model, but * it's likely that I should. * * TODO: Add entries for 'num-mul', 'num-div', 'num-enter', the stand-alone arrow keys, etc, AND at * the same time, add support for multi-byte codes (eg, 0xe01c). */ Keyboard.aKeyCodes[Keyboard.KEYCODE.F11 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F11; Keyboard.aKeyCodes[Keyboard.KEYCODE.F12 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F12; Keyboard.aKeyCodes[Keyboard.KEYCODE.FAKE_CTRLC] = Keyboard.SCANCODE.C + (Keyboard.SCANCODE.CTRL << 8); Keyboard.aKeyCodes[Keyboard.KEYCODE.FAKE_CTRLBREAK] = Keyboard.SCANCODE.SCROLL_LOCK | (Keyboard.SCANCODE.CTRL << 8); Keyboard.aKeyCodes[Keyboard.KEYCODE.FAKE_CTRLALTDEL] = Keyboard.SCANCODE.NUM_DEL + (Keyboard.SCANCODE.CTRL << 8) + (Keyboard.SCANCODE.ALT << 16); /** * keySimulateUpDown() origin codes (useful only for debugging) * * @enum {number} */ Keyboard.SIMCODE = { KEYPRESS: 0, KEYRELEASE: 1, KEYUPDOWN: 2, KEYTIMEOUT: 3, AUTOCLEAR: 4 }; if (DEBUGGER) { Keyboard.aSimCodeDescs = ["keyPress", "keyRelease", "keyUpDown", "keyTimeout", "autoClear"]; } /** * Commands that can be sent to the Keyboard via the 8042; see sendCmd() * * @enum {number} */ Keyboard.CMD = { RESET: 0xFF, RESEND: 0xFE, DEF_ON: 0xF6, DEF_OFF: 0xF5, ENABLE: 0xF4, SET_RATE: 0xF3, ECHO: 0xEE, SET_LEDS: 0xED }; /** * Command responses returned to the Keyboard via the 8042; see sendCmd() * * @enum {number} */ Keyboard.CMDRES = { OVERRUN: 0x00, LOAD_TEST: 0x65, // undocumented "LOAD MANUFACTURING TEST REQUEST" response code BAT_SUCC: 0xAA, // Basic Assurance Test (BAT) completed successfully ECHO: 0xEE, BREAK_PREF: 0xF0, // break prefix ACK: 0xFA, BAT_FAIL: 0xFC, // Basic Assurance Test (BAT) failed DIAG_FAIL: 0xFD, RESEND: 0xFE, BUFF_FULL: 0xFF // TODO: Verify this response code (is it just for older 83-key keyboards?) }; Keyboard.LIMIT = { MAX_SCANCODES: 20 // TODO: Verify this limit for newer keyboards (84-key and up) }; /** * setBinding(sHTMLClass, sHTMLType, sBinding, control) * * @this {Keyboard} * @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output") * @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas") * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "esc") * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement) * @return {boolean} true if binding was successful, false if unrecognized binding request */ Keyboard.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control) { /* * There's a special binding that the Video component uses ("kbd") to effectively bind its * canvas to the entire keyboard, in Video.powerUp(); ie: * * video.kbd.setBinding("input", "canvas", "kbd", video.canvasScreen); * or: * video.kbd.setBinding("input", "textarea", "kbd", video.textareaScreen); * * However, it's also possible for the keyboard XML definition to define a control that serves * a similar purpose; eg: * * Kbd * * The latter is purely experimental, while we work on finding ways to trigger the soft keyboard on * certain pesky devices (like the Kindle Fire). Note that even if you use the latter, the former will * still be enabled (there's currently no way to configure the Video component to not bind its canvas, * but we could certainly add one if the need ever arose). */ var kbd = this; var id = sHTMLType + '-' + sBinding; if (this.bindings[id] === undefined) { switch (sBinding) { case "kbd": this.bindings[id] = control; control.onkeydown = function onKeyDownKeyboard(event) { return kbd.keyUpDown(event, true); }; control.onkeypress = function onKeyPressKeyboard(event) { return kbd.keyPress(event); }; control.onkeyup = function onKeyUpKeyboard(event) { return kbd.keyUpDown(event, false); }; return true; default: if (Keyboard.aButtonCodes[sBinding] !== undefined && sHTMLType == "button") { this.bindings[id] = control; control.onclick = function(kbd, sKey, keyCode) { return function onClickKeyboard(event) { if (DEBUG) kbd.println(sKey + " clicked"); if (kbd.cpu) kbd.cpu.setFocus(); return !kbd.keySimulatePress(keyCode); }; }(this, sBinding, Keyboard.aButtonCodes[sBinding]); return true; } else if (Keyboard.aSoftCodes[sBinding] !== undefined) { this.bindings[id] = control; var fnDown = function(kbd, sKey, bScan) { return function onMouseOrTouchDownKeyboard(event) { kbd.addScanCode(bScan); }; }(this, sBinding, Keyboard.aSoftCodes[sBinding]); var fnUp = function (kbd, sKey, bScan) { return function onMouseOrTouchUpKeyboard(event) { kbd.addScanCode(bScan); }; }(this, sBinding, Keyboard.aSoftCodes[sBinding] | Keyboard.SCANCODE.BREAK); if ('ontouchstart' in window) { control.ontouchstart = fnDown; control.ontouchend = fnUp; } else { control.onmousedown = fnDown; control.onmouseup = control.onmouseout = fnUp; } return true; } break; } } return false; }; /** * findBinding(bKey, t, fDown) * * @this {Keyboard} * @param {number} bKey * @param {string} t is the type of control (eg, "button" or "key") * @param {boolean} [fDown] is true if the key is going down, false if up, or undefined if unchanged * @return {Object} is the HTML control DOM object (eg, HTMLButtonElement), or undefined if no such control exists */ Keyboard.prototype.findBinding = function(bKey, t, fDown) { var e; for (var s in Keyboard.aSoftCodes) { if (Keyboard.aSoftCodes[s] == bKey) { var id = t + '-' + s; e = this.bindings[id]; if (e && fDown !== undefined) { this.setSoftKeyState(e, fDown); } break; } } return e; }; /** * initBus(cmp, bus, cpu, dbg) * * @this {Keyboard} * @param {Computer} cmp * @param {Bus} bus * @param {X86CPU} cpu * @param {Debugger} dbg */ Keyboard.prototype.initBus = function(cmp, bus, cpu, dbg) { this.bus = bus; this.cpu = cpu; this.dbg = dbg; this.chipset = cmp.getComponentByType("ChipSet"); }; /** * setModel(nModel) * * @this {Keyboard} * @param {number} nModel */ Keyboard.prototype.setModel = function(nModel) { }; /** * setReady() * * @this {Keyboard} */ Keyboard.prototype.setReady = function() { this.iOS = web.isUserAgent("iOS"); this.fMobile = (this.iOS || web.isUserAgent("Android")); this.messageDebugger("mobile keyboard support: " + (this.fMobile? "true" : "false")); /* * TODO: Determine how to declare this superclass method in order to avoid a type warning */ return Component.prototype.setReady.call(this); }; /** * resetDevice() * * @this {Keyboard} */ Keyboard.prototype.resetDevice = function() { /* * TODO: There's more to reset, like LED indicators, default type rate, and emptying the scan code buffer. */ this.messageDebugger("keyboard reset", Debugger.MESSAGE.PORT); this.abScanBuffer = [Keyboard.CMDRES.BAT_SUCC]; if (this.chipset) this.chipset.notifyKbdData(true); }; /** * setEnable(fData, fClock) * * This is the ChipSet's primary interface for toggling keyboard "data" and "clock" lines. * For MODEL_5150 and MODEL_5160 machines, this function is called from the ChipSet's PPI_B * output handler. For MODEL_5170 machines, this function is called when selected KBC.CMD * "data bytes" have been written. * * @this {Keyboard} * @param {boolean} fData is true if the keyboard simulated data line should be enabled * @param {boolean} fClock is true if the keyboard's simulated clock line should be enabled * @return {boolean} true if keyboard was re-enabled, false if not (or no change) */ Keyboard.prototype.setEnable = function(fData, fClock) { var fReset = false; if (this.fClock !== fClock) { if (DEBUG) this.messageDebugger("keyboard clock line changing to " + fClock, Debugger.MESSAGE.PORT); /* * Toggling the clock line low and then high signals a "reset", which we acknowledge once the * data line is high as well. */ this.fClock = this.fResetOnEnable = fClock; } if (this.fData !== fData) { if (DEBUG) this.messageDebugger("keyboard data line changing to " + fData, Debugger.MESSAGE.PORT); this.fData = fData; /* * TODO: Review this code; it was added during the early days of MODEL_5150 testing and may not be * *exactly* what's called for here. */ if (fData && !this.fResetOnEnable) { this.shiftScanCode(); } } if (this.fData && this.fResetOnEnable) { this.resetDevice(); this.fResetOnEnable = false; fReset = true; } return fReset; }; /** * sendCmd(bCmd) * * This is the ChipSet's primary interface for controlling "Model M" keyboards (ie, those used * with MODEL_5170 machines). Commands are delivered through the ChipSet's 8042 Keyboard Controller. * * @this {Keyboard} * @param {number} bCmd should be one of the Keyboard.CMD.* command codes (Model M keyboards only) * @return {number} response should be one of the Keyboard.CMDRES.* response codes, or -1 if unrecognized */ Keyboard.prototype.sendCmd = function(bCmd) { var b = -1; switch(bCmd) { case Keyboard.CMD.RESET: b = Keyboard.CMDRES.ACK; this.resetDevice(); break; default: break; } return b; }; /** * readScanCode(fShift) * * This is the ChipSet's interface for reading scan codes. This also doubles as the ChipSet's interface for checking * whether or not any data is available. * * @this {Keyboard} * @param {boolean} [fShift] is used by the MODEL_5170 8042 Keyboard Controller (supersedes the old setEnable() interface) * @return {number} next scan code, or 0 if none */ Keyboard.prototype.readScanCode = function(fShift) { var b = 0; if (this.abScanBuffer.length) { b = this.abScanBuffer[0]; this.messageDebugger("scan code 0x" + str.toHexByte(b) + " delivered"); if (fShift) this.shiftScanCode(); } return b; }; /** * shiftScanCode(fFlush) * * This is the ChipSet's interface to advance (or flush) scan codes. * * @this {Keyboard} * @param {boolean} [fFlush] is true to completely flush the keyboard buffer */ Keyboard.prototype.shiftScanCode = function(fFlush) { if (this.abScanBuffer.length > 0) { if (fFlush) { /* * This is now called after receipt of an 8042 self-test command, to ensure we don't * overwrite the self-test response byte with left-over scan codes. */ this.abScanBuffer = []; } else { /* * The keyboard interrupt service routine toggles the enable bit after reading a scan code, so * presumably this is the proper point at which to shift the last scan code out, and then assert * another interrupt if more scan codes exist. */ this.abScanBuffer.shift(); if (this.abScanBuffer.length > 0) { if (this.chipset) this.chipset.notifyKbdData(true); } } } }; /** * powerUp(data, fRepower) * * @this {Keyboard} * @param {Object|null} data * @param {boolean} [fRepower] * @return {boolean} true if successful, false if failure */ Keyboard.prototype.powerUp = function(data, fRepower) { if (!fRepower) { /* * TODO: Save/restore support for Keyboard is the barest minimum. In fact, originally, I wasn't * saving/restoring anything, and that was OK, but if we don't at least re-initialize fClock/fData, * we can get a spurious reset following a restore. In an ideal world, we might choose to save/restore * abScanBuffer as well, but realistically, I think it's going to be safer to always start with an * empty buffer--and who's going to notice anyway? * * So, like Debugger, we deviate from the typical save/restore pattern: instead of reset OR restore, * we always reset and then perform a (very limited) restore. */ this.reset(); if (data && this.restore) { if (!this.restore(data)) return false; } } return true; }; /** * powerDown(fSave) * * @this {Keyboard} * @param {boolean} fSave * @return {Object|boolean} */ Keyboard.prototype.powerDown = function(fSave) { return fSave && this.save? this.save() : true; }; /** * reset() * * @this {Keyboard} */ Keyboard.prototype.reset = function() { this.setModel(this.nDefaultModel); this.initState(); /* * The physical (not virtual) state of various shift keys. * * TODO: Determine how (or whether) we can query the browser's initial key states. */ this.bitsShift = 0; /* * New scan codes are "pushed" onto abScanBuffer and then "shifted" off. */ this.abScanBuffer = []; /* * When a key "down" is simulated on behalf of some keyCode, I save the timer object responsible for * simulating the key "up" here, so that if I detect the actual key going up sooner, I can cancel the * timer and simulate the "up" immediately. Similarly, if another press for the same key arrives before * last one expired (eg, auto-repeat), I need to cancel the previous timer for that key before setting another. * * NOTE: If this is anything other than an initial reset, then we need to make sure there are no outstanding * timers before we blow the array away. */ if (this.aKeyTimers) { for (var i in this.aKeyTimers) { if (str.isValidInt(i)) continue; // ignore any non-numeric properties, if any if (this.aKeyTimers[i]) clearTimeout(this.aKeyTimers[i]); } } this.aKeyTimers = []; this.prevCharDown = 0; this.prevKeyDown = 0; /* * Make sure the auto-injection buffer is empty (an injection could have been in progress on any reset after the first). */ this.sInjectBuffer = ""; }; /** * save() * * This implements save support for the Keyboard component. * * @this {Keyboard} * @return {Object} */ Keyboard.prototype.save = function() { var state = new State(this); state.set(0, this.saveState()); return state.data(); }; /** * restore(data) * * This implements restore support for the Keyboard component. * * @this {Keyboard} * @param {Object} data * @return {boolean} true if successful, false if failure */ Keyboard.prototype.restore = function(data) { return this.initState(data[0]); }; /** * initState(data) * * @this {Keyboard} * @param {Array} [data] * @return {boolean} true if successful, false if failure */ Keyboard.prototype.initState = function(data) { var i = 0; if (data === undefined) data = []; this.fClock = data[i++]; this.fData = data[i]; return true; }; /** * saveState() * * @this {Keyboard} * @return {Array} */ Keyboard.prototype.saveState = function() { var i = 0; var data = []; data[i++] = this.fClock; data[i] = this.fData; return data; }; /** * setSoftKeyState(control, f) * * @this {Keyboard} * @param {Object} control is an HTML control DOM object * @param {boolean} f is true if the key represented by e should be "on", false if "off" */ Keyboard.prototype.setSoftKeyState = function(control, f) { control.style.color = (f? "#ffffff" : "#000000"); control.style.backgroundColor = (f? "#000000" : "#ffffff"); }; /** * addScanCode(bScan, fRepeat) * * @this {Keyboard} * @param {number} bScan * @param {boolean} [fRepeat] */ Keyboard.prototype.addScanCode = function(bScan, fRepeat) { var bKey = bScan & Keyboard.SCANCODE.MAKE; var fDown = (bKey == bScan); /* * Prepare for the possibility that our reset() function may not have been called yet. * * TODO: Determine whether we need to reset() the Keyboard sooner (ie, in the constructor), * or if we need to protect other methods from prematurely accessing certain Keyboard structures, * as a result of calls from any of the key event handlers established by setBinding(). */ if (this.abScanBuffer) { if (this.abScanBuffer.length < Keyboard.LIMIT.MAX_SCANCODES) { if (!fDown && !this.aScanCodesActive[bKey] || fDown && this.aScanCodesActive[bKey] && !fRepeat) { if (MAXDEBUG) this.messageDebugger("scan code 0x" + str.toHexByte(bScan) + " redundant"); return; } this.aScanCodesActive[bKey] = fDown; this.messageDebugger("scan code 0x" + str.toHexByte(bScan) + " buffered"); this.abScanBuffer.push(bScan); if (this.abScanBuffer.length == 1) { if (this.chipset) this.chipset.notifyKbdData(true); } this.findBinding(bKey, "key", fDown); return; } if (this.abScanBuffer.length == Keyboard.LIMIT.MAX_SCANCODES) { this.abScanBuffer.push(Keyboard.CMDRES.BUFF_FULL); } this.messageDebugger("scan code buffer overflow"); } }; /** * calcReleaseDelay(fRepeat) * * Attempts to scale our default "release" delay appropriately for the current CPU speed. * * Note that if the effective CPU speed exceeds 16Mhz, it becomes very difficult to rely on timer-driven key events * (even the shortest available timer delay still gives the CPU too much time, so it thinks that even the briefest key * press represents a held key, resulting in multiple keystrokes). * * @this {Keyboard} * @param {boolean} fRepeat is true if a timeout had already been active for the current key * @return {number} */ Keyboard.prototype.calcReleaseDelay = function(fRepeat) { /* * NOTE: This delay affects only the "up" delay, not repeat delay, but it's useful to have an initial * "up" delay that's sufficiently large to ensure the native machine's auto-repeat behavior cooperates * with the virtual machine's auto-repeat behavior. msReleaseDelay is the initial delay, msReleaseRepeat * is the subsequent delay. * * Unfortunately, with a large initial delay, we need to enable the auto-clear code in the keyUpDown() * handler, otherwise doing things like pressing ENTER repeatedly will result in sluggish behavior * (because you can generally press/release/repress keys faster than they will auto-repeat). */ var msDelay = (fRepeat? this.msReleaseRepeat : this.msReleaseDelay); if (this.cpu && this.cpu.aCounts.mhz) msDelay /= this.cpu.aCounts.mhz; return msDelay; }; /** * autoClear(notKeyCode) * * @this {Keyboard} * @param {number} [notKeyCode] */ Keyboard.prototype.autoClear = function(notKeyCode) { if (this.prevCharDown && (notKeyCode === undefined || notKeyCode != this.prevCharDown)) { if (DEBUG) this.messageDebugger("autoClear(" + this.prevCharDown + ")"); Component.assert(this.aKeyTimers[this.prevCharDown]); clearTimeout(this.aKeyTimers[this.prevCharDown]); this.keySimulateUpDown(this.prevCharDown, false, Keyboard.SIMCODE.AUTOCLEAR); } }; /** * injectKeys(sKeyCodes, msDelay) * * @this {Keyboard} * @param {string} sKeyCodes * @param {number|undefined} [msDelay] is an optional injection delay (default is msInjectDelay) */ Keyboard.prototype.injectKeys = function(sKeyCodes, msDelay) { this.sInjectBuffer = sKeyCodes; if (DEBUG) this.log("injectKeys(" + this.sInjectBuffer.split("\n").join("\\n") + ")"); this.injectKeysFromBuffer(msDelay || this.msInjectDelay); }; /** * injectKeysFromBuffer(msDelay) * * @this {Keyboard} * @param {number} msDelay is the delay between injected keys */ Keyboard.prototype.injectKeysFromBuffer = function(msDelay) { if (this.sInjectBuffer.length > 0) { var ch = this.sInjectBuffer.charCodeAt(0); /* * I could require all callers to supply CRs instead of LFs, but this is friendlier. */ if (ch == 0x0a) ch = 0x0d; this.sInjectBuffer = this.sInjectBuffer.substr(1); this.keySimulatePress(ch); } if (this.sInjectBuffer.length > 0) { setTimeout(function (kbd) { return function onInjectKeyTimeout() { kbd.injectKeysFromBuffer(msDelay); }; }(this), msDelay); } }; /** * keyUpDown(event, fDown) * * @this {Keyboard} * @param {Object} event * @param {boolean} fDown is true for a keyDown event, false for a keyUp event * @return {boolean} true to pass the event along, false to consume it */ Keyboard.prototype.keyUpDown = function(event, fDown) { var fPass; var fAutoClear = !fDown; var keyCode = event.keyCode; var keyCodeSim = keyCode; if (fDown) this.prevKeyDown = keyCode; if (Keyboard.aKeyCodes[keyCode + Keyboard.KEYCODE.ONDOWN]) { keyCodeSim += Keyboard.KEYCODE.ONDOWN; var bShift = Keyboard.STATEKEYS[keyCode] || 0; if (bShift) { if (event.location == Keyboard.LOCATION.RIGHT) { bShift >>= 1; keyCodeSim += Keyboard.KEYCODE.ONRIGHT; } this.bitsShift &= ~bShift; if (fDown) this.bitsShift |= bShift; if (keyCode == Keyboard.KEYCODE.CAPS_LOCK) { /* * FYI, CAPS_LOCK generates a "down" event ONLY when getting locked, and an "up" event ONLY * when getting unlocked--which is exactly what I want, even though that may seem a little * counter-intuitive (since the key itself actually went down AND up for each event). */ fPass = this.keySimulatePress(keyCodeSim); } else { fAutoClear = false; } } else { if (keyCode == Keyboard.KEYCODE.BS || keyCode == Keyboard.KEYCODE.TAB || keyCode == Keyboard.KEYCODE.ESC) { /* * HACK for simulating Ctrl-Break using Ctrl-Del (Mac) / Ctrl-Backspace (Windows) */ if (keyCode == Keyboard.KEYCODE.BS && (this.bitsShift & (Keyboard.STATE.CTRL|Keyboard.STATE.ALT)) == Keyboard.STATE.CTRL) { keyCodeSim = Keyboard.KEYCODE.FAKE_CTRLBREAK; } /* * If I don't consume TAB on the "down" event, then that's all I'll see, because the * browser will see it and give focus to the next control. But the "down" side is that * that no "press" event will be generated. This puts it in the same category as ESC, * which also generates "down" and "up" events (LOTS of "down" events for that matter), * but no "press" event. The C1P has no TAB key, so it's safe to completely ignore, * hence the code below, but a PC does, so I need to simulate it. * * fPass = fAutoClear = false; * * I don't get keyPress events for ESC (why?) and I never want the browser to act on BS * (which does double-duty as the "Back" button and leaves the current page), so I have * to simulate them now. * * Note that I call the "press" simulate method and NOT the "event" simulate method, because * the former takes care of simulating both individual "down" and "up" events. */ fPass = (fDown? !this.keySimulatePress(keyCodeSim) : false); } else { /* * No effect, at least in IE9.... * if (keyCode == Keyboard.KEYCODE.F1 || keyCode == Keyboard.KEYCODE.F11 || keyCode == Keyboard.KEYCODE.F12) { this.println("preventDefault()"); event.preventDefault(); } */ } } } else { if (keyCode == Keyboard.KEYCODE.COMMAND) { /* * Avoid interfering with useful Browser key commands, like COMMAND-Q, COMMAND-T, etc. */ this.bitsShift &= ~Keyboard.STATE.COMMAND; if (fDown) this.bitsShift |= Keyboard.STATE.COMMAND; fAutoClear = false; fPass = true; } /* * All other ALT and/or CTRL-key combinations are handled here (in part because not all * generate keyPress events, and even those that do may generate odd keyCodes that I'd rather * not create mappings for). */ else if (event.altKey || event.ctrlKey) { if (keyCode >= Keyboard.ASCII.A && keyCode <= Keyboard.ASCII.Z) { /* * Convert "upper-case" letter combinations into "lower-case" combinations, so * that keySimulateUpDown() doesn't think it also needs to simulate a SHIFT key, too. */ keyCodeSim += (Keyboard.ASCII.a - Keyboard.ASCII.A); } // if (DEBUG) this.messageDebugger("ALT event: keyCode " + keyCode); } else { /* * Pass on anything else, and we'll take care of it at the keyPress stage (if at all) rather * than the keyDown/keyUp stage. */ fPass = true; /* * At this point, I have a difficult choice to make: leave fAutoClear true for any remaining * "up" events, so that keys will repeat immediately when released/pressed repeatedly (most * noticeable with the Enter key), or set fAutoClear to false to ensure that polling apps have * enough time to see every key press. * * I've decided that the former is more important than the latter, so if polling apps are still * missing keystrokes, then perhaps nCyclesThreshold needs to be supplemented in some way. * * fAutoClear = false; */ } } if (fAutoClear) { /* * When you use a command like COMMAND-T, I see the COMMAND key going down, but not going up, * so I think the COMMAND key is still down and ignore all input; to easily get out of that state, * I clear our internal BIT_COMMAND whenever I see ANY key go up (well, ALMOST any key; cases * above that explicitly clear fAutoClear -- such as the COMMAND key itself -- are exceptions * to the rule). */ this.bitsShift &= ~Keyboard.STATE.COMMAND; /* * I don't reliably get keyDown/keyUp events for all keys on all devices, but for those devices that * I DO, it seems like a good idea to cancel any pending key "up" simulation on receipt of the actual * keyUp event. * * However, the following code is problematic for Safari on iOS devices, which as noted above, doesn't * generate keyDown/keyUp events until after the press operation is complete, and then they are generated * in rapid succession, which doesn't give the C1P enough time to detect the key. So I simply don't do * this on iOS devices. */ if (!this.fMobile && keyCode == this.prevKeyDown) this.autoClear(); } if (fPass === undefined) { fPass = !this.keySimulateUpDown(keyCodeSim, fDown, Keyboard.SIMCODE.KEYUPDOWN); } if (DEBUG) this.messageDebugger(/*(fDown?"\n":"") +*/ "key" + (fDown? "Down" : "Up") + "(" + keyCode + "): " + (fPass? "pass" : "consume"), Debugger.MESSAGE.KEYS); return fPass; }; /** * keyPress(event) * * @this {Keyboard} * @param {Object} event * @return {boolean} true to pass the event along, false to consume it */ Keyboard.prototype.keyPress = function(event) { var fPass = true; event = event || window.event; var keyCode = event.which || event.keyCode; /* * Let's stop any injection currently in progress, too */ this.sInjectBuffer = ""; if (keyCode == Keyboard.KEYCODE.BS || keyCode == Keyboard.KEYCODE.TAB) { /* * Unlike Safari and Chrome, Firefox doesn't seem to honor our "consume" request for the "down" BS keyEvent, * so we must ALSO check for the BS key here, and again "consume" it. Ditto for TAB. * * This is just one example of a larger Firefox problem (see https://bugzilla.mozilla.org/show_bug.cgi?id=501496). * Basically, Firefox is not honoring our consumption of keyDown events, and generates keyPress events anyway. * This causes us grief for various CTRL and ALT combinations, resulting in duplicate key presses. So, I'm going * to try to fix this below, by setting fPass to true if either of those modifier keys is currently down; * if they're not, then we'll continue with the original code that sets fPass based on the return from keySimulatePress(). */ fPass = false; } else { if (this.bitsShift & Keyboard.STATE.COMMAND) this.bitsShift &= ~Keyboard.STATE.COMMAND; else { // if (DEBUG && event.altKey) this.messageDebugger("ALT press: keyCode " + keyCode, Debugger.MESSAGE.KEYS); if (this.bitsShift & (Keyboard.STATE.CTRL | Keyboard.STATE.ALT)) fPass = false; else fPass = !this.keySimulatePress(keyCode); } } if (DEBUG) this.messageDebugger("keyPress(" + keyCode + "): " + (fPass? "pass" : "consume"), Debugger.MESSAGE.KEYS); return fPass; }; /** * keySimulatePress(keyCode) * * @this {Keyboard} * @param {number} keyCode * @param {boolean} [fQuickRelease] is true to simulate the press and release immediately * @return {boolean} true if successfully simulated, false if unrecognized/unsupported key */ Keyboard.prototype.keySimulatePress = function(keyCode, fQuickRelease) { var fSimulated = false; /* * Auto-clear any previous down key EXCEPT for keyCode (because it may be held and repeating). */ this.autoClear(keyCode); if (this.keySimulateUpDown(keyCode, true, Keyboard.SIMCODE.KEYPRESS)) { /* * If fQuickRelease is set, we switch to an alternate approach, which is to immediately queue a * "release" event as well. I used to also do this at high speeds, because the CPU could get lucky * and execute a LOT of instructions between delivery of the keyPress event and the "keyTimeout" * event, and since JavaScript events (including timeouts) are delivered synchronously, it might * take too long for the "keyTimeout" event to arrive. * * Why don't we ALWAYS do this? Because at normal CPU speeds, we want to faithfully simulate how * long a key is held, so that features like auto-repeat work properly. * * TODO: The above is probably more true for C1Pjs (where some of this code came from) than PCjs, * so revisit these assumptions. The fact that I had to add the fQuickRelease parameter suggests * that it's time to review/overhaul this code. */ if (fQuickRelease /* || this.cpu.speed == CPU.SPEED_MAX */) { this.keySimulateUpDown(keyCode, false, Keyboard.SIMCODE.KEYRELEASE); } else { var fRepeat = false; if (this.aKeyTimers[keyCode]) { clearTimeout(this.aKeyTimers[keyCode]); fRepeat = true; } var msDelay = this.calcReleaseDelay(fRepeat); this.aKeyTimers[this.prevCharDown = keyCode] = setTimeout(function (kbd) { return function onkeySimulatePressTimeout() { kbd.keySimulateUpDown(keyCode, false, Keyboard.SIMCODE.KEYTIMEOUT); }; }(this), msDelay); if (DEBUG) this.messageDebugger("keySimulatePress(" + keyCode + "): setTimeout()"); } fSimulated = true; } if (DEBUG) this.messageDebugger("keySimulatePress(" + keyCode + "): " + (fSimulated? "true" : "false"), Debugger.MESSAGE.KEYS); return fSimulated; }; /** * keySimulateUpDown(keyCode, fDown, simCode) * * @this {Keyboard} * @param {number} keyCode * @param {boolean} fDown * @param {number} simCode indicates the origin of the event * @return {boolean} true if successfully simulated, false if unrecognized/unsupported key */ Keyboard.prototype.keySimulateUpDown = function(keyCode, fDown, simCode) { var fSimulated = false; if (!fDown) { this.aKeyTimers[keyCode] = null; if (this.prevCharDown == keyCode) this.prevCharDown = 0; } var wCode = Keyboard.aKeyCodes[keyCode] || Keyboard.aKeyCodes[keyCode + Keyboard.KEYCODE.ONDOWN]; if (wCode !== undefined) { /* * Hack to transform the IBM "BACKSPACE" key (which we normally map to KEYCODE_DELETE) to the IBM "DEL" key * whenever both CTRL and ALT are pressed as well, so that it's easier to simulate that old favorite: CTRL-ALT-DEL */ if (wCode == Keyboard.SCANCODE.BS) { if ((this.bitsShift & (Keyboard.STATE.CTRL | Keyboard.STATE.ALT)) == (Keyboard.STATE.CTRL | Keyboard.STATE.ALT)) { wCode = Keyboard.SCANCODE.NUM_DEL; } } var abScanCodes = []; var bCode = wCode & 0xff; abScanCodes.push(bCode | (fDown? 0 : Keyboard.SCANCODE.BREAK)); var fAlpha = (keyCode >= Keyboard.ASCII.A && keyCode <= Keyboard.ASCII.Z || keyCode >= Keyboard.ASCII.a && keyCode <= Keyboard.ASCII.z); while (wCode >>>= 8) { var bShift = 0; var bScan = wCode & 0xff; /* * TODO: The handling of aKeyCodes entries with "extended" codes still needs to be tested, and * moreover, if any of them need to perform any shift-state modifications, those modifications will * need to be encoded differently. */ if (bCode == Keyboard.SCANCODE.EXTEND1 || bCode == Keyboard.SCANCODE.EXTEND2) { abScanCodes.push(bCode | (fDown? 0 : Keyboard.SCANCODE.BREAK)); continue; } if (bScan == Keyboard.SCANCODE.SHIFT) { if (!(this.bitsShift & (Keyboard.STATE.SHIFT | Keyboard.STATE.RSHIFT))) { if (!(this.bitsShift & Keyboard.STATE.CAPS_LOCK) || !fAlpha) { bShift = bScan; } } } else if (bScan == Keyboard.SCANCODE.CTRL) { if (!(this.bitsShift & (Keyboard.STATE.CTRL | Keyboard.STATE.RCTRL))) { bShift = bScan; } } else if (bScan == Keyboard.SCANCODE.ALT) { if (!(this.bitsShift & (Keyboard.STATE.ALT | Keyboard.STATE.RALT))) { bShift = bScan; } } else { abScanCodes.push(bCode | (fDown? 0 : Keyboard.SCANCODE.BREAK)); } if (bShift) { if (fDown) abScanCodes.unshift(bShift); else abScanCodes.push(bShift | Keyboard.SCANCODE.BREAK); } } for (var i = 0; i < abScanCodes.length; i++) { this.addScanCode(abScanCodes[i]); } fSimulated = true; } if (DEBUG && DEBUGGER) this.messageDebugger("keySimulateUpDown(" + keyCode + "," + (fDown? "down" : "up") + "," + Keyboard.aSimCodeDescs[simCode] + "): " + (fSimulated? "true" : "false"), Debugger.MESSAGE.KEYS); return fSimulated; }; /** * messageDebugger(sMessage, bitsMessage) * * This is a combination of the Debugger's messageEnabled(MESSAGE_KBD) and message() functions, for convenience. * * @this {Keyboard} * @param {string} sMessage is any caller-defined message string * @param {number} [bitsMessage] is one or more Debugger MESSAGE_* category flag(s) */ Keyboard.prototype.messageDebugger = function(sMessage, bitsMessage) { if (DEBUGGER && this.dbg) { if (bitsMessage == null) bitsMessage = Debugger.MESSAGE.KBD; if (bitsMessage == Debugger.MESSAGE.PORT) bitsMessage |= Debugger.MESSAGE.KBD; if (this.dbg.messageEnabled(bitsMessage)) this.dbg.message(sMessage); } }; /** * Keyboard.init() * * This function operates on every element (e) of class "keyboard", and initializes * all the necessary HTML to construct the Keyboard module(s) as spec'ed. * * Note that each element (e) of class "keyboard" is expected to have a "data-value" * attribute containing the same JSON-encoded parameters that the Keyboard constructor * expects. */ Keyboard.init = function() { var aeKbd = Component.getElementsByClass(window.document, PCJSCLASS, "keyboard"); for (var iKbd = 0; iKbd < aeKbd.length; iKbd++) { var eKbd = aeKbd[iKbd]; var parmsKbd = Component.getComponentParms(eKbd); var kbd = new Keyboard(parmsKbd); Component.bindComponentControls(kbd, eKbd, PCJSCLASS); } }; /* * Initialize every Keyboard module on the page. */ web.onInit(Keyboard.init); if (typeof APP_PCJS !== 'undefined') APP_PCJS.Keyboard = Keyboard; if (typeof module !== 'undefined') module.exports = Keyboard;