First new feature of v1.16.0: support for pointer locking (tested on Chrome and Firefox)
This commit is contained in:
parent
f520a2b47f
commit
de90d921ed
146 changed files with 7272 additions and 1000 deletions
|
|
@ -954,7 +954,11 @@ FDC.prototype.initDrive = function(drive, iDrive, data)
|
|||
}
|
||||
else if (this.loadDiskette(iDrive, sDisketteName, sDiskettePath, true)) {
|
||||
if (drive.disk) {
|
||||
this.addDiskHistory(sDisketteName, sDiskettePath, drive.disk);
|
||||
if (sDiskettePath) {
|
||||
this.addDiskHistory(sDisketteName, sDiskettePath, drive.disk);
|
||||
} else {
|
||||
if (DEBUG) Component.warning("Disk '" + (drive.disk.sDiskName || sDisketteName) + "' not recorded properly in drive " + iDrive);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.setReady(false);
|
||||
|
|
@ -1034,6 +1038,9 @@ FDC.prototype.saveDrive = function(drive)
|
|||
data[i++] = drive.fLocal;
|
||||
data[i++] = drive.sDisketteName;
|
||||
data[i] = drive.sDiskettePath;
|
||||
if (DEBUG && !drive.sDiskettePath && drive.disk && drive.disk.sDiskPath) {
|
||||
Component.warning("Disk '" + drive.disk.sDiskName + "' not saved properly in drive " + drive.iDrive);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
|
|
@ -1520,7 +1527,7 @@ FDC.prototype.addDiskHistory = function(sDisketteName, sDiskettePath, disk)
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (DEBUG) this.messageDebugger("disk '" + sDisketteName + "' added to history (" + sDiskettePath + ")");
|
||||
if (DEBUG) this.messageDebugger("disk '" + sDisketteName + "' added to history (nothing to restore)");
|
||||
this.aDiskHistory[i] = [sDisketteName, sDiskettePath, []];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ function Keyboard(parmsKbd) {
|
|||
Component.call(this, "Keyboard", parmsKbd, Keyboard);
|
||||
|
||||
this.nDefaultModel = parmsKbd['model'];
|
||||
this.fEscapeDisabled = false;
|
||||
|
||||
/*
|
||||
* There are multiple ways that scan codes can be injected into the machine: button events,
|
||||
|
|
@ -533,7 +534,7 @@ Keyboard.aSoftCodes = {
|
|||
};
|
||||
|
||||
/**
|
||||
* This array is used by keySimulateUpDown() to lookup a given keyCode and convert it to a scan code
|
||||
* This array is used by keySimulateUpOrDown() to lookup a given keyCode and convert it to a scan code
|
||||
* (lower byte) plus any required shift key states (upper bytes).
|
||||
*
|
||||
* Using keyCodes from keyPress events proved to be more robust than using keyCodes from keyDown and
|
||||
|
|
@ -545,7 +546,7 @@ Keyboard.aSoftCodes = {
|
|||
* 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: keySimulateUpDown() has the ability to "undo" any states in bitsShift
|
||||
* we've inherited the same solution: keySimulateUpOrDown() has the ability to "undo" any states in bitsShift
|
||||
* that conflict with the state(s) required for the character in question.
|
||||
*
|
||||
* @enum {number}
|
||||
|
|
@ -701,7 +702,7 @@ Keyboard.aKeyCodes[Keyboard.KEYCODE.FAKE_CTRLBREAK] = Keyboard.SCANCODE.SCROLL_
|
|||
Keyboard.aKeyCodes[Keyboard.KEYCODE.FAKE_CTRLALTDEL] = Keyboard.SCANCODE.NUM_DEL + (Keyboard.SCANCODE.CTRL << 8) + (Keyboard.SCANCODE.ALT << 16);
|
||||
|
||||
/**
|
||||
* keySimulateUpDown() origin codes (useful only for debugging)
|
||||
* keySimulateUpOrDown() origin codes (useful only for debugging)
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
|
|
@ -881,6 +882,19 @@ Keyboard.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
this.chipset = cmp.getComponentByType("ChipSet");
|
||||
};
|
||||
|
||||
/**
|
||||
* notifyEscape(fDisabled)
|
||||
*
|
||||
* When ESC is used by the browser to disable pointer lock, this gives us the option of mapping a different key to ESC.
|
||||
*
|
||||
* @this {Keyboard}
|
||||
* @param {boolean} fDisabled
|
||||
*/
|
||||
Keyboard.prototype.notifyEscape = function(fDisabled)
|
||||
{
|
||||
this.fEscapeDisabled = fDisabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* setModel(nModel)
|
||||
*
|
||||
|
|
@ -1285,7 +1299,7 @@ Keyboard.prototype.autoClear = function(notKeyCode)
|
|||
if (DEBUG) this.messageDebugger("autoClear(" + this.prevCharDown + ")");
|
||||
Component.assert(this.aKeyTimers[this.prevCharDown]);
|
||||
clearTimeout(this.aKeyTimers[this.prevCharDown]);
|
||||
this.keySimulateUpDown(this.prevCharDown, false, Keyboard.SIMCODE.AUTOCLEAR);
|
||||
this.keySimulateUpOrDown(this.prevCharDown, false, Keyboard.SIMCODE.AUTOCLEAR);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1427,7 +1441,7 @@ Keyboard.prototype.keyUpDown = function(event, fDown)
|
|||
if (keyCode >= Keyboard.ASCII.A && keyCode <= Keyboard.ASCII.Z) {
|
||||
/*
|
||||
* Convert "upper-case" letter combinations into "lower-case" combinations, so
|
||||
* that keySimulateUpDown() doesn't think it also needs to simulate a SHIFT key, too.
|
||||
* that keySimulateUpOrDown() doesn't think it also needs to simulate a SHIFT key, too.
|
||||
*/
|
||||
keyCodeSim += (Keyboard.ASCII.a - Keyboard.ASCII.A);
|
||||
}
|
||||
|
|
@ -1477,7 +1491,7 @@ Keyboard.prototype.keyUpDown = function(event, fDown)
|
|||
}
|
||||
|
||||
if (fPass === undefined) {
|
||||
fPass = !this.keySimulateUpDown(keyCodeSim, fDown, Keyboard.SIMCODE.KEYUPDOWN);
|
||||
fPass = !this.keySimulateUpOrDown(keyCodeSim, fDown, Keyboard.SIMCODE.KEYUPDOWN);
|
||||
}
|
||||
|
||||
if (DEBUG) this.messageDebugger(/*(fDown?"\n":"") +*/ "key" + (fDown? "Down" : "Up") + "(" + keyCode + "): " + (fPass? "pass" : "consume"), Debugger.MESSAGE.KEYS);
|
||||
|
|
@ -1515,14 +1529,16 @@ Keyboard.prototype.keyPress = function(event)
|
|||
*/
|
||||
fPass = false;
|
||||
} else {
|
||||
if (this.bitsShift & Keyboard.STATE.COMMAND)
|
||||
if (this.bitsShift & Keyboard.STATE.COMMAND) {
|
||||
this.bitsShift &= ~Keyboard.STATE.COMMAND;
|
||||
else {
|
||||
} else {
|
||||
// if (DEBUG && event.altKey) this.messageDebugger("ALT press: keyCode " + keyCode, Debugger.MESSAGE.KEYS);
|
||||
if (this.bitsShift & (Keyboard.STATE.CTRL | Keyboard.STATE.ALT))
|
||||
if (this.bitsShift & (Keyboard.STATE.CTRL | Keyboard.STATE.ALT)) {
|
||||
fPass = false;
|
||||
else
|
||||
} else {
|
||||
if (this.fEscapeDisabled && keyCode == Keyboard.ASCII['`']) keyCode = Keyboard.KEYCODE.ESC;
|
||||
fPass = !this.keySimulatePress(keyCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1547,7 +1563,7 @@ Keyboard.prototype.keySimulatePress = function(keyCode, fQuickRelease)
|
|||
*/
|
||||
this.autoClear(keyCode);
|
||||
|
||||
if (this.keySimulateUpDown(keyCode, true, Keyboard.SIMCODE.KEYPRESS)) {
|
||||
if (this.keySimulateUpOrDown(keyCode, true, Keyboard.SIMCODE.KEYPRESS)) {
|
||||
/*
|
||||
* If fQuickRelease is set, we switch to an alternate approach, which is to immediately queue a
|
||||
* "release" event as well. I used to also do this at high speeds, because the CPU could get lucky
|
||||
|
|
@ -1563,7 +1579,7 @@ Keyboard.prototype.keySimulatePress = function(keyCode, fQuickRelease)
|
|||
* that it's time to review/overhaul this code.
|
||||
*/
|
||||
if (fQuickRelease /* || this.cpu.speed == CPU.SPEED_MAX */) {
|
||||
this.keySimulateUpDown(keyCode, false, Keyboard.SIMCODE.KEYRELEASE);
|
||||
this.keySimulateUpOrDown(keyCode, false, Keyboard.SIMCODE.KEYRELEASE);
|
||||
}
|
||||
else {
|
||||
var fRepeat = false;
|
||||
|
|
@ -1574,7 +1590,7 @@ Keyboard.prototype.keySimulatePress = function(keyCode, fQuickRelease)
|
|||
var msDelay = this.calcReleaseDelay(fRepeat);
|
||||
this.aKeyTimers[this.prevCharDown = keyCode] = setTimeout(function (kbd) {
|
||||
return function onkeySimulatePressTimeout() {
|
||||
kbd.keySimulateUpDown(keyCode, false, Keyboard.SIMCODE.KEYTIMEOUT);
|
||||
kbd.keySimulateUpOrDown(keyCode, false, Keyboard.SIMCODE.KEYTIMEOUT);
|
||||
};
|
||||
}(this), msDelay);
|
||||
if (DEBUG) this.messageDebugger("keySimulatePress(" + keyCode + "): setTimeout()");
|
||||
|
|
@ -1586,7 +1602,7 @@ Keyboard.prototype.keySimulatePress = function(keyCode, fQuickRelease)
|
|||
};
|
||||
|
||||
/**
|
||||
* keySimulateUpDown(keyCode, fDown, simCode)
|
||||
* keySimulateUpOrDown(keyCode, fDown, simCode)
|
||||
*
|
||||
* @this {Keyboard}
|
||||
* @param {number} keyCode
|
||||
|
|
@ -1594,7 +1610,7 @@ Keyboard.prototype.keySimulatePress = function(keyCode, fQuickRelease)
|
|||
* @param {number} simCode indicates the origin of the event
|
||||
* @return {boolean} true if successfully simulated, false if unrecognized/unsupported key
|
||||
*/
|
||||
Keyboard.prototype.keySimulateUpDown = function(keyCode, fDown, simCode)
|
||||
Keyboard.prototype.keySimulateUpOrDown = function(keyCode, fDown, simCode)
|
||||
{
|
||||
var fSimulated = false;
|
||||
|
||||
|
|
@ -1666,7 +1682,7 @@ Keyboard.prototype.keySimulateUpDown = function(keyCode, fDown, simCode)
|
|||
|
||||
fSimulated = true;
|
||||
}
|
||||
if (DEBUG && DEBUGGER) this.messageDebugger("keySimulateUpDown(" + keyCode + "," + (fDown? "down" : "up") + "," + Keyboard.aSimCodeDescs[simCode] + "): " + (fSimulated? "true" : "false"), Debugger.MESSAGE.KEYS);
|
||||
if (DEBUG && DEBUGGER) this.messageDebugger("keySimulateUpOrDown(" + keyCode + "," + (fDown? "down" : "up") + "," + Keyboard.aSimCodeDescs[simCode] + "): " + (fSimulated? "true" : "false"), Debugger.MESSAGE.KEYS);
|
||||
return fSimulated;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -69,15 +69,16 @@ if (typeof module !== 'undefined') {
|
|||
* @extends Component
|
||||
* @param {Object} parmsMouse
|
||||
*/
|
||||
function Mouse(parmsMouse) {
|
||||
|
||||
function Mouse(parmsMouse)
|
||||
{
|
||||
Component.call(this, "Mouse", parmsMouse, Mouse);
|
||||
|
||||
this.idAdapter = parmsMouse['serial'];
|
||||
if (this.idAdapter) {
|
||||
this.sAdapterType = "SerialPort";
|
||||
}
|
||||
this.fActive = false;
|
||||
this.setActive(false);
|
||||
this.fLocked = false;
|
||||
this.setReady();
|
||||
}
|
||||
|
||||
|
|
@ -170,11 +171,13 @@ Mouse.ID_SERIAL = 0x4D;
|
|||
* @param {X86CPU} cpu
|
||||
* @param {Debugger} dbg
|
||||
*/
|
||||
Mouse.prototype.initBus = function(cmp, bus, cpu, dbg) {
|
||||
Mouse.prototype.initBus = function(cmp, bus, cpu, dbg)
|
||||
{
|
||||
this.cmp = cmp;
|
||||
this.bus = bus;
|
||||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
this.video = this.cmp.getComponentByType("Video");
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -183,10 +186,30 @@ Mouse.prototype.initBus = function(cmp, bus, cpu, dbg) {
|
|||
* @this {Mouse}
|
||||
* @return {boolean} true if active, false if not
|
||||
*/
|
||||
Mouse.prototype.isActive = function() {
|
||||
Mouse.prototype.isActive = function()
|
||||
{
|
||||
return this.fActive && (this.cpu? this.cpu.isRunning() : false);
|
||||
};
|
||||
|
||||
/**
|
||||
* setActive(fActive)
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {boolean} fActive is true if active, false if not
|
||||
*/
|
||||
Mouse.prototype.setActive = function(fActive)
|
||||
{
|
||||
this.fActive = fActive;
|
||||
/*
|
||||
* It's currently not possible to automatically lock the pointer outside the context of a user action
|
||||
* (eg, a button or canvas click), so this code is for naught.
|
||||
*
|
||||
* if (this.video) this.video.notifyPointerActive(fActive);
|
||||
*
|
||||
* We now rely on similar code in clickMouse().
|
||||
*/
|
||||
};
|
||||
|
||||
/**
|
||||
* powerUp(data, fRepower)
|
||||
*
|
||||
|
|
@ -195,7 +218,8 @@ Mouse.prototype.isActive = function() {
|
|||
* @param {boolean} [fRepower]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
Mouse.prototype.powerUp = function(data, fRepower) {
|
||||
Mouse.prototype.powerUp = function(data, fRepower)
|
||||
{
|
||||
if (!fRepower) {
|
||||
if (!data || !this.restore) {
|
||||
this.reset();
|
||||
|
|
@ -224,8 +248,7 @@ Mouse.prototype.powerUp = function(data, fRepower) {
|
|||
}
|
||||
}
|
||||
if (this.componentAdapter) {
|
||||
var componentScreen = this.cmp.getComponentByType("Video");
|
||||
if (componentScreen) this.canvasScreen = componentScreen.getCanvas();
|
||||
if (this.video) this.canvasScreen = this.video.getCanvas(this);
|
||||
} else {
|
||||
Component.warning(this.id + ": " + this.sAdapterType + " " + this.idAdapter + " unavailable");
|
||||
}
|
||||
|
|
@ -246,7 +269,8 @@ Mouse.prototype.powerUp = function(data, fRepower) {
|
|||
* @param {boolean} fSave
|
||||
* @return {Object|boolean}
|
||||
*/
|
||||
Mouse.prototype.powerDown = function(fSave) {
|
||||
Mouse.prototype.powerDown = function(fSave)
|
||||
{
|
||||
return fSave && this.save? this.save() : true;
|
||||
};
|
||||
|
||||
|
|
@ -255,7 +279,8 @@ Mouse.prototype.powerDown = function(fSave) {
|
|||
*
|
||||
* @this {Mouse}
|
||||
*/
|
||||
Mouse.prototype.reset = function() {
|
||||
Mouse.prototype.reset = function()
|
||||
{
|
||||
this.initState();
|
||||
};
|
||||
|
||||
|
|
@ -267,7 +292,8 @@ Mouse.prototype.reset = function() {
|
|||
* @this {Mouse}
|
||||
* @return {Object}
|
||||
*/
|
||||
Mouse.prototype.save = function() {
|
||||
Mouse.prototype.save = function()
|
||||
{
|
||||
var state = new State(this);
|
||||
state.set(0, this.saveState());
|
||||
return state.data();
|
||||
|
|
@ -282,7 +308,8 @@ Mouse.prototype.save = function() {
|
|||
* @param {Object} data
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
Mouse.prototype.restore = function(data) {
|
||||
Mouse.prototype.restore = function(data)
|
||||
{
|
||||
return this.initState(data[0]);
|
||||
};
|
||||
|
||||
|
|
@ -293,10 +320,11 @@ Mouse.prototype.restore = function(data) {
|
|||
* @param {Array} [data]
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
Mouse.prototype.initState = function(data) {
|
||||
Mouse.prototype.initState = function(data)
|
||||
{
|
||||
var i = 0;
|
||||
if (data === undefined) data = [false, -1, -1, 0, 0, false, false, 0];
|
||||
this.fActive = data[i++];
|
||||
this.setActive(data[i++]);
|
||||
this.xMouse = data[i++];
|
||||
this.yMouse = data[i++];
|
||||
this.xDelta = data[i++];
|
||||
|
|
@ -313,7 +341,8 @@ Mouse.prototype.initState = function(data) {
|
|||
* @this {Mouse}
|
||||
* @return {Array}
|
||||
*/
|
||||
Mouse.prototype.saveState = function() {
|
||||
Mouse.prototype.saveState = function()
|
||||
{
|
||||
var i = 0;
|
||||
var data = [];
|
||||
data[i++] = this.fActive;
|
||||
|
|
@ -327,6 +356,17 @@ Mouse.prototype.saveState = function() {
|
|||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* notifyPointerLocked()
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {boolean} fLocked
|
||||
*/
|
||||
Mouse.prototype.notifyPointerLocked = function(fLocked)
|
||||
{
|
||||
this.fLocked = fLocked;
|
||||
};
|
||||
|
||||
/**
|
||||
* captureMouse(control)
|
||||
*
|
||||
|
|
@ -336,7 +376,8 @@ Mouse.prototype.saveState = function() {
|
|||
* @this {Mouse}
|
||||
* @param {Object} control from the HTML DOM (eg, the canvas for the simulated screen)
|
||||
*/
|
||||
Mouse.prototype.captureMouse = function(control) {
|
||||
Mouse.prototype.captureMouse = function(control)
|
||||
{
|
||||
if (control) {
|
||||
var mouse = this;
|
||||
if (!this.fCaptured) {
|
||||
|
|
@ -388,7 +429,8 @@ Mouse.prototype.captureMouse = function(control) {
|
|||
* @this {Mouse}
|
||||
* @param {Object} control from the HTML DOM
|
||||
*/
|
||||
Mouse.prototype.releaseMouse = function(control) {
|
||||
Mouse.prototype.releaseMouse = function(control)
|
||||
{
|
||||
if (control) {
|
||||
control['style']['cursor'] = "auto";
|
||||
}
|
||||
|
|
@ -406,20 +448,31 @@ Mouse.prototype.releaseMouse = function(control) {
|
|||
* client-area coordinates. In fact, layerX and layerY are probably closer to what I really want,
|
||||
* but I don't think they're available in all browsers. screenX and screenY would work as well.
|
||||
*
|
||||
* Anyway, all I care about are deltas. For now.
|
||||
* This is because all we care about are deltas. We record clientX and clientY (as xMouse and yMouse)
|
||||
* merely to calculate xDelta and yDelta.
|
||||
*
|
||||
* @this {Mouse}
|
||||
* @param {Object} event object from a 'mousemove' event (specifically, a MouseEvent object)
|
||||
*/
|
||||
Mouse.prototype.moveMouse = function(event) {
|
||||
Mouse.prototype.moveMouse = function(event)
|
||||
{
|
||||
if (this.isActive()) {
|
||||
if (this.xMouse < 0 || this.yMouse < 0) {
|
||||
this.xMouse = event.clientX;
|
||||
this.yMouse = event.clientY;
|
||||
}
|
||||
this.xDelta = event.clientX - this.xMouse;
|
||||
this.yDelta = event.clientY - this.yMouse;
|
||||
if (this.fLocked) {
|
||||
this.xDelta = event['movementX'] || event['mozMovementX'] || event['webkitMovementX'] || 0;
|
||||
this.yDelta = event['movementY'] || event['mozMovementY'] || event['webkitMovementY'] || 0;
|
||||
} else {
|
||||
this.xDelta = event.clientX - this.xMouse;
|
||||
this.yDelta = event.clientY - this.yMouse;
|
||||
}
|
||||
if (this.xDelta || this.yDelta) {
|
||||
/*
|
||||
* As sendPacket() indicates, the x and y coordinates we pass below are for diagnostic purposes
|
||||
* only. sendPacket() really only cares about xDelta and yDelta, which it then zeroes on completion.
|
||||
*/
|
||||
this.sendPacket(null, event.clientX, event.clientY);
|
||||
}
|
||||
this.xMouse = event.clientX;
|
||||
|
|
@ -434,8 +487,18 @@ Mouse.prototype.moveMouse = function(event) {
|
|||
* @param {number} iButton is 0 for fButton1 (the LEFT button), 2 for fButton2 (the RIGHT button)
|
||||
* @param {boolean} fDown
|
||||
*/
|
||||
Mouse.prototype.clickMouse = function(iButton, fDown) {
|
||||
Mouse.prototype.clickMouse = function(iButton, fDown)
|
||||
{
|
||||
if (this.isActive()) {
|
||||
if (this.fLocked === false) {
|
||||
/*
|
||||
* If there's no support for automatic pointer locking in the Video component, then notifyPointerActive()
|
||||
* will return false, and we will set fLocked to null, ensuring that we never attempt this again.
|
||||
*/
|
||||
if (!this.video || !this.video.notifyPointerActive(true)) {
|
||||
this.fLocked = null;
|
||||
}
|
||||
}
|
||||
var sDiag;
|
||||
switch (iButton) {
|
||||
case 0:
|
||||
|
|
@ -475,7 +538,8 @@ Mouse.prototype.clickMouse = function(iButton, fDown) {
|
|||
* @param {number} [xDiag] original x-coordinate (optional; for diagnostic use only)
|
||||
* @param {number} [yDiag] original y-coordinate (optional; for diagnostic use only)
|
||||
*/
|
||||
Mouse.prototype.sendPacket = function(sDiag, xDiag, yDiag) {
|
||||
Mouse.prototype.sendPacket = function(sDiag, xDiag, yDiag)
|
||||
{
|
||||
var b1 = 0x40 | (this.fButton1? 0x20 : 0) | (this.fButton2? 0x10 : 0) | ((this.yDelta & 0xC0) >> 4) | ((this.xDelta & 0xC0) >> 6);
|
||||
var b2 = this.xDelta & 0x3F;
|
||||
var b3 = this.yDelta & 0x3F;
|
||||
|
|
@ -502,7 +566,8 @@ Mouse.prototype.sendPacket = function(sDiag, xDiag, yDiag) {
|
|||
* @this {Mouse}
|
||||
* @param {number} bMCR
|
||||
*/
|
||||
Mouse.prototype.notifyMCR = function(bMCR) {
|
||||
Mouse.prototype.notifyMCR = function(bMCR)
|
||||
{
|
||||
var fActive = ((bMCR & (SerialPort.MCR.DTR | SerialPort.MCR.RTS)) == (SerialPort.MCR.DTR | SerialPort.MCR.RTS));
|
||||
if (fActive) {
|
||||
if (!this.fActive) {
|
||||
|
|
@ -541,7 +606,7 @@ Mouse.prototype.notifyMCR = function(bMCR) {
|
|||
this.messageDebugger("serial mouse ID sent");
|
||||
}
|
||||
this.captureMouse(this.canvasScreen);
|
||||
this.fActive = fActive;
|
||||
this.setActive(fActive);
|
||||
}
|
||||
} else {
|
||||
if (this.fActive) {
|
||||
|
|
@ -559,7 +624,7 @@ Mouse.prototype.notifyMCR = function(bMCR) {
|
|||
*/
|
||||
this.messageDebugger("serial mouse inactive");
|
||||
this.releaseMouse(this.canvasScreen);
|
||||
this.fActive = fActive;
|
||||
this.setActive(fActive);
|
||||
}
|
||||
}
|
||||
this.bMCR = bMCR;
|
||||
|
|
@ -573,7 +638,8 @@ Mouse.prototype.notifyMCR = function(bMCR) {
|
|||
* @this {Mouse}
|
||||
* @param {string} sMessage is any caller-defined message string
|
||||
*/
|
||||
Mouse.prototype.messageDebugger = function(sMessage) {
|
||||
Mouse.prototype.messageDebugger = function(sMessage)
|
||||
{
|
||||
if (DEBUGGER && this.dbg) {
|
||||
if (this.dbg.messageEnabled(Debugger.MESSAGE.MOUSE)) {
|
||||
this.dbg.message(sMessage + " @" + str.toHexAddr(this.cpu.regIP, this.cpu.segCS.sel));
|
||||
|
|
@ -591,7 +657,8 @@ Mouse.prototype.messageDebugger = function(sMessage) {
|
|||
* attribute containing the same JSON-encoded parameters that the Mouse constructor
|
||||
* expects.
|
||||
*/
|
||||
Mouse.init = function() {
|
||||
Mouse.init = function()
|
||||
{
|
||||
var aeMouse = Component.getElementsByClass(window.document, PCJSCLASS, "mouse");
|
||||
for (var iMouse = 0; iMouse < aeMouse.length; iMouse++) {
|
||||
var eMouse = aeMouse[iMouse];
|
||||
|
|
|
|||
|
|
@ -30,68 +30,6 @@
|
|||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
/*
|
||||
* MDA/CGA Support
|
||||
*
|
||||
* This component implements a combination of the MDA and CGA cards, with the ability to
|
||||
* dynamically switch between the two, by simply toggling the SW1 motherboard switch settings
|
||||
* and resetting the virtual machine. As a result, this component always installs I/O port
|
||||
* handlers for both MDA and CGA, regardless which card is initially active.
|
||||
*
|
||||
* Since there's a lot of similarity between the two cards (eg, their text-mode video buffer
|
||||
* format, and their use of the 6845 CRT controller), since the MDA ROM contains the fonts
|
||||
* used by both devices, and since the same ROM BIOS supports both (in fact, the BIOS rather
|
||||
* indiscriminately initializes both, regardless which is actually installed), I decided to
|
||||
* use the same component to emulate both devices. Note that it was possible for an IBM PC
|
||||
* to have both an MDA and CGA running in the same machine, so if that's something we want to
|
||||
* support later, it can be done by instantiating this component twice, with some additional
|
||||
* properties to force each instance to register only those I/O ports belonging to its specific
|
||||
* configuration.
|
||||
*
|
||||
* While supporting both devices is mostly a convenience, it also creates a few inconveniences.
|
||||
* For one, the MDA prefers a native window size of 720x350, as it supports only one video mode,
|
||||
* 80x25, with a 9x14 cell size. The CGA, on the other hand, has an 8x8 cell size, so when using
|
||||
* an MDA-size window, an 80x25 CGA screen will end up with 40-pixel borders on the left and right,
|
||||
* and 75-pixel borders on the top and bottom. The result is a rather tiny CGA font surrounded
|
||||
* by lots of wasted space, so it's best to turn on font scaling (see the "scale" property) and
|
||||
* go with a larger window size of, say, 960x400 (50% larger in width, 100% larger in height).
|
||||
*
|
||||
* I've also added support for font-doubling in createFont(). We use the 8x8 font for 80-column
|
||||
* modes and the "doubled" 16x16 font for 40-column modes OR whenever the screen is large enough
|
||||
* to use the 16x16 font, since font rendering without scaling provides the sharpest results.
|
||||
* In fact, there's special logic in setDimensions() to ignore fScaleFont in certain cases (eg,
|
||||
* 40-column modes, to improve sharpness and avoid stretching the font beyond readability).
|
||||
*
|
||||
* Graphics modes, on the other hand, are always scaled to the window size. Pixels are captured
|
||||
* in an off-screen buffer, which is then drawn to match the size of the virtual display window.
|
||||
*
|
||||
* TODO: Whenever there are borders, they should be filled with the CGA's overscan colors. However,
|
||||
* in the case of graphics modes (and text modes whenever font scaling is enabled), we don't reserve
|
||||
* any space for borders, so if borders are important, explicit support will be required.
|
||||
*/
|
||||
|
||||
/*
|
||||
* EGA Support
|
||||
*
|
||||
* EGA support piggy-backs on the existing MDA/CGA support. All the existing MDA/CGA port handlers
|
||||
* now refer to either cardMono or cardColor (instead of directly to cardMDA or cardCGA), enabling
|
||||
* the handlers to be redirected to cardMDA, cardCGA or cardEGA as appropriate.
|
||||
*
|
||||
* Note that an MDA card supported only a Monochrome Display and a CGA card supported only a Color
|
||||
* Display (well, OK, *or* a TV monitor, which we don't currently support), but the EGA is much
|
||||
* more flexible: the Enhanced Color Display was the preferred display, but the EGA also supported
|
||||
* older displays; a Color Display on EGA wasn't ideal (same low resolutions but with more colors),
|
||||
* but the EGA also brought high-resolution graphics to Monochrome displays, which was nice. Anyway,
|
||||
* while all those EGA/monitor combinations will be nice to support, our virtual display support
|
||||
* will focus initially on the Enhanced Color Display.
|
||||
*
|
||||
* TODO: Add support for jumpers P1 and P3 (see EGA TechRef p.85). P1 selects either 5-color-output
|
||||
* for a CGA monitor or 6-color-output for an EGA monitor; we would presumably use this only to
|
||||
* control certain assumptions about the virtual display's capabilities (ie, Color Display vs. Enhanced
|
||||
* Color Display). P3 can switch all the I/O ports from 0x3nn to 0x2nn; the default is 0x3nn, and
|
||||
* that's the only port range the EGA ROM supports as well.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
|
|
@ -130,6 +68,7 @@ if (typeof module !== 'undefined') {
|
|||
* charRows: number of character rows
|
||||
* fontROM: path to .rom file (or a JSON representation) that defines the character set
|
||||
* screenColor: background color of the screen window (default is black)
|
||||
* autoLock: true to (attempt to) automatically lock the mouse to the canvas (default is false)
|
||||
*
|
||||
* An EGA may specify the following additional properties:
|
||||
*
|
||||
|
|
@ -220,6 +159,13 @@ function Video(parmsVideo, canvas, context, textarea)
|
|||
this.contextScreen = context;
|
||||
this.textareaScreen = textarea;
|
||||
|
||||
/*
|
||||
* If a Mouse exists, we'll be notified when it requests our canvas, and we make a note of it
|
||||
* so that if lockPointer() is ever invoked, we can notify the Mouse.
|
||||
*/
|
||||
this.mouse = null;
|
||||
this.fAutoLock = parmsVideo['autoLock'];
|
||||
|
||||
/*
|
||||
* Originally, setMode() would map/unmap the video buffer ONLY when the active card changed,
|
||||
* because as long as an MDA or CGA remained active, its video buffer never changed. However,
|
||||
|
|
@ -254,13 +200,29 @@ function Video(parmsVideo, canvas, context, textarea)
|
|||
this.fHasFocus = false;
|
||||
|
||||
var video = this;
|
||||
if (this.canvasScreen) {
|
||||
this.canvasScreen.onfocus = function onFocusCanvas() {
|
||||
if (canvas) {
|
||||
canvas.onfocus = function onFocusCanvas() {
|
||||
return video.onFocusChange(true);
|
||||
};
|
||||
this.canvasScreen.onblur = function onBlurCanvas() {
|
||||
canvas.onblur = function onBlurCanvas() {
|
||||
return video.onFocusChange(false);
|
||||
};
|
||||
canvas.lockPointer = canvas['requestPointerLock'] || canvas['mozRequestPointerLock'] || canvas['webkitRequestPointerLock'];
|
||||
canvas.unlockPointer = canvas['exitPointerLock'] || canvas['mozExitPointerLock'] || canvas['webkitExitPointerLock'];
|
||||
var onPointerLockChange = function() {
|
||||
var fLocked = (
|
||||
document['pointerLockElement'] === video.canvasScreen ||
|
||||
document['mozPointerLockElement'] === video.canvasScreen ||
|
||||
document['webkitPointerLockElement'] === video.canvasScreen);
|
||||
video.notifyPointerLocked(fLocked);
|
||||
};
|
||||
if ('onpointerlockchange' in document) {
|
||||
document.addEventListener('pointerlockchange', onPointerLockChange, false);
|
||||
} else if ('onmozpointerlockchange' in document) {
|
||||
document.addEventListener('mozpointerlockchange', onPointerLockChange, false);
|
||||
} else if ('onwebkitpointerlockchange' in document) {
|
||||
document.addEventListener('webkitpointerlockchange', onPointerLockChange, false);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -300,6 +262,66 @@ Component.subclass(Component, Video);
|
|||
|
||||
Video.TRAPALL = true; // monitor all I/O by default (not just deltas)
|
||||
|
||||
/*
|
||||
* MDA/CGA Support
|
||||
*
|
||||
* This component implements a combination of the MDA and CGA cards, with the ability to
|
||||
* dynamically switch between the two, by simply toggling the SW1 motherboard switch settings
|
||||
* and resetting the virtual machine. As a result, this component always installs I/O port
|
||||
* handlers for both MDA and CGA, regardless which card is initially active.
|
||||
*
|
||||
* Since there's a lot of similarity between the two cards (eg, their text-mode video buffer
|
||||
* format, and their use of the 6845 CRT controller), since the MDA ROM contains the fonts
|
||||
* used by both devices, and since the same ROM BIOS supports both (in fact, the BIOS rather
|
||||
* indiscriminately initializes both, regardless which is actually installed), I decided to
|
||||
* use the same component to emulate both devices. Note that it was possible for an IBM PC
|
||||
* to have both an MDA and CGA running in the same machine, so if that's something we want to
|
||||
* support later, it can be done by instantiating this component twice, with some additional
|
||||
* properties to force each instance to register only those I/O ports belonging to its specific
|
||||
* configuration.
|
||||
*
|
||||
* While supporting both devices is mostly a convenience, it also creates a few inconveniences.
|
||||
* For one, the MDA prefers a native window size of 720x350, as it supports only one video mode,
|
||||
* 80x25, with a 9x14 cell size. The CGA, on the other hand, has an 8x8 cell size, so when using
|
||||
* an MDA-size window, an 80x25 CGA screen will end up with 40-pixel borders on the left and right,
|
||||
* and 75-pixel borders on the top and bottom. The result is a rather tiny CGA font surrounded
|
||||
* by lots of wasted space, so it's best to turn on font scaling (see the "scale" property) and
|
||||
* go with a larger window size of, say, 960x400 (50% larger in width, 100% larger in height).
|
||||
*
|
||||
* I've also added support for font-doubling in createFont(). We use the 8x8 font for 80-column
|
||||
* modes and the "doubled" 16x16 font for 40-column modes OR whenever the screen is large enough
|
||||
* to use the 16x16 font, since font rendering without scaling provides the sharpest results.
|
||||
* In fact, there's special logic in setDimensions() to ignore fScaleFont in certain cases (eg,
|
||||
* 40-column modes, to improve sharpness and avoid stretching the font beyond readability).
|
||||
*
|
||||
* Graphics modes, on the other hand, are always scaled to the window size. Pixels are captured
|
||||
* in an off-screen buffer, which is then drawn to match the size of the virtual display window.
|
||||
*
|
||||
* TODO: Whenever there are borders, they should be filled with the CGA's overscan colors. However,
|
||||
* in the case of graphics modes (and text modes whenever font scaling is enabled), we don't reserve
|
||||
* any space for borders, so if borders are important, explicit support will be required.
|
||||
*
|
||||
* EGA Support
|
||||
*
|
||||
* EGA support piggy-backs on the existing MDA/CGA support. All the existing MDA/CGA port handlers
|
||||
* now refer to either cardMono or cardColor (instead of directly to cardMDA or cardCGA), enabling
|
||||
* the handlers to be redirected to cardMDA, cardCGA or cardEGA as appropriate.
|
||||
*
|
||||
* Note that an MDA card supported only a Monochrome Display and a CGA card supported only a Color
|
||||
* Display (well, OK, *or* a TV monitor, which we don't currently support), but the EGA is much
|
||||
* more flexible: the Enhanced Color Display was the preferred display, but the EGA also supported
|
||||
* older displays; a Color Display on EGA wasn't ideal (same low resolutions but with more colors),
|
||||
* but the EGA also brought high-resolution graphics to Monochrome displays, which was nice. Anyway,
|
||||
* while all those EGA/monitor combinations will be nice to support, our virtual display support
|
||||
* will focus initially on the Enhanced Color Display.
|
||||
*
|
||||
* TODO: Add support for jumpers P1 and P3 (see EGA TechRef p.85). P1 selects either 5-color-output
|
||||
* for a CGA monitor or 6-color-output for an EGA monitor; we would presumably use this only to
|
||||
* control certain assumptions about the virtual display's capabilities (ie, Color Display vs. Enhanced
|
||||
* Color Display). P3 can switch all the I/O ports from 0x3nn to 0x2nn; the default is 0x3nn, and
|
||||
* that's the only port range the EGA ROM supports as well.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Supported Cards
|
||||
*
|
||||
|
|
@ -1984,7 +2006,23 @@ Video.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
Video.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
||||
{
|
||||
var video = this;
|
||||
var canvas, lockPointer;
|
||||
|
||||
switch (sBinding) {
|
||||
|
||||
case "lockPointer":
|
||||
this.bindings[sBinding] = control;
|
||||
if (this.canvasScreen && this.canvasScreen.lockPointer) {
|
||||
control.onclick = function onClickLockPointer() {
|
||||
if (DEBUG) video.messageDebugger("lockPointer()");
|
||||
video.lockPointer(true);
|
||||
};
|
||||
} else {
|
||||
if (DEBUG) this.log("Pointer Lock API not available");
|
||||
control.parentNode.removeChild(control);
|
||||
}
|
||||
return true;
|
||||
|
||||
case "refresh":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function onClickRefresh() {
|
||||
|
|
@ -1992,6 +2030,7 @@ Video.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
|
|||
video.updateScreen(true);
|
||||
};
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -2014,13 +2053,73 @@ Video.prototype.setFocus = function()
|
|||
* This is an interface used by the Mouse component, so that it can invoke capture/release mouse events from the screen element.
|
||||
*
|
||||
* @this {Video}
|
||||
* @param {Mouse} [mouse]
|
||||
* @return {Object|undefined}
|
||||
*/
|
||||
Video.prototype.getCanvas = function()
|
||||
Video.prototype.getCanvas = function(mouse)
|
||||
{
|
||||
this.mouse = mouse;
|
||||
return this.canvasScreen;
|
||||
};
|
||||
|
||||
/**
|
||||
* lockPointer()
|
||||
*
|
||||
* TODO: Consider relabeling the "Lock Pointer" button for the duration of the lock.
|
||||
*
|
||||
* @this {Video}
|
||||
* @param {boolean} fLock
|
||||
* @return {boolean} true if request successful, false if not (eg, failed OR not supported)
|
||||
*/
|
||||
Video.prototype.lockPointer = function(fLock)
|
||||
{
|
||||
if (this.canvasScreen) {
|
||||
if (fLock) {
|
||||
if (this.canvasScreen.lockPointer) {
|
||||
this.canvasScreen.lockPointer();
|
||||
this.mouse.notifyPointerLocked(true);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (this.canvasScreen.unlockPointer) {
|
||||
this.canvasScreen.unlockPointer();
|
||||
this.mouse.notifyPointerLocked(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* notifyPointerActive(fActive)
|
||||
*
|
||||
* @this {Video}
|
||||
* @param {boolean} fActive
|
||||
* @return {boolean} true if autolock enabled AND pointer lock supported, false if not
|
||||
*/
|
||||
Video.prototype.notifyPointerActive = function(fActive)
|
||||
{
|
||||
if (this.fAutoLock) {
|
||||
return this.lockPointer(fActive);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* notifyPointerLocked(fLocked)
|
||||
*
|
||||
* @this {Video}
|
||||
* @param {boolean} fLocked
|
||||
*/
|
||||
Video.prototype.notifyPointerLocked = function(fLocked)
|
||||
{
|
||||
if (this.mouse) {
|
||||
this.mouse.notifyPointerLocked(fLocked);
|
||||
if (this.kbd) this.kbd.notifyEscape(fLocked);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* captureTouch()
|
||||
*
|
||||
|
|
|
|||
|
|
@ -14,11 +14,14 @@
|
|||
.pcjs-embed:after {
|
||||
clear:both;
|
||||
}
|
||||
.pcjs-name {
|
||||
.pcjs-name, .pcjs-menu {
|
||||
clear: both;
|
||||
font-weight: bold;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.pcjs-menu {
|
||||
float: left;
|
||||
}
|
||||
.pcjs-canvas {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
|
|
|
|||
|
|
@ -196,6 +196,12 @@
|
|||
<xsl:apply-templates select="name" mode="component"/>
|
||||
</xsl:if>
|
||||
<div class="{$APPCLASS}-container" style="{$border}{$style}">
|
||||
<xsl:if test="$component = 'machine'">
|
||||
<xsl:apply-templates select="menu" mode="machine"/>
|
||||
</xsl:if>
|
||||
<xsl:if test="$component != 'machine'">
|
||||
<xsl:apply-templates select="menu" mode="component"/>
|
||||
</xsl:if>
|
||||
<xsl:if test="$class != '' and $component != 'machine'">
|
||||
<div class="{$APPCLASS}-{$class}-object" data-value="id:'{$id}',name:'{$name}'{$comment}{$parms}"> </div>
|
||||
</xsl:if>
|
||||
|
|
@ -236,6 +242,14 @@
|
|||
<div class="{$APPCLASS}-name"><xsl:apply-templates/></div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="menu" mode="component">
|
||||
<xsl:apply-templates mode="component"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="title" mode="component">
|
||||
<div class="{$APPCLASS}-menu"><xsl:apply-templates/></div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="control" mode="component">
|
||||
<xsl:variable name="type">
|
||||
<xsl:text>type:'</xsl:text><xsl:value-of select="@type"/><xsl:text>'</xsl:text>
|
||||
|
|
@ -481,6 +495,9 @@
|
|||
<xsl:template match="name">
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="title">
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="control">
|
||||
</xsl:template>
|
||||
|
||||
|
|
@ -884,10 +901,16 @@
|
|||
<xsl:otherwise>false</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:variable name="autoLock">
|
||||
<xsl:choose>
|
||||
<xsl:when test="@autolock"><xsl:value-of select="@autolock"/></xsl:when>
|
||||
<xsl:otherwise>false</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:variable>
|
||||
<xsl:call-template name="component">
|
||||
<xsl:with-param name="machine" select="$machine"/>
|
||||
<xsl:with-param name="class">video</xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',mode:<xsl:value-of select="$mode"/>,screenWidth:<xsl:value-of select="$screenWidth"/>,screenHeight:<xsl:value-of select="$screenHeight"/>,memory:<xsl:value-of select="$memory"/>,switches:'<xsl:value-of select="$switches"/>',scale:<xsl:value-of select="$scale"/>,charCols:<xsl:value-of select="$charCols"/>,charRows:<xsl:value-of select="$charRows"/>,fontROM:'<xsl:value-of select="$fontROM"/>',screenColor:'<xsl:value-of select="$screenColor"/>',touchScreen:<xsl:value-of select="$touchScreen"/></xsl:with-param>
|
||||
<xsl:with-param name="parms">,model:'<xsl:value-of select="$model"/>',mode:<xsl:value-of select="$mode"/>,screenWidth:<xsl:value-of select="$screenWidth"/>,screenHeight:<xsl:value-of select="$screenHeight"/>,memory:<xsl:value-of select="$memory"/>,switches:'<xsl:value-of select="$switches"/>',scale:<xsl:value-of select="$scale"/>,charCols:<xsl:value-of select="$charCols"/>,charRows:<xsl:value-of select="$charRows"/>,fontROM:'<xsl:value-of select="$fontROM"/>',screenColor:'<xsl:value-of select="$screenColor"/>',touchScreen:<xsl:value-of select="$touchScreen"/>,autoLock:<xsl:value-of select="$autoLock"/></xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:template>
|
||||
|
||||
|
|
|
|||
|
|
@ -498,6 +498,8 @@ Component.bindExternalControl = function(component, sControl, sBinding, sType)
|
|||
}
|
||||
};
|
||||
|
||||
if (window && !window.document.ELEMENT_NODE) window.document.ELEMENT_NODE = 1;
|
||||
|
||||
/**
|
||||
* Component.bindComponentControls(component, element, sAppClass)
|
||||
*
|
||||
|
|
@ -888,9 +890,4 @@ Component.prototype = {
|
|||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* TODO: What was this work-around for? I forget....
|
||||
*/
|
||||
if (window && !window.document.ELEMENT_NODE) window.document.ELEMENT_NODE = 1;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = Component;
|
||||
|
|
|
|||
Loading…
Reference in a new issue