/** * @fileoverview Implements the PCx86 Keyboard component. * @author Jeff Parsons * @version 1.0 * Created 2012-Jun-20 * * Copyright © 2012-2016 Jeff Parsons * * This file is part of PCjs, a computer emulation software project at . * * 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 COPYRIGHT in /modules/shared/lib/defines.js). * * 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 PCjs * for purposes of the GNU General Public License, and the author does not claim any copyright * as to their contents. */ "use strict"; if (NODE) { var str = require("../../shared/lib/strlib"); var web = require("../../shared/lib/weblib"); var Component = require("../../shared/lib/component"); var Messages = require("./messages"); var ChipSet = require("./chipset"); var State = require("./state"); var CPU = require("./cpu"); } /** * Keyboard(parmsKbd) * * The Keyboard component can be configured with the following (parmsKbd) properties: * * model: keyboard model string, which must match one of the values listed in Keyboard.MODELS: * * "US83" (default) * "US84" * "US101" * * 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, Messages.KEYBOARD); this.setModel(parmsKbd['model']); this.fMobile = web.isMobile(); this.fMSIE = web.isUserAgent("MSIE"); this.printMessage("mobile keyboard support: " + (this.fMobile? "true" : "false")); /* * This is count of the number of "soft keyboard" keys present. At the moment, its only * purpose is to signal findBinding() whether to waste any time looking for SOFTCODE matches. */ this.cSoftCodes = 0; /* * Updated by onFocusChange() */ this.fHasFocus = true; /* * This is true whenever the physical Escape key is disabled (eg, by pointer locking code), * giving us the opportunity to map a different physical key to machine's virtual Escape key. */ this.fEscapeDisabled = false; /* * This is set whenever we notice a discrepancy between our internal CAPS_LOCK state and its * apparent state; we check whenever aKeysActive has been emptied. */ this.fToggleCapsLock = false; /* * New unified approach to key event processing: When we process a key on the "down" event, * we check the aKeysActive array: if the key is already active, do nothing; otherwise, insert * it into the table, generate the "make" scan code(s), and set a timeout for "repeat" if it's * a repeatable key (most are). * * Similarly, when a key goes "up", if it's already not active, do nothing; otherwise, generate * the "break" scan code(s), cancel any pending timeout, and remove it from the active key table. * * If a "press" event is received, then if the key is already active, remove it and (re)insert * it at the head of the table, generate the "make" scan code(s), set nRepeat to -1, and set a * timeout for "break". * * This requires an aKeysActive array that keeps track of the status of every active key; only the * first entry in the array is allowed to repeat. Each entry is a key object with the following * properties: * * simCode: our simulated keyCode from onKeyDown, onKeyUp, or onKeyPress * fDown: next state to simulate (true for down, false for up) * nRepeat: > 0 if timer should generate more "make" scan code(s), -1 for "break" scan code(s) * timer: timer for next key operation, if any * * Keys are inserted at the head of aKeysActive, using splice(0, 0, key), but not before zeroing * nRepeat of any repeating key that already occupies the head (index 0), so that at most only one * key (ie, the most recent) will ever be in a repeating state. * * IBM PC keyboard repeat behavior: when pressing CTRL, then C, and then releasing CTRL while still * holding C, the repeated CTRL_C characters turn into 'c' characters. We emulate that behavior. * However, when pressing C, then CTRL, all repeating stops: not a single CTRL_C is generated, and * even if the CTRL is released before the C, no more more 'c' characters are generated either. * We do NOT fully emulate that behavior -- we DO stop the repeating, but we also generate one CTRL_C. * More investigation is required, because I need to confirm whether the IBM keyboard automatically * "breaks" all non-shift keys before it "makes" the CTRL. */ this.aKeysActive = []; this.msAutoRepeat = 500; this.msNextRepeat = 100; this.msAutoRelease = 50; this.msInjectDelay = 300; // number of milliseconds between injected keystrokes /* * HACK: We set fAllDown to false to ignore all down/up events for keys not explicitly marked as ONDOWN; * even though that prevents those keys from being repeated properly (ie, at the simulation's repeat rate * rather than the browser's repeat rate), it's the safest thing to do when dealing with international keyboards, * because our mapping tables are designed for US keyboards, and testing all the permutations of international * keyboards and web browsers is more work than I can take on right now. TODO: Dig into this some day. */ this.fAllDown = false; this.setReady(); } Component.subclass(Keyboard); /* * Supported keyboard models (the first entry is the default if the specified model isn't recognized) */ Keyboard.MODELS = ["US83", "US84", "US101"]; /** * 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 = { CTRL_A: 1, CTRL_C: 3, CTRL_Z: 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 CLICKCODE 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, /* 0x13 */ PAUSE: 19, // PAUSE/BREAK /* 0x14 */ CAPS_LOCK: 20, /* 0x1B */ ESC: 27, /* 0x20 */ SPACE: 32, /* 0x21 */ PGUP: 33, /* 0x22 */ PGDN: 34, /* 0x23 */ END: 35, /* 0x24 */ HOME: 36, /* 0x25 */ LEFT: 37, /* 0x26 */ UP: 38, /* 0x27 */ RIGHT: 39, /* 0x27 */ FF_QUOTE: 39, /* 0x28 */ DOWN: 40, /* 0x2C */ FF_COMMA: 44, /* 0x2C */ PRTSC: 44, /* 0x2D */ INS: 45, /* 0x2E */ DEL: 46, /* 0x2E */ FF_PERIOD: 46, /* 0x2F */ FF_SLASH: 47, /* 0x3B */ FF_SEMI: 59, /* 0x3D */ FF_EQUALS: 61, /* 0x5B */ CMD: 91, // aka WIN /* 0x5B */ FF_LBRACK: 91, /* 0x5C */ FF_BSLASH: 92, /* 0x5D */ RCMD: 93, // aka MENU /* 0x5D */ FF_RBRACK: 93, /* 0x60 */ NUM_INS: 96, // 0 /* 0x60 */ FF_BQUOTE: 96, /* 0x61 */ NUM_END: 97, // 1 /* 0x62 */ NUM_DOWN: 98, // 2 /* 0x63 */ NUM_PGDN: 99, // 3 /* 0x64 */ NUM_LEFT: 100, // 4 /* 0x65 */ NUM_CENTER: 101, // 5 /* 0x66 */ NUM_RIGHT: 102, // 6 /* 0x67 */ NUM_HOME: 103, // 7 /* 0x68 */ NUM_UP: 104, // 8 /* 0x69 */ NUM_PGUP: 105, // 9 /* 0x6A */ NUM_MUL: 106, /* 0x6B */ NUM_ADD: 107, /* 0x6D */ NUM_SUB: 109, /* 0x6E */ NUM_DEL: 110, // . /* 0x6F */ NUM_DIV: 111, /* 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, /* 0x90 */ NUM_LOCK: 144, /* 0x91 */ SCROLL_LOCK: 145, /* 0xAD */ FF_DASH: 173, /* 0xBA */ SEMI: 186, // Firefox: 59 /* 0xBB */ EQUALS: 187, // Firefox: 61 /* 0xBC */ COMMA: 188, // Firefox: 44 /* 0xBD */ DASH: 189, // Firefox: 173 /* 0xBE */ PERIOD: 190, // Firefox: 46 /* 0xBF */ SLASH: 191, // Firefox: 47 /* 0xC0 */ BQUOTE: 192, // Firefox: 96 /* 0xDB */ LBRACK: 219, // Firefox: 91 /* 0xDC */ BSLASH: 220, // Firefox: 92 /* 0xDD */ RBRACK: 221, // Firefox: 93 /* 0xDE */ QUOTE: 222, // Firefox: 39 /* 0xE0 */ FF_CMD: 224, // Firefox only (used for both CMD and RCMD) // // The following biases use what I'll call Decimal Coded Binary or DCB (the opposite of BCD), // where the thousands digit is used to store the sum of "binary" digits 1 and/or 2 and/or 4. // // Technically, that makes it DCO (Decimal Coded Octal), but then again, BCD should have really // been called HCD (Hexadecimal Coded Decimal), so if "they" can take liberties, so can I. // // ONDOWN is a bias we add to browser keyCodes that we want to handle on "down" rather than on "press". // 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 }; /* * Maps "stupid" keyCodes to their "non-stupid" counterparts */ Keyboard.STUPID_KEYCODES = {}; Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.SEMI] = Keyboard.ASCII[';']; // 186 -> 59 Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.EQUALS] = Keyboard.ASCII['=']; // 187 -> 61 Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.COMMA] = Keyboard.ASCII[',']; // 188 -> 44 Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.DASH] = Keyboard.ASCII['-']; // 189 -> 45 Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.PERIOD] = Keyboard.ASCII['.']; // 190 -> 46 Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.SLASH] = Keyboard.ASCII['/']; // 191 -> 47 Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.BQUOTE] = Keyboard.ASCII['`']; // 192 -> 96 Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.LBRACK] = Keyboard.ASCII['[']; // 219 -> 91 Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.BSLASH] = Keyboard.ASCII['\\']; // 220 -> 92 Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.RBRACK] = Keyboard.ASCII[']']; // 221 -> 93 Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.QUOTE] = Keyboard.ASCII["'"]; // 222 -> 39 Keyboard.STUPID_KEYCODES[Keyboard.KEYCODE.FF_DASH] = Keyboard.ASCII['-']; /* * Maps unshifted keyCodes to their shifted counterparts; to be used when a shift-key is down. * Alphabetic characters are handled in code, since they must also take CAPS_LOCK into consideration. */ Keyboard.SHIFTED_KEYCODES = {}; Keyboard.SHIFTED_KEYCODES[Keyboard.ASCII['1']] = Keyboard.ASCII['!']; Keyboard.SHIFTED_KEYCODES[Keyboard.ASCII['2']] = Keyboard.ASCII['@']; Keyboard.SHIFTED_KEYCODES[Keyboard.ASCII['3']] = Keyboard.ASCII['#']; Keyboard.SHIFTED_KEYCODES[Keyboard.ASCII['4']] = Keyboard.ASCII['$']; Keyboard.SHIFTED_KEYCODES[Keyboard.ASCII['5']] = Keyboard.ASCII['%']; Keyboard.SHIFTED_KEYCODES[Keyboard.ASCII['6']] = Keyboard.ASCII['^']; Keyboard.SHIFTED_KEYCODES[Keyboard.ASCII['7']] = Keyboard.ASCII['&']; Keyboard.SHIFTED_KEYCODES[Keyboard.ASCII['8']] = Keyboard.ASCII['*']; Keyboard.SHIFTED_KEYCODES[Keyboard.ASCII['9']] = Keyboard.ASCII['(']; Keyboard.SHIFTED_KEYCODES[Keyboard.ASCII['0']] = Keyboard.ASCII[')']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.SEMI] = Keyboard.ASCII[':']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.EQUALS] = Keyboard.ASCII['+']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.COMMA] = Keyboard.ASCII['<']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.DASH] = Keyboard.ASCII['_']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.PERIOD] = Keyboard.ASCII['>']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.SLASH] = Keyboard.ASCII['?']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.BQUOTE] = Keyboard.ASCII['~']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.LBRACK] = Keyboard.ASCII['{']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.BSLASH] = Keyboard.ASCII['|']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.RBRACK] = Keyboard.ASCII['}']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.QUOTE] = Keyboard.ASCII['"']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.FF_DASH] = Keyboard.ASCII['_']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.FF_EQUALS] = Keyboard.ASCII['+']; Keyboard.SHIFTED_KEYCODES[Keyboard.KEYCODE.FF_SEMI] = Keyboard.ASCII[':']; Keyboard.SIMCODE = { BS: Keyboard.KEYCODE.BS + Keyboard.KEYCODE.ONDOWN, TAB: Keyboard.KEYCODE.TAB + Keyboard.KEYCODE.ONDOWN, SHIFT: Keyboard.KEYCODE.SHIFT + Keyboard.KEYCODE.ONDOWN, RSHIFT: Keyboard.KEYCODE.SHIFT + Keyboard.KEYCODE.ONDOWN + Keyboard.KEYCODE.ONRIGHT, CTRL: Keyboard.KEYCODE.CTRL + Keyboard.KEYCODE.ONDOWN, ALT: Keyboard.KEYCODE.ALT + Keyboard.KEYCODE.ONDOWN, CAPS_LOCK: Keyboard.KEYCODE.CAPS_LOCK + Keyboard.KEYCODE.ONDOWN, ESC: Keyboard.KEYCODE.ESC + Keyboard.KEYCODE.ONDOWN, /* * It seems that a recent change to Safari on iOS (first noticed in iOS 9.1) treats SPACE * differently now, at least with regard to