Fixed space handling on iOS 9.1

This commit is contained in:
Jeff Parsons 2015-12-01 19:22:03 -08:00
commit bf75b8a059
5 changed files with 136 additions and 50 deletions

View file

@ -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);

View file

@ -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();
}

View file

@ -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

View file

@ -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)

View file

@ -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
}
};
/**