/** * @fileoverview Implements the PCx86 Keyboard component. * @author Jeff Parsons * @copyright © Jeff Parsons 2012-2016 * * 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 modified copy of this work * and to display that copyright notice when the software starts running; see COPYRIGHT in * . * * 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 Keys = require("../../shared/lib/keys"); var State = require("../../shared/lib/state"); var PCX86 = require("./defines"); var Messages = require("./messages"); var ChipSet = require("./chipset"); 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"]; Keyboard.SIMCODE = { BS: Keys.KEYCODE.BS + Keys.KEYCODE.ONDOWN, TAB: Keys.KEYCODE.TAB + Keys.KEYCODE.ONDOWN, SHIFT: Keys.KEYCODE.SHIFT + Keys.KEYCODE.ONDOWN, RSHIFT: Keys.KEYCODE.SHIFT + Keys.KEYCODE.ONDOWN + Keys.KEYCODE.ONRIGHT, CTRL: Keys.KEYCODE.CTRL + Keys.KEYCODE.ONDOWN, ALT: Keys.KEYCODE.ALT + Keys.KEYCODE.ONDOWN, CAPS_LOCK: Keys.KEYCODE.CAPS_LOCK + Keys.KEYCODE.ONDOWN, ESC: Keys.KEYCODE.ESC + Keys.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