/**
* @fileoverview Implements the PCx86 Keyboard component.
* @author Jeff Parsons
* @copyright © Jeff Parsons 2012-2016
*
* This file is part of PCjs, a computer emulation software project at .
*
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCjs. If not,
* see .
*
* You are required to include the above copyright notice in every modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* .
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of PCjs
* for purposes of the GNU General Public License, and the author does not claim any copyright
* as to their contents.
*/
"use strict";
if (NODE) {
var str = require("../../shared/lib/strlib");
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var Keys = require("../../shared/lib/keys");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var ChipSet = require("./chipset");
var CPU = require("./cpu");
}
/**
* Keyboard(parmsKbd)
*
* The Keyboard component can be configured with the following (parmsKbd) properties:
*
* model: keyboard model string, which must match one of the values listed in Keyboard.MODELS:
*
* "US83" (default)
* "US84"
* "US101"
*
* Its main purpose is to receive binding requests for various keyboard events, and to use those events
* to simulate the PC's keyboard hardware.
*
* @constructor
* @extends Component
* @param {Object} parmsKbd
*/
function Keyboard(parmsKbd)
{
Component.call(this, "Keyboard", parmsKbd, Keyboard, Messages.KEYBOARD);
this.setModel(parmsKbd['model']);
this.fMobile = web.isMobile();
this.fMSIE = web.isUserAgent("MSIE");
this.printMessage("mobile keyboard support: " + (this.fMobile? "true" : "false"));
/*
* This is count of the number of "soft keyboard" keys present. At the moment, its only
* purpose is to signal findBinding() whether to waste any time looking for SOFTCODE matches.
*/
this.cSoftCodes = 0;
/*
* Updated by onFocusChange()
*/
this.fHasFocus = true;
/*
* This is true whenever the physical Escape key is disabled (eg, by pointer locking code),
* giving us the opportunity to map a different physical key to machine's virtual Escape key.
*/
this.fEscapeDisabled = false;
/*
* This is set whenever we notice a discrepancy between our internal CAPS_LOCK state and its
* apparent state; we check whenever aKeysActive has been emptied.
*/
this.fToggleCapsLock = false;
/*
* New unified approach to key event processing: When we process a key on the "down" event,
* we check the aKeysActive array: if the key is already active, do nothing; otherwise, insert
* it into the table, generate the "make" scan code(s), and set a timeout for "repeat" if it's
* a repeatable key (most are).
*
* Similarly, when a key goes "up", if it's already not active, do nothing; otherwise, generate
* the "break" scan code(s), cancel any pending timeout, and remove it from the active key table.
*
* If a "press" event is received, then if the key is already active, remove it and (re)insert
* it at the head of the table, generate the "make" scan code(s), set nRepeat to -1, and set a
* timeout for "break".
*
* This requires an aKeysActive array that keeps track of the status of every active key; only the
* first entry in the array is allowed to repeat. Each entry is a key object with the following
* properties:
*
* simCode: our simulated keyCode from onKeyDown, onKeyUp, or onKeyPress
* fDown: next state to simulate (true for down, false for up)
* nRepeat: > 0 if timer should generate more "make" scan code(s), -1 for "break" scan code(s)
* timer: timer for next key operation, if any
*
* Keys are inserted at the head of aKeysActive, using splice(0, 0, key), but not before zeroing
* nRepeat of any repeating key that already occupies the head (index 0), so that at most only one
* key (ie, the most recent) will ever be in a repeating state.
*
* IBM PC keyboard repeat behavior: when pressing CTRL, then C, and then releasing CTRL while still
* holding C, the repeated CTRL_C characters turn into 'c' characters. We emulate that behavior.
* However, when pressing C, then CTRL, all repeating stops: not a single CTRL_C is generated, and
* even if the CTRL is released before the C, no more more 'c' characters are generated either.
* We do NOT fully emulate that behavior -- we DO stop the repeating, but we also generate one CTRL_C.
* More investigation is required, because I need to confirm whether the IBM keyboard automatically
* "breaks" all non-shift keys before it "makes" the CTRL.
*/
this.aKeysActive = [];
this.msAutoRepeat = 500;
this.msNextRepeat = 100;
this.msAutoRelease = 50;
this.msInjectDelay = 300; // number of milliseconds between injected keystrokes
/*
* HACK: We set fAllDown to false to ignore all down/up events for keys not explicitly marked as ONDOWN;
* even though that prevents those keys from being repeated properly (ie, at the simulation's repeat rate
* rather than the browser's repeat rate), it's the safest thing to do when dealing with international keyboards,
* because our mapping tables are designed for US keyboards, and testing all the permutations of international
* keyboards and web browsers is more work than I can take on right now. TODO: Dig into this some day.
*/
this.fAllDown = false;
this.setReady();
}
Component.subclass(Keyboard);
/*
* Supported keyboard models (the first entry is the default if the specified model isn't recognized)
*/
Keyboard.MODELS = ["US83", "US84", "US101"];
Keyboard.SIMCODE = {
BS: Keys.KEYCODE.BS + Keys.KEYCODE.ONDOWN,
TAB: Keys.KEYCODE.TAB + Keys.KEYCODE.ONDOWN,
SHIFT: Keys.KEYCODE.SHIFT + Keys.KEYCODE.ONDOWN,
RSHIFT: Keys.KEYCODE.SHIFT + Keys.KEYCODE.ONDOWN + Keys.KEYCODE.ONRIGHT,
CTRL: Keys.KEYCODE.CTRL + Keys.KEYCODE.ONDOWN,
ALT: Keys.KEYCODE.ALT + Keys.KEYCODE.ONDOWN,
CAPS_LOCK: Keys.KEYCODE.CAPS_LOCK + Keys.KEYCODE.ONDOWN,
ESC: Keys.KEYCODE.ESC + Keys.KEYCODE.ONDOWN,
/*
* It seems that a recent change to Safari on iOS (first noticed in iOS 9.1) treats SPACE
* differently now, at least with regard to