pcjs/my_modules/pcjs-client/lib/keyboard.js

1574 lines
67 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);
/**
* 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,
ACK: 0xFA,
BAT_FAIL: 0xFC, // Basic Assurance Test (BAT) failed
DIAG_FAIL: 0xFD,
RESEND: 0xFE
};
/**
* TODO: Determine what we can do to get ALL constants like these inlined (enum doesn't seem to be getting
* the job done); the basic problem seems to be caused by referencing the properties with quotes.
*
* @enum {number}
*/
Keyboard.ASCII = {
' ': 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.
*
* @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 */ CAPSLOCK: 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,
//
// This is a bias we add to browser keyCodes that we want to handle on "down" rather than "press"
//
ONDOWN: 1000,
//
// This is a bias we add to browser keyCodes that need to check for a "right" location (default is "left")
//
ONRIGHT: 2000,
//
// This is a bias we add to create fake keyCodes that correspond to special keystroke sequences
//
FAKE: 4000,
FAKE_CTRLC: 4003,
FAKE_CTRLBREAK: 4101,
FAKE_CTRLALTDEL: 4102
};
/**
* 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 "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
CAPSLOCK: 0x0100,
NUMLOCK: 0x0200, // TODO: Placeholder
SCROLLLOCK: 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 = {
0x10: Keyboard.STATE.SHIFT,
0x11: Keyboard.STATE.CTRL,
0x12: Keyboard.STATE.ALT
};
/**
* Browsers brilliantly define keyCodes for non-ASCII keys (eg, 37 for the LEFT arrow key) with
* values that overlap ASCII keyCodes (eg, 37 is also the ASCII code for '%'), which unnecessarily
* complicates life for keyDown/keyPress/keyUp handlers, all of which receive a keyCode property,
* but which does NOT always contain the same value for the same key press.
*
* We solve the problem by adding an ONDOWN bias to all these particular keyCodes, and then make
* sure we store these keyCodes in aKeyCodes with the same bias. ONDOWN is also a signal to our
* keyCode handlers that the key in question should be handled during keyDown, not keyPress, often
* because they 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.CAPSLOCK + 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
};
/**
* Define "soft keyboard" identifiers for all possible keys, based on their primary (unshifted) character
* or function. This also serves as a definition of all supported scan codes.
*
* One exception to the above rule 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 (I'm using double-quotes to describe the operations and single-quotes to
* describe the keys).
*
* On 101-key keyboards, IBM decided to move both those special operations to a new 'pause' key, alongside
* the dedicated 'prtsc' ('Print Screen') key. Those keys behaved 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').
*
* 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': 0x01,
/* 2 */ '1': 0x02,
/* 3 */ '2': 0x03,
/* 4 */ '3': 0x04,
/* 5 */ '4': 0x05,
/* 6 */ '5': 0x06,
/* 7 */ '6': 0x07,
/* 8 */ '7': 0x08,
/* 9 */ '8': 0x09,
/* 10 */ '9': 0x0a,
/* 11 */ '0': 0x0b,
/* 12 */ '-': 0x0c,
/* 13 */ '=': 0x0d,
/* 14 */ 'backspace': 0x0e,
/* 15 */ 'tab': 0x0f,
/* 16 */ 'q': 0x10,
/* 17 */ 'w': 0x11,
/* 18 */ 'e': 0x12,
/* 19 */ 'r': 0x13,
/* 20 */ 't': 0x14,
/* 21 */ 'y': 0x15,
/* 22 */ 'u': 0x16,
/* 23 */ 'i': 0x17,
/* 24 */ 'o': 0x18,
/* 25 */ 'p': 0x19,
/* 26 */ '[': 0x1a,
/* 27 */ ']': 0x1b,
/* 28 */ 'enter': 0x1c,
/* 29 */ 'ctrl': 0x1d,
/* 30 */ 'a': 0x1e,
/* 31 */ 's': 0x1f,
/* 32 */ 'd': 0x20,
/* 33 */ 'f': 0x21,
/* 34 */ 'g': 0x22,
/* 35 */ 'h': 0x23,
/* 36 */ 'j': 0x24,
/* 37 */ 'k': 0x25,
/* 38 */ 'l': 0x26,
/* 39 */ ';': 0x27,
/* 40 */ 'quote': 0x28, // formerly "squote"
/* 41 */ '`': 0x29, // formerly "bquote"
/* 42 */ 'shift': 0x2a, // formerly "lshift"
/* 43 */ '\\': 0x2b, // formerly "bslash"
/* 44 */ 'z': 0x2c,
/* 45 */ 'x': 0x2d,
/* 46 */ 'c': 0x2e,
/* 47 */ 'v': 0x2f,
/* 48 */ 'b': 0x30,
/* 49 */ 'n': 0x31,
/* 50 */ 'm': 0x32,
/* 51 */ ',': 0x33,
/* 52 */ '.': 0x34,
/* 53 */ '/': 0x35,
/* 54 */ 'right-shift': 0x36, // formerly "rshift"
/* 55 */ 'prtsc': 0x37, // unshifted '*'; becomes dedicated 'Print Screen' key on 101-key keyboards
/* 56 */ 'alt': 0x38,
/* 57 */ 'space': 0x39,
/* 58 */ 'caps-lock': 0x3a,
/* 59 */ 'f1': 0x3b,
/* 60 */ 'f2': 0x3c,
/* 61 */ 'f3': 0x3d,
/* 62 */ 'f4': 0x3e,
/* 63 */ 'f5': 0x3f,
/* 64 */ 'f6': 0x40,
/* 65 */ 'f7': 0x41,
/* 66 */ 'f8': 0x42,
/* 67 */ 'f9': 0x43,
/* 68 */ 'f10': 0x44,
/* 69 */ 'num-lock': 0x45,
/* 70 */ 'scroll-lock': 0x46, // 0xe046 on 101-key keyboards?
/* 71 */ 'num-home': 0x47, // formerly "home"
/* 72 */ 'num-up': 0x48, // formerly "up-arrow"
/* 73 */ 'num-pgup': 0x49, // formerly "page-up"
/* 74 */ 'num-sub': 0x4a, // formerly "num-minus"
/* 75 */ 'num-left': 0x4b, // formerly "left-arrow"
/* 76 */ 'num-center': 0x4c, // formerly "center"
/* 77 */ 'num-right': 0x4d, // formerly "right-arrow"
/* 78 */ 'num-add': 0x4e, // formerly "num-plus"
/* 79 */ 'num-end': 0x4f, // formerly "end"
/* 80 */ 'num-down': 0x50, // formerly "down-arrow"
/* 81 */ 'num-pgdn': 0x51, // formerly "page-down"
/* 82 */ 'num-ins': 0x52, // formerly "ins"
/* 83 */ 'num-del': 0x53, // formerly "del"
/* 84 */ 'sysreq': 0x54, // 84-key keyboard only (simulated with 'alt'+'prtsc' on 101-key keyboards)
/* 84 */ 'pause': 0x54, // 101-key keyboard only
/* 85 */ 'f11': 0x57,
/* 86 */ 'f12': 0x58,
/* 87 */ 'num-enter': 0xe01c,
/* 88 */ 'right-ctrl': 0xe01d,
/* 89 */ 'num-div': 0xe035,
/* 90 */ 'num-mul': 0xe037,
/* 91 */ 'right-alt': 0xe038,
/* 92 */ 'home': 0xe047,
/* 93 */ 'up': 0xe048,
/* 94 */ 'pgup': 0xe049,
/* 95 */ 'left': 0xe04b,
/* 96 */ 'right': 0xe04d,
/* 97 */ 'end': 0xe04f,
/* 98 */ 'down': 0xe050,
/* 99 */ 'pgdn': 0xe051,
/* 100 */ 'ins': 0xe052,
/* 101 */ 'del': 0xe053,
'win': 0xe05b, // ie, the 'Windows' key
'right-win': 0xe05c,
'menu': 0xe05d
};
/**
* This array is used by keyEventSimulate() 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: keyEventSimulate() 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.aSoftCodes['esc'];
Keyboard.aKeyCodes[Keyboard.ASCII['1']] = Keyboard.aSoftCodes['1'];
Keyboard.aKeyCodes[Keyboard.ASCII['!']] = Keyboard.aSoftCodes['1'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['2']] = Keyboard.aSoftCodes['2'];
Keyboard.aKeyCodes[Keyboard.ASCII['@']] = Keyboard.aSoftCodes['2'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['3']] = Keyboard.aSoftCodes['3'];
Keyboard.aKeyCodes[Keyboard.ASCII['#']] = Keyboard.aSoftCodes['3'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['4']] = Keyboard.aSoftCodes['4'];
Keyboard.aKeyCodes[Keyboard.ASCII['$']] = Keyboard.aSoftCodes['4'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['5']] = Keyboard.aSoftCodes['5'];
Keyboard.aKeyCodes[Keyboard.ASCII['%']] = Keyboard.aSoftCodes['5'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['6']] = Keyboard.aSoftCodes['6'];
Keyboard.aKeyCodes[Keyboard.ASCII['^']] = Keyboard.aSoftCodes['6'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['7']] = Keyboard.aSoftCodes['7'];
Keyboard.aKeyCodes[Keyboard.ASCII['&']] = Keyboard.aSoftCodes['7'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['8']] = Keyboard.aSoftCodes['8'];
Keyboard.aKeyCodes[Keyboard.ASCII['*']] = Keyboard.aSoftCodes['8'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['9']] = Keyboard.aSoftCodes['9'];
Keyboard.aKeyCodes[Keyboard.ASCII['(']] = Keyboard.aSoftCodes['9'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['0']] = Keyboard.aSoftCodes['0'];
Keyboard.aKeyCodes[Keyboard.ASCII[')']] = Keyboard.aSoftCodes['0'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['-']] = Keyboard.aSoftCodes['-'];
Keyboard.aKeyCodes[Keyboard.ASCII['_']] = Keyboard.aSoftCodes['-'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['=']] = Keyboard.aSoftCodes['='];
Keyboard.aKeyCodes[Keyboard.ASCII['+']] = Keyboard.aSoftCodes['='] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.KEYCODE.BS + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['backspace'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.TAB + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['tab'];
Keyboard.aKeyCodes[Keyboard.ASCII.q] = Keyboard.aSoftCodes['q'];
Keyboard.aKeyCodes[Keyboard.ASCII.Q] = Keyboard.aSoftCodes['q'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.w] = Keyboard.aSoftCodes['w'];
Keyboard.aKeyCodes[Keyboard.ASCII.W] = Keyboard.aSoftCodes['w'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.e] = Keyboard.aSoftCodes['e'];
Keyboard.aKeyCodes[Keyboard.ASCII.E] = Keyboard.aSoftCodes['e'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.r] = Keyboard.aSoftCodes['r'];
Keyboard.aKeyCodes[Keyboard.ASCII.R] = Keyboard.aSoftCodes['r'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.t] = Keyboard.aSoftCodes['t'];
Keyboard.aKeyCodes[Keyboard.ASCII.T] = Keyboard.aSoftCodes['t'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.y] = Keyboard.aSoftCodes['y'];
Keyboard.aKeyCodes[Keyboard.ASCII.Y] = Keyboard.aSoftCodes['y'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.u] = Keyboard.aSoftCodes['u'];
Keyboard.aKeyCodes[Keyboard.ASCII.U] = Keyboard.aSoftCodes['u'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.i] = Keyboard.aSoftCodes['i'];
Keyboard.aKeyCodes[Keyboard.ASCII.I] = Keyboard.aSoftCodes['i'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.o] = Keyboard.aSoftCodes['o'];
Keyboard.aKeyCodes[Keyboard.ASCII.O] = Keyboard.aSoftCodes['o'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.p] = Keyboard.aSoftCodes['p'];
Keyboard.aKeyCodes[Keyboard.ASCII.P] = Keyboard.aSoftCodes['p'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['[']] = Keyboard.aSoftCodes['['];
Keyboard.aKeyCodes[Keyboard.ASCII['{']] = Keyboard.aSoftCodes['['] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII[']']] = Keyboard.aSoftCodes[']'];
Keyboard.aKeyCodes[Keyboard.ASCII['}']] = Keyboard.aSoftCodes[']'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.KEYCODE.CR] = Keyboard.aSoftCodes['enter'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.CTRL + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['ctrl'];
Keyboard.aKeyCodes[Keyboard.ASCII.a] = Keyboard.aSoftCodes['a'];
Keyboard.aKeyCodes[Keyboard.ASCII.A] = Keyboard.aSoftCodes['a'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.s] = Keyboard.aSoftCodes['s'];
Keyboard.aKeyCodes[Keyboard.ASCII.S] = Keyboard.aSoftCodes['s'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.d] = Keyboard.aSoftCodes['d'];
Keyboard.aKeyCodes[Keyboard.ASCII.D] = Keyboard.aSoftCodes['d'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.f] = Keyboard.aSoftCodes['f'];
Keyboard.aKeyCodes[Keyboard.ASCII.F] = Keyboard.aSoftCodes['f'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.g] = Keyboard.aSoftCodes['g'];
Keyboard.aKeyCodes[Keyboard.ASCII.G] = Keyboard.aSoftCodes['g'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.h] = Keyboard.aSoftCodes['h'];
Keyboard.aKeyCodes[Keyboard.ASCII.H] = Keyboard.aSoftCodes['h'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.j] = Keyboard.aSoftCodes['j'];
Keyboard.aKeyCodes[Keyboard.ASCII.J] = Keyboard.aSoftCodes['j'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.k] = Keyboard.aSoftCodes['k'];
Keyboard.aKeyCodes[Keyboard.ASCII.K] = Keyboard.aSoftCodes['k'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.l] = Keyboard.aSoftCodes['l'];
Keyboard.aKeyCodes[Keyboard.ASCII.L] = Keyboard.aSoftCodes['l'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII[';']] = Keyboard.aSoftCodes[';'];
Keyboard.aKeyCodes[Keyboard.ASCII[':']] = Keyboard.aSoftCodes[';'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII["'"]] = Keyboard.aSoftCodes['quote'];
Keyboard.aKeyCodes[Keyboard.ASCII['"']] = Keyboard.aSoftCodes['quote'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['`']] = Keyboard.aSoftCodes['`'];
Keyboard.aKeyCodes[Keyboard.ASCII['~']] = Keyboard.aSoftCodes['`'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.KEYCODE.SHIFT + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['shift'];
Keyboard.aKeyCodes[Keyboard.ASCII['\\']] = Keyboard.aSoftCodes['\\'];
Keyboard.aKeyCodes[Keyboard.ASCII['\\']] = Keyboard.aSoftCodes['\\']| (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.z] = Keyboard.aSoftCodes['z'];
Keyboard.aKeyCodes[Keyboard.ASCII.Z] = Keyboard.aSoftCodes['z'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.x] = Keyboard.aSoftCodes['x'];
Keyboard.aKeyCodes[Keyboard.ASCII.X] = Keyboard.aSoftCodes['x'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.c] = Keyboard.aSoftCodes['c'];
Keyboard.aKeyCodes[Keyboard.ASCII.C] = Keyboard.aSoftCodes['c'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.v] = Keyboard.aSoftCodes['v'];
Keyboard.aKeyCodes[Keyboard.ASCII.V] = Keyboard.aSoftCodes['v'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.b] = Keyboard.aSoftCodes['b'];
Keyboard.aKeyCodes[Keyboard.ASCII.B] = Keyboard.aSoftCodes['b'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.n] = Keyboard.aSoftCodes['n'];
Keyboard.aKeyCodes[Keyboard.ASCII.N] = Keyboard.aSoftCodes['n'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII.m] = Keyboard.aSoftCodes['m'];
Keyboard.aKeyCodes[Keyboard.ASCII.M] = Keyboard.aSoftCodes['m'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII[',']] = Keyboard.aSoftCodes[','];
Keyboard.aKeyCodes[Keyboard.ASCII['<']] = Keyboard.aSoftCodes[','] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['.']] = Keyboard.aSoftCodes['.'];
Keyboard.aKeyCodes[Keyboard.ASCII['>']] = Keyboard.aSoftCodes['.'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.ASCII['/']] = Keyboard.aSoftCodes['/'];
Keyboard.aKeyCodes[Keyboard.ASCII['?']] = Keyboard.aSoftCodes['/'] | (Keyboard.STATE.SHIFT << 8);
Keyboard.aKeyCodes[Keyboard.KEYCODE.SHIFT + Keyboard.KEYCODE.ONDOWN + Keyboard.KEYCODE.ONRIGHT] = Keyboard.aSoftCodes['right-shift'];
// TBD: 0x37 ('prtsc') goes here
Keyboard.aKeyCodes[Keyboard.KEYCODE.ALT + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['alt'];
Keyboard.aKeyCodes[Keyboard.ASCII[' ']] = Keyboard.aSoftCodes['space'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.CAPSLOCK+Keyboard.KEYCODE.ONDOWN]= Keyboard.aSoftCodes['caps-lock'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.F1 + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['f1'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.F2 + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['f2'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.F3 + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['f3'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.F4 + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['f4'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.F5 + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['f5'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.F6 + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['f6'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.F7 + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['f7'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.F8 + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['f8'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.F9 + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['f9'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.F10 + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['f10'];
// TBD: 0x45 ('num-lock') and 0x46 ('scroll-lock') go here
Keyboard.aKeyCodes[Keyboard.KEYCODE.HOME + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['num-home'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.UP + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['num-up'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.PGUP + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['num-pgup'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.LEFT + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['num-left'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.RIGHT + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['num-right'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.END + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['num-end'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.DOWN + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['num-down'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.PGDN + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['num-pgdn'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.INS + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['num-ins'];
Keyboard.aKeyCodes[Keyboard.KEYCODE.DEL + Keyboard.KEYCODE.ONDOWN] = Keyboard.aSoftCodes['num-del'];
// TBD for all keyboards: 'num-add', 'num-sub'
// TBD for 101-key keyboards: 'num-mul', 'num-div', 'num-enter', the stand-alone arrow keys, etc.
Keyboard.aKeyCodes[Keyboard.KEYCODE.FAKE_CTRLC] = Keyboard.aSoftCodes['c'] + (Keyboard.STATE.CTRL << 8);
Keyboard.aKeyCodes[Keyboard.KEYCODE.FAKE_CTRLBREAK] = Keyboard.aSoftCodes['scroll-lock'] + (Keyboard.STATE.CTRL << 8);
Keyboard.aKeyCodes[Keyboard.KEYCODE.FAKE_CTRLALTDEL] = Keyboard.aSoftCodes['num-del'] + (Keyboard.STATE.CTRL << 8) + (Keyboard.STATE.ALT << 16);
/**
* Options for keyEventSimulate()
*
* @enum {number}
*/
Keyboard.SIMCODE = {
KEYPRESS: 0,
KEYRELEASE: 1,
KEYEVENT: 2,
KEYTIMEOUT: 3,
AUTOCLEAR: 4
};
if (DEBUGGER) {
Keyboard.aSimCodeDescs = ["keyPress", "keyRelease", "keyEvent", "keyTimeout", "autoClear"];
}
/**
* 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.keyEvent(event, true);
};
control.onkeypress = function onKeyPressKeyboard(event) {
return kbd.keyPress(event);
};
control.onkeyup = function onKeyUpKeyboard(event) {
return kbd.keyEvent(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.keyPressSimulate(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] | 0x80);
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.setIRR(ChipSet.IRQ.KBD, 4);
};
/**
* 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 {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.setIRR(ChipSet.IRQ.KBD);
}
}
}
};
/**
* 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.
*
* QUESTION: In JavaScript, how do you query 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, too (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)
*
* An actual IBM keyboard will only buffer up to 20 scan codes, so we impose the same limit here.
*
* Just as 0xAA is a special scan code response to a software reset, 0xFF is a special scan code response
* to an internal buffer overrun. I try to simulate both.
*
* @this {Keyboard}
* @param {number} bScan
* @param {boolean} [fRepeat]
*/
Keyboard.prototype.addScanCode = function(bScan, fRepeat)
{
var bKey = bScan & 0x7f;
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 < 20) {
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.setIRR(ChipSet.IRQ.KBD);
}
this.findBinding(bKey, "key", fDown);
return;
}
if (this.abScanBuffer.length == 20) {
this.abScanBuffer.push(0xFF);
}
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 keyEvent()
* 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.keyEventSimulate(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.keyPressSimulate(ch);
}
if (this.sInjectBuffer.length > 0) {
setTimeout(function (kbd) {
return function onInjectKeyTimeout() {
kbd.injectKeysFromBuffer(msDelay);
};
}(this), msDelay);
}
};
/**
* keyEvent(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.keyEvent = function(event, fDown)
{
var fPass;
var fAutoClear = !fDown;
var keyCode = event.keyCode;
var keyCodeSim = keyCode;
if (fDown) this.prevKeyDown = keyCode;
var wCode = Keyboard.aKeyCodes[keyCode + Keyboard.KEYCODE.ONDOWN];
if (wCode) {
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.CAPSLOCK) {
/*
* FYI, CAPSLOCK 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.keyPressSimulate(keyCodeSim);
} else {
fAutoClear = false;
}
} else {
if (keyCode == Keyboard.KEYCODE.TAB || keyCode == Keyboard.KEYCODE.ESC || keyCode == Keyboard.KEYCODE.BS) {
/*
* 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 DELETE
* (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.keyPressSimulate(keyCodeSim) : false);
}
}
}
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 >= 0x41 && keyCode <= 0x5A) {
/*
* Convert "upper-case" letter combinations into "lower-case" combinations, so
* that keyEventSimulate() doesn't think it also needs to simulate a SHIFT key, too.
*/
keyCodeSim += 0x20;
}
// 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.keyEventSimulate(keyCodeSim, fDown, Keyboard.SIMCODE.KEYEVENT);
}
if (DEBUG) this.messageDebugger(/*(fDown?"\n":"") +*/ "key" + (fDown? "Down" : "Up") + "(" + keyCode + "): " + (fPass? "pass" : "consume"), Debugger.MESSAGE_KEYS);
return fPass;
};
/**
* keyPress(event)
*
* We've stopped relying on keyPress for keyboard emulation purposes, but it's still handy to hook and monitor
* when debugging.
*
* @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" DELETE keyEvent,
* so we must ALSO check for the DELETE key here, and again "consume" it. Ditto for TAB.
*
* In fact, 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 value from keyPressSimulate().
*/
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.keyPressSimulate(keyCode);
}
}
if (DEBUG) this.messageDebugger("keyPress(" + keyCode + "): " + (fPass? "pass" : "consume"), Debugger.MESSAGE_KEYS);
return fPass;
};
/**
* keyPressSimulate(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.keyPressSimulate = 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.keyEventSimulate(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.keyEventSimulate(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 onKeyPressSimulateTimeout() {
kbd.keyEventSimulate(keyCode, false, Keyboard.SIMCODE.KEYTIMEOUT);
};
}(this), msDelay);
if (DEBUG) this.messageDebugger("keyPressSimulate(" + keyCode + "): setTimeout()");
}
fSimulated = true;
}
if (DEBUG) this.messageDebugger("keyPressSimulate(" + keyCode + "): " + (fSimulated? "true" : "false"));
return fSimulated;
};
/**
* keyEventSimulate(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.keyEventSimulate = 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) {
/*
* Perhaps we're dealing with a CTRL variation of an alphabetic key; this won't
* affect non-CTRL-key combos like CR or LF, because they're defined in aKeyCodes,
* and this bit of code relieves us from having to explicitly define every CTRL-letter
* possibility in aKeyCodes.
*
* TODO: Support for CTRL-anything-else (as well as ALT-anything-else) is still TBD.
*/
if (keyCode >= 0x01 && keyCode <= 0x1A) {
keyCode += 0x40;
wCode = (Keyboard.aKeyCodes[keyCode] & 0xff) | (Keyboard.STATE.CTRL << 8);
}
}
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 == 0x0E) {
if ((this.bitsShift & (Keyboard.STATE.CTRL | Keyboard.STATE.ALT)) == (Keyboard.STATE.CTRL | Keyboard.STATE.ALT)) {
wCode = 0x53;
}
}
var abScanCodes = [];
abScanCodes.push((wCode & 0xff) | (fDown? 0 : 0x80));
while (wCode >>= 8) {
var bScan = 0;
var bShift = wCode & 0xff;
if (bShift == Keyboard.STATE.SHIFT) {
/*
* TODO: The fact that CAPSLOCK is currently set affects only letters....
*/
if (!(this.bitsShift & (Keyboard.STATE.SHIFT | Keyboard.STATE.RSHIFT | Keyboard.STATE.CAPSLOCK))) {
bScan = 0x2A;
}
} else if (bShift == Keyboard.STATE.CTRL) {
if (!(this.bitsShift & (Keyboard.STATE.CTRL | Keyboard.STATE.RCTRL))) {
bScan = 0x1D;
}
} else if (bShift == Keyboard.STATE.ALT) {
if (!(this.bitsShift & (Keyboard.STATE.ALT | Keyboard.STATE.RALT))) {
bScan = 0x38;
}
}
if (bScan) {
if (fDown)
abScanCodes.unshift(bScan);
else
abScanCodes.push(bScan | 0x80);
}
}
for (var i = 0; i < abScanCodes.length; i++) {
this.addScanCode(abScanCodes[i]);
}
fSimulated = true;
}
if (DEBUG && DEBUGGER) this.messageDebugger("keyEventSimulate(" + keyCode + "," + (fDown? "down" : "up") + "," + Keyboard.aSimCodeDescs[simCode] + "): " + (fSimulated? "true" : "false"));
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;