1397 lines
58 KiB
JavaScript
1397 lines
58 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");
|
|
}
|
|
|
|
/**
|
|
* Keyboard(parmsKbd)
|
|
*
|
|
* The Keyboard component can be configured with the following (parmsKbd) properties:
|
|
*
|
|
* model: model string; should be one of:
|
|
*
|
|
* us83
|
|
* us84
|
|
* us101
|
|
*
|
|
* Default is "us83" (US keyboard layout, 83 keys)
|
|
*
|
|
* 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()
|
|
*/
|
|
Keyboard.CMD = {};
|
|
Keyboard.CMD.RESET = 0xFF;
|
|
Keyboard.CMD.RESEND = 0xFE;
|
|
Keyboard.CMD.DEFAULT_ON = 0xF6;
|
|
Keyboard.CMD.DEFAULT_OFF = 0xF5;
|
|
Keyboard.CMD.ENABLE = 0xF4;
|
|
Keyboard.CMD.SETRATE = 0xF3;
|
|
Keyboard.CMD.ECHO = 0xEE;
|
|
Keyboard.CMD.SETLEDS = 0xED;
|
|
|
|
Keyboard.CMDRES = {};
|
|
Keyboard.CMDRES.OVERRUN = 0x00;
|
|
Keyboard.CMDRES.LOADTEST = 0x65; // this is an undocumented "LOAD MANUFACTURING TEST REQUEST" response code
|
|
Keyboard.CMDRES.BATSUCCESS = 0xAA; // Basic Assurance Test (BAT) completed successfully
|
|
Keyboard.CMDRES.ECHO = 0xEE;
|
|
Keyboard.CMDRES.BREAKPREFIX = 0xF0;
|
|
Keyboard.CMDRES.ACK = 0xFA;
|
|
Keyboard.CMDRES.BATFAIL = 0xFC; // Basic Assurance Test (BAT) failed
|
|
Keyboard.CMDRES.DIAGFAIL = 0xFD;
|
|
Keyboard.CMDRES.RESEND = 0xFE;
|
|
|
|
/*
|
|
* Keyboard keyCodes I must pay particular attention to...
|
|
*/
|
|
Keyboard.KEYCODE = {};
|
|
Keyboard.KEYCODE.DELETE = 0x08;
|
|
Keyboard.KEYCODE.TAB = 0x09;
|
|
Keyboard.KEYCODE.LF = 0x0A;
|
|
Keyboard.KEYCODE.CR = 0x0D;
|
|
Keyboard.KEYCODE.SHIFT = 0x10; // I map this to CHARCODE_LSHIFT
|
|
Keyboard.KEYCODE.CONTROL = 0x11;
|
|
Keyboard.KEYCODE.ALT = 0x12;
|
|
Keyboard.KEYCODE.CAPSLOCK = 0x14;
|
|
Keyboard.KEYCODE.ESC = 0x1B; // NOTE: for some reason, this arrive only via keyDown/keyUp, not keyPress
|
|
Keyboard.KEYCODE.COMMAND = 0x5B;
|
|
|
|
Keyboard.KEYCODE.F1 = 0x70;
|
|
Keyboard.KEYCODE.F2 = 0x71;
|
|
Keyboard.KEYCODE.F3 = 0x72;
|
|
Keyboard.KEYCODE.F4 = 0x73;
|
|
Keyboard.KEYCODE.F5 = 0x74;
|
|
Keyboard.KEYCODE.F6 = 0x75;
|
|
Keyboard.KEYCODE.F7 = 0x76;
|
|
Keyboard.KEYCODE.F8 = 0x77;
|
|
Keyboard.KEYCODE.F9 = 0x78;
|
|
Keyboard.KEYCODE.F10 = 0x79;
|
|
Keyboard.KEYCODE.L_ARROW = 0x25;
|
|
Keyboard.KEYCODE.U_ARROW = 0x26;
|
|
Keyboard.KEYCODE.R_ARROW = 0x27;
|
|
Keyboard.KEYCODE.D_ARROW = 0x28;
|
|
|
|
/*
|
|
* The following charCodes are the same as the corresponding keyCodes
|
|
*/
|
|
Keyboard.CHARCODE = {};
|
|
Keyboard.CHARCODE.DELETE = Keyboard.KEYCODE.DELETE;
|
|
Keyboard.CHARCODE.TAB = Keyboard.KEYCODE.TAB;
|
|
Keyboard.CHARCODE.LF = Keyboard.KEYCODE.LF;
|
|
Keyboard.CHARCODE.CR = Keyboard.KEYCODE.CR;
|
|
Keyboard.CHARCODE.ESC = Keyboard.KEYCODE.ESC;
|
|
|
|
/*
|
|
* The following charCodes are NOT the same as the corresponding keyCodes, hence the bias (CHARCODE.PSEUDO).
|
|
* I've deliberately chosen a bias that still produces values in the byte range (0x00-0xFF) for all the "shift"
|
|
* keys and will therefore fit into individual bytes of aCharCodes, but which shouldn't conflict with any actual,
|
|
* type-able keys.
|
|
*/
|
|
Keyboard.CHARCODE.PSEUDO = 0xE0;
|
|
Keyboard.CHARCODE.RSHIFT = Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.LSHIFT = Keyboard.KEYCODE.SHIFT + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.CTRL = Keyboard.KEYCODE.CONTROL + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.ALT = Keyboard.KEYCODE.ALT + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.CAPSLOCK = Keyboard.KEYCODE.CAPSLOCK + Keyboard.CHARCODE.PSEUDO;
|
|
|
|
Keyboard.CHARCODE.F1 = Keyboard.KEYCODE.F1 + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.F2 = Keyboard.KEYCODE.F2 + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.F3 = Keyboard.KEYCODE.F3 + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.F4 = Keyboard.KEYCODE.F4 + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.F5 = Keyboard.KEYCODE.F5 + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.F6 = Keyboard.KEYCODE.F6 + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.F7 = Keyboard.KEYCODE.F7 + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.F8 = Keyboard.KEYCODE.F8 + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.F9 = Keyboard.KEYCODE.F9 + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.F10 = Keyboard.KEYCODE.F10 + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.L_ARROW = Keyboard.KEYCODE.L_ARROW + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.U_ARROW = Keyboard.KEYCODE.U_ARROW + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.R_ARROW = Keyboard.KEYCODE.R_ARROW + Keyboard.CHARCODE.PSEUDO;
|
|
Keyboard.CHARCODE.D_ARROW = Keyboard.KEYCODE.D_ARROW + Keyboard.CHARCODE.PSEUDO;
|
|
|
|
/*
|
|
* TODO: Looking at these two random definitions reminds me I need to do a COMPREHENSIVE review of all keyCode/charCode
|
|
* processing.
|
|
*/
|
|
Keyboard.CHARCODE.CTRLBREAK = 0xFE;
|
|
Keyboard.CHARCODE.CTRLALTDEL= 0xFF;
|
|
|
|
/*
|
|
* Other common character codes
|
|
*/
|
|
Keyboard.CHARCODE.CTRLC = 0x03;
|
|
Keyboard.CHARCODE.CTRLO = 0x0F;
|
|
|
|
/*
|
|
* These are "shift key" states stored in bitsShift
|
|
*/
|
|
Keyboard.STATE = {};
|
|
Keyboard.STATE.LSHIFT = 0x01;
|
|
Keyboard.STATE.RSHIFT = 0x02;
|
|
Keyboard.STATE.CTRL = 0x04;
|
|
Keyboard.STATE.ALT = 0x08;
|
|
Keyboard.STATE.CAPSLOCK = 0x10;
|
|
Keyboard.STATE.COMMAND = 0x20;
|
|
Keyboard.STATE.SIMULATE = (Keyboard.STATE.LSHIFT | Keyboard.STATE.RSHIFT | Keyboard.STATE.CTRL | Keyboard.STATE.ALT);
|
|
|
|
Keyboard.SIMCODE = {};
|
|
Keyboard.SIMCODE.KEYPRESS = 0;
|
|
Keyboard.SIMCODE.KEYRELEASE = 1;
|
|
Keyboard.SIMCODE.KEYEVENT = 2;
|
|
Keyboard.SIMCODE.KEYTIMEOUT = 3;
|
|
Keyboard.SIMCODE.AUTOCLEAR = 4;
|
|
|
|
if (DEBUGGER) {
|
|
Keyboard.aSimCodeDescs = ["keyPress", "keyRelease", "keyEvent", "keyTimeout", "autoClear"];
|
|
}
|
|
|
|
Keyboard.aSoftCodes = {
|
|
'esc': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6, '6': 7, '7': 8, '8': 9, '9': 10, '0': 11, '-': 12, '=': 13, 'backspace': 14,
|
|
'tab': 15, 'q': 16, 'w': 17, 'e': 18, 'r': 19, 't': 20, 'y': 21, 'u': 22, 'i': 23, 'o': 24, 'p': 25, '[': 26, ']': 27, 'enter': 28,
|
|
'ctrl': 29, 'a': 30, 's': 31, 'd': 32, 'f': 33, 'g': 34, 'h': 35, 'j': 36, 'k': 37, 'l': 38, ';': 39, 'squote': 40, 'bquote': 41,
|
|
'lshift': 42, 'bslash': 43, 'z': 44, 'x': 45, 'c': 46, 'v': 47, 'b': 48, 'n': 49, 'm': 50, ',': 51, '.': 52, '/': 53, 'rshift': 54, 'prtsc': 55,
|
|
'alt': 56, 'space': 57, 'caps-lock': 58, 'f1': 59, 'f2': 60, 'f3': 61, 'f4': 62, 'f5': 63, 'f6': 64, 'f7': 65, 'f8': 66, 'f9': 67, 'f10': 68, 'num-lock': 69, 'scroll-lock': 70,
|
|
'home': 71, 'up-arrow': 72, 'page-up': 73, 'num-minus': 74, 'left-arrow': 75, 'center': 76, 'right-arrow': 77, 'num-plus': 78, 'end': 79, 'down-arrow': 80, 'page-down': 81,
|
|
'ins': 82, 'del': 83
|
|
};
|
|
|
|
Keyboard.aButtonCodes = {
|
|
'tab': Keyboard.CHARCODE.TAB,
|
|
'esc': Keyboard.CHARCODE.ESC,
|
|
'rshift': Keyboard.CHARCODE.RSHIFT,
|
|
'lshift': Keyboard.CHARCODE.LSHIFT,
|
|
'ctrl': Keyboard.CHARCODE.CTRL,
|
|
'alt': Keyboard.CHARCODE.ALT,
|
|
'caps-lock': Keyboard.CHARCODE.CAPSLOCK,
|
|
'f1': Keyboard.CHARCODE.F1,
|
|
'f2': Keyboard.CHARCODE.F2,
|
|
'f3': Keyboard.CHARCODE.F3,
|
|
'f4': Keyboard.CHARCODE.F4,
|
|
'f5': Keyboard.CHARCODE.F5,
|
|
'f6': Keyboard.CHARCODE.F6,
|
|
'f7': Keyboard.CHARCODE.F7,
|
|
'f8': Keyboard.CHARCODE.F8,
|
|
'f9': Keyboard.CHARCODE.F9,
|
|
'f10': Keyboard.CHARCODE.F10,
|
|
'left-arrow': Keyboard.CHARCODE.L_ARROW,
|
|
'up-arrow': Keyboard.CHARCODE.U_ARROW,
|
|
'right-arrow': Keyboard.CHARCODE.R_ARROW,
|
|
'down-arrow': Keyboard.CHARCODE.D_ARROW,
|
|
/*
|
|
* These last few bindings are for convenience (common key combinations that can be bound to a single control)
|
|
*/
|
|
'ctrl-c': Keyboard.CHARCODE.CTRLC,
|
|
'ctrl-break': Keyboard.CHARCODE.CTRLBREAK,
|
|
'ctrl-alt-del': Keyboard.CHARCODE.CTRLALTDEL
|
|
};
|
|
|
|
/*
|
|
* This array is used by keyEventSimulate() to lookup a given charCode and convert it to a scan code
|
|
* (lower byte) plus any required shift key states (upper bytes).
|
|
*
|
|
* Using charCodes (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.
|
|
*
|
|
* There are still a few times that I call keyEventSimulate() from keyEvent(), and for those occasions,
|
|
* I create a pseudo-charCode value by adding CHARCODE.PSEUDO (0xE0) to the keyCode value, to avoid any
|
|
* confusion with real charCodes:
|
|
*
|
|
* CHARCODE_LSHIFT (originally 0x10, which also looks like CTRL-P, so converted to 0xF0)
|
|
* CHARCODE_CTRL (originally 0x11, which also looks like CTRL-Q, so converted to 0xF1)
|
|
* CHARCODE_ALT (originally 0x12, which also looks like CTRL-R, so converted to 0xF2)
|
|
* CHARCODE_CAPSLOCK (originally 0x14, which also looks like CTRL-T, so converted to 0xF4)
|
|
*
|
|
* Again, as things currently stand, iOS devices should never generate the above charCodes, so any emulated
|
|
* software that relies detecting on shift-key state changes will not work on those devices.
|
|
*/
|
|
Keyboard.aCharCodes = [];
|
|
Keyboard.aCharCodes[0x20] = 0x39; // SPACE
|
|
Keyboard.aCharCodes[0x31] = 0x02; // 1
|
|
Keyboard.aCharCodes[0x21] = 0x02 | (Keyboard.CHARCODE.LSHIFT << 8); // !
|
|
Keyboard.aCharCodes[0x32] = 0x03; // 2
|
|
Keyboard.aCharCodes[0x40] = 0x03 | (Keyboard.CHARCODE.LSHIFT << 8); // @
|
|
Keyboard.aCharCodes[0x33] = 0x04; // 3
|
|
Keyboard.aCharCodes[0x23] = 0x04 | (Keyboard.CHARCODE.LSHIFT << 8); // #
|
|
Keyboard.aCharCodes[0x34] = 0x05; // 4
|
|
Keyboard.aCharCodes[0x24] = 0x05 | (Keyboard.CHARCODE.LSHIFT << 8); // $
|
|
Keyboard.aCharCodes[0x35] = 0x06; // 5
|
|
Keyboard.aCharCodes[0x25] = 0x06 | (Keyboard.CHARCODE.LSHIFT << 8); // %
|
|
Keyboard.aCharCodes[0x36] = 0x07; // 6
|
|
Keyboard.aCharCodes[0x5E] = 0x07 | (Keyboard.CHARCODE.LSHIFT << 8); // ^
|
|
Keyboard.aCharCodes[0x37] = 0x08; // 7
|
|
Keyboard.aCharCodes[0x26] = 0x08 | (Keyboard.CHARCODE.LSHIFT << 8); // &
|
|
Keyboard.aCharCodes[0x38] = 0x09; // 8
|
|
Keyboard.aCharCodes[0x2A] = 0x09 | (Keyboard.CHARCODE.LSHIFT << 8); // *
|
|
Keyboard.aCharCodes[0x39] = 0x0A; // 9
|
|
Keyboard.aCharCodes[0x28] = 0x0A | (Keyboard.CHARCODE.LSHIFT << 8); // (
|
|
Keyboard.aCharCodes[0x30] = 0x0B; // 0
|
|
Keyboard.aCharCodes[0x29] = 0x0B | (Keyboard.CHARCODE.LSHIFT << 8); // )
|
|
Keyboard.aCharCodes[0x2D] = 0x0C; // -
|
|
Keyboard.aCharCodes[0x5F] = 0x0C | (Keyboard.CHARCODE.LSHIFT << 8); // _
|
|
Keyboard.aCharCodes[0x3D] = 0x0D; // =
|
|
Keyboard.aCharCodes[0x2B] = 0x0D | (Keyboard.CHARCODE.LSHIFT << 8); // +
|
|
Keyboard.aCharCodes[0x71] = 0x10; // q
|
|
Keyboard.aCharCodes[0x51] = 0x10 | (Keyboard.CHARCODE.LSHIFT << 8); // Q
|
|
Keyboard.aCharCodes[0x77] = 0x11; // w
|
|
Keyboard.aCharCodes[0x57] = 0x11 | (Keyboard.CHARCODE.LSHIFT << 8); // W
|
|
Keyboard.aCharCodes[0x65] = 0x12; // e
|
|
Keyboard.aCharCodes[0x45] = 0x12 | (Keyboard.CHARCODE.LSHIFT << 8); // E
|
|
Keyboard.aCharCodes[0x72] = 0x13; // r
|
|
Keyboard.aCharCodes[0x52] = 0x13 | (Keyboard.CHARCODE.LSHIFT << 8); // R
|
|
Keyboard.aCharCodes[0x74] = 0x14; // t
|
|
Keyboard.aCharCodes[0x54] = 0x14 | (Keyboard.CHARCODE.LSHIFT << 8); // T
|
|
Keyboard.aCharCodes[0x79] = 0x15; // y
|
|
Keyboard.aCharCodes[0x59] = 0x15 | (Keyboard.CHARCODE.LSHIFT << 8); // Y
|
|
Keyboard.aCharCodes[0x75] = 0x16; // u
|
|
Keyboard.aCharCodes[0x55] = 0x16 | (Keyboard.CHARCODE.LSHIFT << 8); // U
|
|
Keyboard.aCharCodes[0x69] = 0x17; // i
|
|
Keyboard.aCharCodes[0x49] = 0x17 | (Keyboard.CHARCODE.LSHIFT << 8); // I
|
|
Keyboard.aCharCodes[0x6F] = 0x18; // o
|
|
Keyboard.aCharCodes[0x4F] = 0x18 | (Keyboard.CHARCODE.LSHIFT << 8); // O
|
|
Keyboard.aCharCodes[0x70] = 0x19; // p
|
|
Keyboard.aCharCodes[0x50] = 0x19 | (Keyboard.CHARCODE.LSHIFT << 8); // P
|
|
Keyboard.aCharCodes[0x5B] = 0x1A; // [
|
|
Keyboard.aCharCodes[0x7B] = 0x1A | (Keyboard.CHARCODE.LSHIFT << 8); // {
|
|
Keyboard.aCharCodes[0x5D] = 0x1B; // ]
|
|
Keyboard.aCharCodes[0x7D] = 0x1B | (Keyboard.CHARCODE.LSHIFT << 8); // }
|
|
Keyboard.aCharCodes[0x61] = 0x1E; // a
|
|
Keyboard.aCharCodes[0x41] = 0x1E | (Keyboard.CHARCODE.LSHIFT << 8); // A
|
|
Keyboard.aCharCodes[0x73] = 0x1F; // s
|
|
Keyboard.aCharCodes[0x53] = 0x1F | (Keyboard.CHARCODE.LSHIFT << 8); // S
|
|
Keyboard.aCharCodes[0x64] = 0x20; // d
|
|
Keyboard.aCharCodes[0x44] = 0x20 | (Keyboard.CHARCODE.LSHIFT << 8); // D
|
|
Keyboard.aCharCodes[0x66] = 0x21; // f
|
|
Keyboard.aCharCodes[0x46] = 0x21 | (Keyboard.CHARCODE.LSHIFT << 8); // F
|
|
Keyboard.aCharCodes[0x67] = 0x22; // g
|
|
Keyboard.aCharCodes[0x47] = 0x22 | (Keyboard.CHARCODE.LSHIFT << 8); // G
|
|
Keyboard.aCharCodes[0x68] = 0x23; // h
|
|
Keyboard.aCharCodes[0x48] = 0x23 | (Keyboard.CHARCODE.LSHIFT << 8); // H
|
|
Keyboard.aCharCodes[0x6A] = 0x24; // j
|
|
Keyboard.aCharCodes[0x4A] = 0x24 | (Keyboard.CHARCODE.LSHIFT << 8); // J
|
|
Keyboard.aCharCodes[0x6B] = 0x25; // k
|
|
Keyboard.aCharCodes[0x4B] = 0x25 | (Keyboard.CHARCODE.LSHIFT << 8); // K
|
|
Keyboard.aCharCodes[0x6C] = 0x26; // l
|
|
Keyboard.aCharCodes[0x4C] = 0x26 | (Keyboard.CHARCODE.LSHIFT << 8); // L
|
|
Keyboard.aCharCodes[0x3B] = 0x27; // ;
|
|
Keyboard.aCharCodes[0x3A] = 0x27 | (Keyboard.CHARCODE.LSHIFT << 8); // :
|
|
Keyboard.aCharCodes[0x27] = 0x28; // '
|
|
Keyboard.aCharCodes[0x22] = 0x28 | (Keyboard.CHARCODE.LSHIFT << 8); // "
|
|
Keyboard.aCharCodes[0x60] = 0x29; // `
|
|
Keyboard.aCharCodes[0x7E] = 0x29 | (Keyboard.CHARCODE.LSHIFT << 8); // ~
|
|
Keyboard.aCharCodes[0x5C] = 0x2B; // \
|
|
Keyboard.aCharCodes[0x7C] = 0x2B | (Keyboard.CHARCODE.LSHIFT << 8); // |
|
|
Keyboard.aCharCodes[0x7A] = 0x2C; // z
|
|
Keyboard.aCharCodes[0x5A] = 0x2C | (Keyboard.CHARCODE.LSHIFT << 8); // Z
|
|
Keyboard.aCharCodes[0x78] = 0x2D; // x
|
|
Keyboard.aCharCodes[0x58] = 0x2D | (Keyboard.CHARCODE.LSHIFT << 8); // X
|
|
Keyboard.aCharCodes[0x63] = 0x2E; // c
|
|
Keyboard.aCharCodes[0x43] = 0x2E | (Keyboard.CHARCODE.LSHIFT << 8); // C
|
|
Keyboard.aCharCodes[0x76] = 0x2F; // v
|
|
Keyboard.aCharCodes[0x56] = 0x2F | (Keyboard.CHARCODE.LSHIFT << 8); // V
|
|
Keyboard.aCharCodes[0x62] = 0x30; // b
|
|
Keyboard.aCharCodes[0x42] = 0x30 | (Keyboard.CHARCODE.LSHIFT << 8); // B
|
|
Keyboard.aCharCodes[0x6E] = 0x31; // n
|
|
Keyboard.aCharCodes[0x4E] = 0x31 | (Keyboard.CHARCODE.LSHIFT << 8); // N
|
|
Keyboard.aCharCodes[0x6D] = 0x32; // m
|
|
Keyboard.aCharCodes[0x4D] = 0x32 | (Keyboard.CHARCODE.LSHIFT << 8); // M
|
|
Keyboard.aCharCodes[0x2C] = 0x33; // ,
|
|
Keyboard.aCharCodes[0x3C] = 0x33 | (Keyboard.CHARCODE.LSHIFT << 8); // <
|
|
Keyboard.aCharCodes[0x2E] = 0x34; // .
|
|
Keyboard.aCharCodes[0x3E] = 0x34 | (Keyboard.CHARCODE.LSHIFT << 8); // >
|
|
Keyboard.aCharCodes[0x2F] = 0x35; // /
|
|
Keyboard.aCharCodes[0x3F] = 0x35 | (Keyboard.CHARCODE.LSHIFT << 8); // ?
|
|
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.DELETE] = 0x0E;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.TAB] = 0x0F;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.CR] = 0x1C;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.ESC] = 0x01;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.LSHIFT] = 0x2A;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.RSHIFT] = 0x36;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.CTRL] = 0x1D;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.ALT] = 0x38;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.CAPSLOCK] = 0x3A;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.F1] = 0x3B;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.F2] = 0x3C;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.F3] = 0x3D;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.F4] = 0x3E;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.F5] = 0x3F;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.F6] = 0x40;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.F7] = 0x41;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.F8] = 0x42;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.F9] = 0x43;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.F10] = 0x44;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.L_ARROW] = 0x4B;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.U_ARROW] = 0x48;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.R_ARROW] = 0x4D;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.D_ARROW] = 0x50;
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.CTRLBREAK]= 0x46 + (Keyboard.CHARCODE.CTRL << 8);
|
|
Keyboard.aCharCodes[Keyboard.CHARCODE.CTRLALTDEL]=0x53 + (Keyboard.CHARCODE.CTRL << 8) + (Keyboard.CHARCODE.ALT << 16);
|
|
|
|
/**
|
|
* 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 a way 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, charCode) {
|
|
return function onClickKeyboard(event) {
|
|
if (DEBUG) kbd.println(sKey + " clicked");
|
|
if (kbd.cpu) kbd.cpu.setFocus();
|
|
return !kbd.keyPressSimulate(charCode);
|
|
};
|
|
}(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.cmp = cmp;
|
|
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"));
|
|
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", true);
|
|
this.abScanBuffer = [Keyboard.CMDRES.BATSUCCESS];
|
|
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, true);
|
|
/*
|
|
* 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, true);
|
|
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 " + 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 charCode, 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 (DEBUG) this.messageDebugger("scan code " + str.toHexByte(bScan) + " redundant");
|
|
return;
|
|
}
|
|
this.aScanCodesActive[bKey] = fDown;
|
|
this.messageDebugger("scan code " + 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.mhz) msDelay /= this.cpu.mhz;
|
|
return msDelay;
|
|
};
|
|
|
|
/**
|
|
* autoClear(notCharCode)
|
|
*
|
|
* @this {Keyboard}
|
|
* @param {number} [notCharCode]
|
|
*/
|
|
Keyboard.prototype.autoClear = function(notCharCode)
|
|
{
|
|
if (this.prevCharDown && (notCharCode === undefined || notCharCode != this.prevCharDown)) {
|
|
if (DEBUG) this.messageDebugger("autoClear(" + str.toHexWord(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 if called for a keyDown event, false if called 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;
|
|
|
|
if (fDown) this.prevKeyDown = keyCode;
|
|
|
|
if (keyCode + Keyboard.CHARCODE.PSEUDO == Keyboard.CHARCODE.LSHIFT) {
|
|
this.bitsShift &= ~Keyboard.STATE.LSHIFT;
|
|
if (fDown) this.bitsShift |= Keyboard.STATE.LSHIFT;
|
|
keyCode += Keyboard.CHARCODE.PSEUDO;
|
|
fAutoClear = false;
|
|
}
|
|
else if (keyCode + Keyboard.CHARCODE.PSEUDO == Keyboard.CHARCODE.CTRL) {
|
|
this.bitsShift &= ~Keyboard.STATE.CTRL;
|
|
if (fDown) this.bitsShift |= Keyboard.STATE.CTRL;
|
|
keyCode += Keyboard.CHARCODE.PSEUDO;
|
|
fAutoClear = false;
|
|
}
|
|
else if (keyCode + Keyboard.CHARCODE.PSEUDO == Keyboard.CHARCODE.ALT) {
|
|
this.bitsShift &= ~Keyboard.STATE.ALT;
|
|
if (fDown) this.bitsShift |= Keyboard.STATE.ALT;
|
|
keyCode += Keyboard.CHARCODE.PSEUDO;
|
|
fAutoClear = false;
|
|
}
|
|
else if (keyCode + Keyboard.CHARCODE.PSEUDO == Keyboard.CHARCODE.CAPSLOCK) {
|
|
/*
|
|
* FYI, this 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).
|
|
*/
|
|
this.bitsShift &= ~Keyboard.STATE.CAPSLOCK;
|
|
if (fDown) this.bitsShift |= Keyboard.STATE.CAPSLOCK;
|
|
keyCode += Keyboard.CHARCODE.PSEUDO;
|
|
fPass = this.keyPressSimulate(keyCode);
|
|
}
|
|
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;
|
|
}
|
|
else if (keyCode == Keyboard.KEYCODE.TAB || keyCode == Keyboard.KEYCODE.ESC || keyCode == Keyboard.KEYCODE.DELETE) {
|
|
/*
|
|
* HACK for simulating Ctrl-Break using Ctrl-Del (Mac) / Ctrl-Backspace (Windows)
|
|
*/
|
|
if (keyCode == Keyboard.KEYCODE.DELETE && (this.bitsShift & (Keyboard.STATE.CTRL|Keyboard.STATE.ALT)) == Keyboard.STATE.CTRL) {
|
|
keyCode = Keyboard.CHARCODE.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(keyCode) : false);
|
|
}
|
|
else {
|
|
/*
|
|
* Function keys, arrow keys, etc, should fall into the next category, independent of
|
|
* whatever modifier keys (eg, ALT, CTRL, etc) may also be pressed.
|
|
*/
|
|
if (Keyboard.aCharCodes[keyCode + Keyboard.CHARCODE.PSEUDO] !== undefined) {
|
|
keyCode += Keyboard.CHARCODE.PSEUDO;
|
|
}
|
|
/*
|
|
* 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.
|
|
*/
|
|
keyCode += 0x20;
|
|
}
|
|
// this.messageDebugger("ALT event: keyCode: 0x" + str.toHexWord(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(keyCode, fDown, Keyboard.SIMCODE.KEYEVENT);
|
|
}
|
|
|
|
if (DEBUG) this.messageDebugger(/*(fDown?"\n":"") +*/ "key" + (fDown ? "Down" : "Up") + "(" + str.toHexWord(keyCode) + "): " + (fPass ? "pass" : "consume"));
|
|
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;
|
|
/*
|
|
* Browser-independent charCode extraction...
|
|
*/
|
|
event = event || window.event;
|
|
var charCode = event.which || event.keyCode;
|
|
|
|
/*
|
|
* Let's stop any injection currently in progress, too
|
|
*/
|
|
this.sInjectBuffer = "";
|
|
|
|
if (charCode == Keyboard.CHARCODE.DELETE || charCode == Keyboard.CHARCODE.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 (event.altKey) this.messageDebugger("ALT press: charCode: 0x" + str.toHexWord(charCode));
|
|
if (this.bitsShift & (Keyboard.STATE.CTRL | Keyboard.STATE.ALT))
|
|
fPass = false;
|
|
else
|
|
fPass = !this.keyPressSimulate(charCode);
|
|
}
|
|
}
|
|
|
|
if (DEBUG) this.messageDebugger("keyPress(0x" + str.toHexWord(charCode) + "): " + (fPass ? "pass" : "consume"));
|
|
return fPass;
|
|
};
|
|
|
|
/**
|
|
* keyPressSimulate(charCode)
|
|
*
|
|
* @this {Keyboard}
|
|
* @param {number} charCode
|
|
* @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(charCode, fQuickRelease)
|
|
{
|
|
var fSimulated = false;
|
|
|
|
/*
|
|
* Auto-clear any previous down key EXCEPT for charCode (because it may be held and repeating).
|
|
*/
|
|
this.autoClear(charCode);
|
|
|
|
if (this.keyEventSimulate(charCode, 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(charCode, false, Keyboard.SIMCODE.KEYRELEASE);
|
|
}
|
|
else {
|
|
var fRepeat = false;
|
|
if (this.aKeyTimers[charCode]) {
|
|
clearTimeout(this.aKeyTimers[charCode]);
|
|
fRepeat = true;
|
|
}
|
|
var msDelay = this.calcReleaseDelay(fRepeat);
|
|
this.aKeyTimers[this.prevCharDown = charCode] = setTimeout(function (kbd) {
|
|
return function onKeyPressSimulateTimeout() {
|
|
kbd.keyEventSimulate(charCode, false, Keyboard.SIMCODE.KEYTIMEOUT);
|
|
};
|
|
}(this), msDelay);
|
|
if (DEBUG) this.messageDebugger("keyPressSimulate(0x" + str.toHexWord(charCode) + "): setTimeout()");
|
|
}
|
|
fSimulated = true;
|
|
}
|
|
if (DEBUG) this.messageDebugger("keyPressSimulate(0x" + str.toHexWord(charCode) + "): " + (fSimulated ? "true" : "false"));
|
|
return fSimulated;
|
|
};
|
|
|
|
/**
|
|
* keyEventSimulate(charCode, fDown, simCode)
|
|
*
|
|
* @this {Keyboard}
|
|
* @param {number} charCode
|
|
* @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(charCode, fDown, simCode)
|
|
{
|
|
var fSimulated = false;
|
|
|
|
if (!fDown) {
|
|
this.aKeyTimers[charCode] = null;
|
|
if (this.prevCharDown == charCode) this.prevCharDown = 0;
|
|
}
|
|
|
|
var wCode = Keyboard.aCharCodes[charCode];
|
|
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 aCharCodes,
|
|
* and this bit of code relieves us from having to explicitly define every CTRL-letter
|
|
* possibility in aCharCodes.
|
|
*
|
|
* TODO: Support for CTRL-anything-else (as well as ALT-anything-else) is still TBD.
|
|
*/
|
|
if (charCode >= 0x01 && charCode <= 0x1A) {
|
|
charCode += 0x40;
|
|
wCode = (Keyboard.aCharCodes[charCode] & 0xff) | (Keyboard.CHARCODE.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 bShiftCode = wCode & 0xff;
|
|
if (bShiftCode == Keyboard.CHARCODE.LSHIFT) {
|
|
if (!(this.bitsShift & (Keyboard.STATE.LSHIFT | Keyboard.STATE.CAPSLOCK))) {
|
|
bScan = 0x2A;
|
|
}
|
|
} else if (bShiftCode == Keyboard.CHARCODE.RSHIFT) {
|
|
if (!(this.bitsShift & (Keyboard.STATE.RSHIFT | Keyboard.STATE.CAPSLOCK))) {
|
|
bScan = 0x36;
|
|
}
|
|
} else if (bShiftCode == Keyboard.CHARCODE.CTRL) {
|
|
if (!(this.bitsShift & Keyboard.STATE.CTRL)) {
|
|
bScan = 0x1D;
|
|
}
|
|
} else if (bShiftCode == Keyboard.CHARCODE.ALT) {
|
|
if (!(this.bitsShift & Keyboard.STATE.ALT)) {
|
|
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(0x" + str.toHexWord(charCode) + "," + (fDown ? "down" : "up") + "," + Keyboard.aSimCodeDescs[simCode] + "): " + (fSimulated ? "true" : "false"));
|
|
return fSimulated;
|
|
};
|
|
|
|
/**
|
|
* messageDebugger(sMessage, fPort)
|
|
*
|
|
* 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 {boolean} [fPort] is true if the message is port-related, false if not
|
|
*/
|
|
Keyboard.prototype.messageDebugger = function(sMessage, fPort)
|
|
{
|
|
if (DEBUGGER && this.dbg) {
|
|
if (this.dbg.messageEnabled(Debugger.MESSAGE_KBD | (fPort ? Debugger.MESSAGE_PORT : 0))) {
|
|
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;
|