diff --git a/modules/pcjs/lib/keyboard.js b/modules/pcjs/lib/keyboard.js index cb9553c23..a67165f32 100644 --- a/modules/pcjs/lib/keyboard.js +++ b/modules/pcjs/lib/keyboard.js @@ -47,11 +47,11 @@ if (NODE) { * * The Keyboard component can be configured with the following (parmsKbd) properties: * - * model: model string; should be one of: + * model: keyboard model string, which must match one of the values listed in Keyboard.MODELS: * - * us83 (default) - * us84 (TODO: awaiting implementation) - * us101 (TODO: awaiting implementation) + * "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. @@ -64,7 +64,7 @@ function Keyboard(parmsKbd) { Component.call(this, "Keyboard", parmsKbd, Keyboard, Messages.KEYBOARD); - this.nDefaultModel = parmsKbd['model']; + this.setModel(parmsKbd['model']); this.fMobile = web.isMobile(); this.fMSIE = web.isUserAgent("MSIE"); @@ -148,6 +148,11 @@ function Keyboard(parmsKbd) Component.subclass(Keyboard); +/* + * Supported keyboard models (the first entry is the default if the specified model isn't recognized) + */ +Keyboard.MODELS = ["US83", "US84", "US101"]; + /** * Alphanumeric and other common (printable) ASCII codes. * @@ -197,6 +202,7 @@ Keyboard.KEYCODE = { /* 0x13 */ PAUSE: 19, // PAUSE/BREAK /* 0x14 */ CAPS_LOCK: 20, /* 0x1B */ ESC: 27, + /* 0x20 */ SPACE: 32, /* 0x21 */ PGUP: 33, /* 0x22 */ PGDN: 34, /* 0x23 */ END: 35, @@ -339,6 +345,7 @@ Keyboard.SIMCODE = { ALT: Keyboard.KEYCODE.ALT + Keyboard.KEYCODE.ONDOWN, CAPS_LOCK: Keyboard.KEYCODE.CAPS_LOCK + Keyboard.KEYCODE.ONDOWN, ESC: Keyboard.KEYCODE.ESC + Keyboard.KEYCODE.ONDOWN, + SPACE: Keyboard.KEYCODE.SPACE + Keyboard.KEYCODE.ONDOWN, F1: Keyboard.KEYCODE.F1 + Keyboard.KEYCODE.ONDOWN, F2: Keyboard.KEYCODE.F2 + Keyboard.KEYCODE.ONDOWN, F3: Keyboard.KEYCODE.F3 + Keyboard.KEYCODE.ONDOWN, @@ -673,7 +680,7 @@ Keyboard.SOFTCODES = { /* 54 */ 'right-shift': Keyboard.SIMCODE.RSHIFT, // formerly "rshift" /* 55 */ 'prtsc': Keyboard.SIMCODE.PRTSC, // unshifted '*'; becomes dedicated 'Print Screen' key on 101-key keyboards /* 56 */ 'alt': Keyboard.SIMCODE.ALT, - /* 57 */ 'space': Keyboard.ASCII[' '], + /* 57 */ 'space': Keyboard.SIMCODE.SPACE, /* 58 */ 'caps-lock': Keyboard.SIMCODE.CAPS_LOCK, /* 59 */ 'f1': Keyboard.SIMCODE.F1, /* 60 */ 'f2': Keyboard.SIMCODE.F2, @@ -858,7 +865,7 @@ Keyboard.SIMCODES[Keyboard.ASCII['?']] = Keyboard.SCANCODE.SLASH | (K Keyboard.SIMCODES[Keyboard.SIMCODE.RSHIFT] = Keyboard.SCANCODE.RSHIFT; Keyboard.SIMCODES[Keyboard.SIMCODE.PRTSC] = Keyboard.SCANCODE.PRTSC; Keyboard.SIMCODES[Keyboard.SIMCODE.ALT] = Keyboard.SCANCODE.ALT; -Keyboard.SIMCODES[Keyboard.ASCII[' ']] = Keyboard.SCANCODE.SPACE; +Keyboard.SIMCODES[Keyboard.SIMCODE.SPACE] = Keyboard.SCANCODE.SPACE; Keyboard.SIMCODES[Keyboard.SIMCODE.CAPS_LOCK] = Keyboard.SCANCODE.CAPS_LOCK; Keyboard.SIMCODES[Keyboard.SIMCODE.F1] = Keyboard.SCANCODE.F1; Keyboard.SIMCODES[Keyboard.SIMCODE.F2] = Keyboard.SCANCODE.F2; @@ -1150,13 +1157,26 @@ Keyboard.prototype.notifyEscape = function(fDisabled, fAllDown) }; /** - * setModel(nModel) + * setModel(sModel) + * + * This breaks a model string (eg, "US83") into two parts: modelCountry (eg, "US") and modelKeys (eg, 83). + * If the model string isn't recognized, we use Keyboard.MODELS[0] (ie, the first entry in the model array). * * @this {Keyboard} - * @param {number} nModel + * @param {string|undefined} sModel */ -Keyboard.prototype.setModel = function(nModel) +Keyboard.prototype.setModel = function(sModel) { + var iModel = 0; + this.model = null; + if (sModel) { + this.model = sModel.toUpperCase(); + iModel = Keyboard.MODELS.indexOf(this.model); + if (iModel < 0) iModel = 0; + } + sModel = Keyboard.MODELS[iModel]; + this.modelCountry = sModel.substr(0, 2); + this.modelKeys = parseInt(sModel.substr(2), 10); }; /** @@ -1383,7 +1403,22 @@ Keyboard.prototype.powerDown = function(fSave, fShutdown) */ Keyboard.prototype.reset = function() { - this.setModel(this.nDefaultModel); + /* + * If no keyboard model was specified, our initial setModel() call will select the "US83" keyboard as the + * default, but now that the ChipSet is initialized, we can pick a better default, based on the ChipSet model. + */ + if (!this.model && this.chipset) { + switch(this.chipset.model) { + case ChipSet.MODEL_5150: + case ChipSet.MODEL_5160: + this.setModel(Keyboard.MODELS[0]); + break; + case ChipSet.MODEL_5170: + default: + this.setModel(Keyboard.MODELS[1]); + break; + } + } this.initState(); @@ -1881,7 +1916,7 @@ Keyboard.prototype.updateActiveKey = function(key, msTimer) this.printMessage((msTimer? '\n' : "") + "updateActiveKey(" + key.simCode + (msTimer? "," + msTimer + "ms" : "") + "): " + (key.fDown? "down" : "up"), true); } - this.keySimulate(key.simCode, key.fDown); + if (!this.keySimulate(key.simCode, key.fDown)) return; if (!key.nRepeat) return; @@ -2173,6 +2208,14 @@ Keyboard.prototype.keySimulate = function(simCode, fDown) var abScanCodes = []; var bCode = wCode & 0xff; + + /* + * TODO: Update the following restrictions to address 84-key and 101-key keyboards limitations. + */ + if (bCode > 83 && this.modelKeys == 83) { + return false; + } + abScanCodes.push(bCode | (fDown? 0 : Keyboard.SCANCODE.BREAK)); var fAlpha = (simCode >= Keyboard.ASCII.A && simCode <= Keyboard.ASCII.Z || simCode >= Keyboard.ASCII.a && simCode <= Keyboard.ASCII.z); diff --git a/modules/pcjs/lib/serialport.js b/modules/pcjs/lib/serialport.js index 11f4d4379..987c28bb7 100644 --- a/modules/pcjs/lib/serialport.js +++ b/modules/pcjs/lib/serialport.js @@ -343,12 +343,23 @@ SerialPort.prototype.setBinding = function(sHTMLType, sBinding, control) switch (sBinding) { case SerialPort.sIOBuffer: + /* + * TODO: Figure out how to make this control activate the soft keyboard on iOS; since + * this control has the "readonly" attribute by default, iOS refuses to activate the + * keyboard, and adding the "contenteditable" attribute doesn't override that behavior. + * + * Removing the "readonly" attribute works, but then every key press results in double + * characters; presumably that could be overcome by processing all keys in the onKeyDown() + * handler instead, but that would require keyCode mapping tables, because down events + * are not the same as press events. + */ this.bindings[sBinding] = this.controlIOBuffer = control; + /* * By establishing an onkeypress handler here, we make it possible for DOS commands like * "CTTY COM1" to more or less work (use "CTTY CON" to restore control to the DOS console). */ - control.onkeydown = function onKeyDownSerial(event) { + control.onkeydown = function onKeyDown(event) { /* * This is required in addition to onkeypress, because it's the only way to prevent * BACKSPACE (keyCode 8) from being interpreted by the browser as a "Back" operation; @@ -369,18 +380,28 @@ SerialPort.prototype.setBinding = function(sHTMLType, sBinding, control) } return true; }; - control.onkeypress = function onKeyPressSerial(event) { + control.onkeypress = function onKeyPress(event) { /* - * Browser-independent keyCode extraction (refer to keyPress() and the other key - * event handlers in keyboard.js). - * - * The additional check for SPACE (keyCode 0x20) and subsequent preventDefault() call - * prevents SPACE from bubbling up to the document event handlers, where its default - * behavior is typically to scroll the entire page -- a real nuisance. + * Browser-independent keyCode extraction; refer to keyPress() and the other key event + * handlers in keyboard.js. */ event = event || window.event; var keyCode = event.which || event.keyCode; serial.sendRBR([keyCode]); + /* + * This additional check for SPACE (keyCode 0x20) and subsequent preventDefault() + * prevents SPACE from bubbling up to the document event handlers, where the default + * behavior is typically to scroll the entire page -- a real nuisance. + * + * Keyboard.onKeyPress() has a similar issue, but it seems to be limited to Safari on iOS, + * first noticed on iOS 9.1. The problem there is that Safari's default SPACE behavior + * occurs BEFORE the onkeypress handler is called, so we would have to call preventDefault() + * in the onkeydown handler, but then the onkeypress handler would no longer be called. + * + * So, to resolve the Keyboard.onKeyPress() issue, we now define SPACE as an ONDOWN key, + * so that onKeyDown() will process the SPACE key immediately and automatically invoke + * preventDefault(). + */ if (keyCode == 0x20) { if (event.preventDefault) event.preventDefault(); } diff --git a/modules/pcjs/lib/x86.js b/modules/pcjs/lib/x86.js index 00acca715..1cb17f9d9 100644 --- a/modules/pcjs/lib/x86.js +++ b/modules/pcjs/lib/x86.js @@ -89,7 +89,7 @@ var X86 = { * Priority: Instruction exception, TRAP, NMI, Processor Extension Segment Overrun, and finally INTR. * * All exceptions can also occur in real-mode, except where noted. A GP_FAULT in real-mode can be triggered - * by "any memory reference instruction that attempts to reference [a] 16-bit word at offset 0FFFFH". + * by "any memory reference instruction that attempts to reference [a] 16-bit word at offset 0xFFFF". * * Interrupts beyond 0x10 (up through 0x1F) are reserved for future exceptions. * @@ -137,11 +137,11 @@ var X86 = { DF: 0x0400, // bit 10: Direction flag OF: 0x0800, // bit 11: Overflow flag IOPL: { - MASK: 0x3000, // bits 12-13: I/O Privilege Level (always set on 8086/80186, clear on 80286 reset) + MASK: 0x3000, // bits 12-13: I/O Privilege Level (always set on 8086/80186; clear on 80286 reset) SHIFT: 12 }, - NT: 0x4000, // bit 14: Nested Task flag (always set on 8086/80186, clear on 80286 reset) - BIT15: 0x8000, // bit 15: reserved (always set on 8086/80186, clear otherwise) + NT: 0x4000, // bit 14: Nested Task flag (always set on 8086/80186; clear on 80286 reset) + BIT15: 0x8000, // bit 15: reserved (always set on 8086/80186; clear otherwise) RF: 0x10000, // bit 16: Resume Flag (temporarily disables debug exceptions; 80386 only) VM: 0x20000 // bit 17: Virtual 8086 Mode (80386 only) }, @@ -461,7 +461,7 @@ var X86 = { SS: 0x36, // opSS() DS: 0x3E, // opDS() PUSHSP: 0x54, // opPUSHSP() - PUSHA: 0x60, // opPUHSA() (80186 and up) + PUSHA: 0x60, // opPUSHA() (80186 and up) POPA: 0x61, // opPOPA() (80186 and up) BOUND: 0x62, // opBOUND() (80186 and up) ARPL: 0x63, // opARPL() (80286 and up) @@ -519,12 +519,12 @@ var X86 = { UD2: 0x0B0F // UD2 (invalid opcode "guaranteed" to generate UD_FAULT on all post-8086 processors) }, /* - * Floating Point Unit (FPU), aka Numeric Data Processor (NDP), aka Numeric Processor Extension (NPX) definitions + * Floating Point Unit (FPU), aka Numeric Data Processor (NDP), aka Numeric Processor Extension (NPX), aka Coprocessor definitions */ FPU: { MODEL_8087: 8087, MODEL_80287: 80287, - MODEL_80287XL: 80387, // internally, the 80287XL was an 80387SX, so in general, we treat this as MODEL_80387 + MODEL_80287XL: 80387, // internally, the 80287XL was an 80387SX, so generally, we treat this as MODEL_80387 MODEL_80387: 80387, CONTROL: { // FPU Control Word IM: 0x0001, // bit 0: Invalid Operation Mask @@ -534,18 +534,18 @@ var X86 = { UM: 0x0010, // bit 4: Underflow Mask PM: 0x0020, // bit 5: Precision Mask EXC: 0x003F, // all of the above exceptions - // bit 6: unused IEM: 0x0080, // bit 7: Interrupt Enable Mask (0 enables interrupts, 1 masks them; 8087 only) PC: 0x0300, // bits 8-9: Precision Control - RC: 0x0C00, // bits 10-11: Rounding Control - RC_NEAR: 0x0000, - RC_DOWN: 0x0400, - RC_UP: 0x0800, - RC_CHOP: 0x0C00, + RC: { // bits 10-11: Rounding Control + NEAR: 0x0000, + DOWN: 0x0400, + UP: 0x0800, + CHOP: 0x0C00, + MASK: 0x0C00 + }, IC: 0x1000, // bit 12: Infinity Control (0 for Projective, 1 for Affine) - // bits 13-15: unused - INIT: 0x03BF, // X86.FPU.CONTROL.IM | X86.FPU.CONTROL.DM | X86.FPU.CONTROL.ZM | X86.FPU.CONTROL.OM | X86.FPU.CONTROL.UM | X86.FPU.CONTROL.PM | X86.FPU.CONTROL.IEM | X86.FPU.CONTROL.PC - UNUSED: 0xE040 + UNUSED: 0xE040, // bits 6,13-15: unused + INIT: 0x03BF // X86.FPU.CONTROL.IM | X86.FPU.CONTROL.DM | X86.FPU.CONTROL.ZM | X86.FPU.CONTROL.OM | X86.FPU.CONTROL.UM | X86.FPU.CONTROL.PM | X86.FPU.CONTROL.IEM | X86.FPU.CONTROL.PC }, STATUS: { // FPU Status Word IE: 0x0001, // bit 0: Invalid Operation diff --git a/modules/pcjs/lib/x86fpu.js b/modules/pcjs/lib/x86fpu.js index befb6237c..fab94a11c 100644 --- a/modules/pcjs/lib/x86fpu.js +++ b/modules/pcjs/lib/x86fpu.js @@ -60,7 +60,7 @@ if (NODE) { * * FPU Coprocessor Trivia * - * Microsoft C libraries executed software interrupts in the range 0x34-0x3B immediately after + * Microsoft C 4.00 libraries executed software interrupts in the range 0x34-0x3B immediately after * FPU operations, to assist with floating-point emulation when no coprocessor was present, since * processors prior to the 80286 had no mechanism for generating a fault when an unsupported FPU * instruction was executed. @@ -68,7 +68,7 @@ if (NODE) { * In short, INT 0x34 through INT 0x3B was used after ESC opcodes 0xD8 through 0xDF, INT 0x3C was * used for FPU instructions containing a segment override, and INT 0x3D was used for FWAIT. * - * A sample piece of code is available in x86ops.js, because it also highlights the Microsoft C + * A sample piece of code is available in x86ops.js, because it also highlights the Microsoft C 4.00 * library's dependency on the 8086/8088 behavior of "PUSH SP" (see the opPUSHSP_8086() function). */ @@ -276,6 +276,8 @@ X86FPU.prototype.restore = function(data) * us whenever an I/O operation that resets the coprocessor is performed. Only 80487 coprocessors and higher will * also clear the "exception" registers, but the 80487 is currently beyond my planned level of support. * + * TODO: Add support for X86.FPU.CONTROL.PC (Precision Control) and X86.FPU.CONTROL.IC (Infinity Control) + * * @this {X86FPU} */ X86FPU.prototype.resetFPU = function() @@ -287,8 +289,8 @@ X86FPU.prototype.resetFPU = function() this.iST = 0; // the ST bits for regStatus are actually stored here if (DEBUG) { /* - * All the registers were tagged "unused" above, which is all that would normally happen, - * but debugging is a little easier if we also zero everything, too. + * All the registers were tagged "unused" above, which is all that would normally happen, but debugging is + * a little easier if we zero all the registers as well. */ for (var iReg = 0; iReg < this.regStack.length; iReg++) { this.regStack[iReg] = 0.0; @@ -315,7 +317,7 @@ X86FPU.prototype.isModel = function(model) /** * isAtLeastModel(model) * - * If the current model is greater than or equal to the specified model, then it's assumed the + * If the current model is greater than or equal to the specified model, then it's assumed that the * current operation is supported, and we return true. * * @this {X86FPU} @@ -334,9 +336,9 @@ X86FPU.prototype.isAtLeastModel = function(model) * to a "temp-real" (REAL80) and back again losslessly, otherwise a bug in either getTRFromLR() or getLRFromTR() * might exist. That test code can be resurrected from the repo; this code is being retained for future tests. * - * NOTE: If either min or max is a value containing 32 or more bits AND bit 31 is set AND it has passed - * through some bit-wise operation(s), then that value may end up being negative, so you may end up with an - * inverted (or empty) range or other unexpected results. + * NOTE: If either min or max is a value containing 32 or more significant bits AND bit 31 is set AND it has passed + * through some bit-wise operation(s), then that value may end up being negative, so you may end up with an inverted + * range, or a range that's smaller or larger than intended. * * @this {X86FPU} * @param {number} min (inclusive) @@ -712,16 +714,16 @@ X86FPU.prototype.roundInteger = function(operand, max) { if (operand == null) return null; - var rc = (this.regControl & X86.FPU.CONTROL.RC), result; + var rc = (this.regControl & X86.FPU.CONTROL.RC.MASK), result; - if (rc == X86.FPU.CONTROL.RC_NEAR) { + if (rc == X86.FPU.CONTROL.RC.NEAR) { result = Math.round(operand); if (result - operand === 0.5 && (result % 2)) result--; } - else if (rc == X86.FPU.CONTROL.RC_DOWN || rc == X86.FPU.CONTROL.RC_CHOP && operand > 0) { + else if (rc == X86.FPU.CONTROL.RC.DOWN || rc == X86.FPU.CONTROL.RC.CHOP && operand > 0) { result = Math.floor(operand); } - else { // X86.FPU.CONTROL.RC_UP or X86.FPU.CONTROL.RC_CHOP && operand <= 0 + else { // X86.FPU.CONTROL.RC.UP or X86.FPU.CONTROL.RC.CHOP && operand <= 0 result = Math.ceil(operand); } @@ -1417,6 +1419,8 @@ X86FPU.prototype.saveEnv = function(addr) /** * opFPU(bOpcode, bModRM, dst, src) * + * This is called by the CPU's ESC opcode handlers, after each instruction has been fully decoded. + * * @this {X86FPU} * @param {number} bOpcode (0xD8-0xDF) * @param {number} bModRM @@ -1485,6 +1489,23 @@ X86FPU.prototype.opFPU = function(bOpcode, bModRM, dst, src) } }; +/** + * opWAIT() + * + * This is called by the CPU's WAIT opcode handler, giving us the opportunity to synchronize the FPU with the CPU, + * charge an appropriate number of cycles, and return true. In this context, it's considered an FWAIT instruction, + * but technically, it's the same opcode. + * + * If we choose to do nothing, then we must return false, so that the CPU can charge a default number of cycles. + * + * @this {X86FPU} + * @return {boolean} true if implemented, false if not + */ +X86FPU.prototype.opWAIT = function() +{ + return false; +}; + if (DEBUGGER) { /** * readFPUStack(i) diff --git a/modules/pcjs/lib/x86ops.js b/modules/pcjs/lib/x86ops.js index 9aa9d9c73..af668cd23 100644 --- a/modules/pcjs/lib/x86ops.js +++ b/modules/pcjs/lib/x86ops.js @@ -2600,8 +2600,9 @@ X86.opCALLF = function() */ X86.opWAIT = function() { - this.printMessage("WAIT not implemented"); - this.nStepCycles--; + if (!this.fpu || !this.fpu.opWAIT()) { + this.nStepCycles -= 3; // X86FPU.opWAIT() is required to charge some number of cycles if it returns true + } }; /**