pcjs/modules/pcjs/lib/keyboard.js
2014-11-11 08:19:48 -08:00

1719 lines
75 KiB
JavaScript

/**
* @fileoverview Implements the PCjs Keyboard component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2012-Jun-20
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* 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 <http://www.gnu.org/licenses/gpl.html>.
*
* 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:
*
* <control class="input" type="text" binding="kbd" width="2em">Kbd</control>
*
* 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;