pcjs/modules/pcx86/lib/mouse.js
2017-08-25 09:15:21 -07:00

992 lines
38 KiB
JavaScript

/**
* @fileoverview Implements the PCx86 Mouse component.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @copyright © Jeff Parsons 2012-2017
*
* This file is part of PCjs, a computer emulation software project at <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 modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <http://pcjs.org/modules/shared/lib/defines.js>.
*
* 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 State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Messages = require("./messages");
var SerialPort = require("./serial");
}
/**
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
* which would force us to declare all class properties in the constructor, as well as prevent
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
*
* @unrestricted
*/
class Mouse extends Component {
/**
* Mouse(parmsMouse)
*
* The Mouse component has the following component-specific (parmsMouse) properties:
*
* adapter: 1 (primary) or 2 (secondary); 0 if not defined
*
* binding: name of a corresponding device component (implies type="serial")
*
* scaleMouse: a floating-point number used to scale incoming mouse coordinates; the default is 0.5
*
* serial: the ID of a corresponding serial component (used in lieu of type="serial" and binding="ID")
*
* type: one of "bus", "inport", or "serial"; the default is "serial" if serial or binding properties are set
*
* The first version of this component supported ONLY emulation of the original Microsoft serial mouse,
* so a valid SerialPort component ID using the 'serial' property was required. Now, using the 'type' property,
* it's possible to enable support for other types of mouse hardware (eg, 'bus' for the original Microsoft
* Bus Mouse interface or 'inport' for the Microsoft InPort Mouse interface). The 'adapter' property is used
* only when the selected type supports different configurations (eg, primary vs. secondary InPort adapters).
*
* If the 'type' property is set to "serial" (or 'type' is not set and either the original 'serial' property
* or the new 'binding' property is set), then serial communication will be established with the specified
* SerialPort component, requesting access to the corresponding serial component ID. If the SerialPort component
* is not installed and/or the specified serial component ID is not present, a configuration error will be reported.
*
* To recap, the following machine XML syntax is still supported:
*
* <mouse serial="com2"/>
*
* but going forward, you should stop using the serial attribute and use syntax like this instead:
*
* <mouse type="serial" binding="com2"/>
*
* @this {Mouse}
* @param {Object} parmsMouse
*/
constructor(parmsMouse)
{
super("Mouse", parmsMouse, Messages.MOUSE);
this.iAdapter = parmsMouse['adapter'] || 0;
this.idDevice = parmsMouse['serial'] || parmsMouse['binding'];
this.sType = parmsMouse['type'] || (this.idDevice? Mouse.TYPE.SERIAL : Mouse.TYPE.BUS);
this.typeDevice = (this.sType == Mouse.TYPE.SERIAL? "SerialPort" : null);
this.componentDevice = null;
this.scale = parmsMouse['scaleMouse'];
this.setActive(false);
this.fActive = this.fCaptured = this.fLocked = false;
/*
* Initially, no video devices, and therefore no screens, are attached. initBus() will update aVideo,
* and powerUp() will update aScreens.
*/
this.aVideo = [];
this.aScreens = [];
}
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {Mouse}
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {DebuggerX86} dbg
*/
initBus(cmp, bus, cpu, dbg)
{
this.cmp = cmp;
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.scale = cmp.getMachineParm('scaleMouse') || this.scale;
/*
* Enumerate all the Video components that we may need to interact with.
*/
for (var video = null; (video = cmp.getMachineComponent("Video", video));) {
this.aVideo.push(video);
}
if (this.sType == Mouse.TYPE.BUS) {
bus.addPortInputTable(this, Mouse.aBusInput, Mouse.BUS.DATA.PORT);
bus.addPortOutputTable(this, Mouse.aBusOutput, Mouse.BUS.DATA.PORT);
}
this.setReady();
}
/**
* isActive()
*
* @this {Mouse}
* @return {boolean} true if active, false if not
*/
isActive()
{
return this.fActive && (this.cpu? this.cpu.isRunning() : false);
}
/**
* setActive(fActive)
*
* @this {Mouse}
* @param {boolean} fActive is true if active, false if not
*/
setActive(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 screen click), so this code is for naught.
*
* if (this.aVideo.length) this.aVideo[0].notifyPointerActive(fActive);
*
* We now rely on similar code in clickMouse().
*/
}
/**
* powerUp(data, fRepower)
*
* @this {Mouse}
* @param {Object|null} data
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
powerUp(data, fRepower)
{
if (!fRepower) {
if (!data || !this.restore) {
this.reset();
} else {
if (!this.restore(data)) return false;
}
if (this.typeDevice && !this.componentDevice) {
var componentDevice = null;
while ((componentDevice = this.cmp.getMachineComponent(this.typeDevice, componentDevice))) {
if (componentDevice.attachMouse) {
this.componentDevice = componentDevice.attachMouse(this.idDevice, this, this.receiveStatus);
if (this.componentDevice) {
/*
* It's possible that the SerialPort we've just attached to might want to bring us "up to speed"
* on the device's state, which is why I envisioned a subsequent syncMouse() call. And you would
* want to do that as a separate call, not as part of attachMouse(), because componentDevice
* isn't set until attachMouse() returns.
*
* However, syncMouse() seems unnecessary, given that SerialPort initializes its MCR to an "inactive"
* state, and even when restoring a previous state, if we've done our job properly, both SerialPort
* and Mouse should be restored in sync, making any explicit attempt at sync'ing unnecessary (or so I hope).
*/
// this.componentDevice.syncMouse();
break;
}
}
}
if (this.componentDevice) {
this.aScreens = []; // ensure the screen array is empty before (re)filling it
for (var i = 0; i < this.aVideo.length; i++) {
var screen = this.aVideo[i].getScreen(this);
if (screen) this.aScreens.push(screen);
}
} else {
Component.warning(this.id + ": " + this.typeDevice + " " + this.idDevice + " unavailable");
}
}
if (this.fActive) {
this.captureAll();
} else {
this.releaseAll();
}
}
return true;
}
/**
* powerDown(fSave, fShutdown)
*
* @this {Mouse}
* @param {boolean} [fSave]
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/
powerDown(fSave, fShutdown)
{
return fSave? this.save() : true;
}
/**
* reset()
*
* @this {Mouse}
*/
reset()
{
this.initState();
}
/**
* save()
*
* This implements save support for the Mouse component.
*
* @this {Mouse}
* @return {Object}
*/
save()
{
var state = new State(this);
state.set(0, this.saveState());
return state.data();
}
/**
* restore(data)
*
* This implements restore support for the Mouse component.
*
* @this {Mouse}
* @param {Object} data
* @return {boolean} true if successful, false if failure
*/
restore(data)
{
return this.initState(data[0]);
}
/**
* initState(data)
*
* @this {Mouse}
* @param {Array} [data]
* @return {boolean} true if successful, false if failure
*/
initState(data)
{
var i = 0;
if (data === undefined) data = [false, -1, -1, 0, 0, false, false, 0];
this.setActive(data[i++]);
this.xMouse = data[i++];
this.yMouse = data[i++];
this.xDelta = data[i++];
this.yDelta = data[i++];
this.fButton1 = data[i++]; // FYI, we consider button1 to be the LEFT button
this.fButton2 = data[i++]; // FYI, we consider button2 to be the RIGHT button
this.pins = data[i];
/*
* Convert old UART "MCR" data to new RS-232 "pins" data, in case we're loading an old state;
* detection and conversion relies on the fact that the MCR bits don't overlap with any RS-232 bits.
*/
if (this.pins & (SerialPort.MCR.DTR | SerialPort.MCR.RTS)) {
this.pins = ((this.pins & SerialPort.MCR.DTR)? RS232.DTR.MASK : 0) | ((this.pins & SerialPort.MCR.RTS)? RS232.RTS.MASK : 0);
}
return true;
}
/**
* saveState()
*
* @this {Mouse}
* @return {Array}
*/
saveState()
{
var i = 0;
var data = [];
data[i++] = this.fActive;
data[i++] = this.xMouse;
data[i++] = this.yMouse;
data[i++] = this.xDelta;
data[i++] = this.yDelta;
data[i++] = this.fButton1;
data[i++] = this.fButton2;
data[i] = this.pins;
return data;
}
/**
* notifyPointerLocked()
*
* @this {Mouse}
* @param {boolean} fLocked
*/
notifyPointerLocked(fLocked)
{
this.fLocked = fLocked;
}
/**
* captureAll()
*
* @this {Mouse}
*/
captureAll()
{
if (!this.fCaptured) {
for (var i = 0; i < this.aScreens.length; i++) {
if (this.captureMouse(this.aScreens[i])) this.fCaptured = true;
}
}
}
/**
* releaseAll()
*
* @this {Mouse}
*/
releaseAll()
{
if (this.fCaptured) {
for (var i = 0; i < this.aScreens.length; i++) {
if (this.releaseMouse(this.aScreens[i])) this.fCaptured = false;
}
}
}
/**
* captureMouse(control)
*
* NOTE: addEventListener() wasn't supported in Internet Explorer until IE9, but that's OK, because
* IE9 is the oldest IE we support anyway (since versions prior to IE9 lack the necessary HTML5 support).
*
* @this {Mouse}
* @param {HTMLElement} control from the HTML DOM (eg, the control for the simulated screen)
* @return {boolean} true if event handlers were actually added, false if not
*/
captureMouse(control)
{
if (control) {
var mouse = this;
control.addEventListener(
'mousemove',
function onMouseMove(event) {
mouse.processMouseEvent(event);
},
false // we'll specify false for the 'useCapture' parameter for now...
);
control.addEventListener(
'mousedown',
function onMouseDown(event) {
mouse.processMouseEvent(event, true);
},
false // we'll specify false for the 'useCapture' parameter for now...
);
control.addEventListener(
'mouseup',
function onMouseUp(event) {
mouse.processMouseEvent(event, false);
},
false // we'll specify false for the 'useCapture' parameter for now...
);
/*
* None of these tricks seemed to work for IE10, so I'm giving up hiding the browser's mouse pointer in IE for now.
*
* control['style']['cursor'] = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjbQg61aAAAADUlEQVQYV2P4//8/IwAI/QL/+TZZdwAAAABJRU5ErkJggg=='), url('/versions/images/current/blank.cur'), none";
*
* Setting the cursor style to "none" may not be a standard, but it works in Safari, Firefox and Chrome, so that's pretty
* good for a non-standard!
*
* TODO: The reference to '/versions/images/current/blank.cur' is also problematic for anyone who might want
* to run this app from a different server, so think about that as well.
*/
control['style']['cursor'] = "none";
return true;
}
return false;
}
/**
* releaseMouse(control)
*
* TODO: Use removeEventListener() to clean up our handlers; since I'm currently using anonymous functions,
* and since I'm not seeing any compelling reason to remove the handlers once they've been established, it's
* less code to leave them in place.
*
* @this {Mouse}
* @param {HTMLElement} control from the HTML DOM
* @return {boolean} true if event handlers were actually released, false if not
*/
releaseMouse(control)
{
if (control) {
control['style']['cursor'] = "auto";
}
return false;
}
/**
* processMouseEvent(event, fDown)
*
* @this {Mouse}
* @param {Object} event object from a 'mousemove', 'mousedown' or 'mouseup' event (ie, a MouseEvent object)
* @param {boolean} [fDown] (undefined if neither a down nor up event)
*/
processMouseEvent(event, fDown)
{
if (fDown !== undefined) {
if (this.fLocked === false) {
/*
* If there's no support for automatic pointer locking in the Video component, notifyPointerActive()
* will return false, and we will set fLocked to null, ensuring that we never attempt this again.
*/
if (!this.aVideo.length || !this.aVideo[0].notifyPointerActive(true)) {
this.fLocked = null;
}
}
this.clickMouse(event.button, fDown);
} else {
/*
* All we really care about are deltas. We record screenX and screenY (as xMouse and yMouse)
* merely to calculate xDelta and yDelta.
*/
var xDelta, yDelta;
if (this.xMouse < 0 || this.yMouse < 0) {
this.xMouse = event.screenX;
this.yMouse = event.screenY;
}
if (this.fLocked) {
xDelta = event['movementX'] || event['mozMovementX'] || event['webkitMovementX'] || 0;
yDelta = event['movementY'] || event['mozMovementY'] || event['webkitMovementY'] || 0;
} else {
xDelta = event.screenX - this.xMouse;
yDelta = event.screenY - this.yMouse;
}
this.xMouse = event.screenX;
this.yMouse = event.screenY;
this.moveMouse(xDelta, yDelta, this.xMouse, this.yMouse);
}
}
/**
* clickMouse(iButton, fDown)
*
* @this {Mouse}
* @param {number} iButton is Mouse.BUTTON.LEFT (0) for fButton1, Mouse.BUTTON.RIGHT (2) for fButton2
* @param {boolean} fDown
*/
clickMouse(iButton, fDown)
{
if (this.isActive()) {
var sDiag = DEBUGGER? ("mouse button" + iButton + ' ' + (fDown? "dn" : "up")) : null;
switch (iButton) {
case Mouse.BUTTON.LEFT:
if (this.fButton1 != fDown) {
this.fButton1 = fDown;
this.sendPacket(sDiag);
return;
}
break;
case Mouse.BUTTON.RIGHT:
if (this.fButton2 != fDown) {
this.fButton2 = fDown;
this.sendPacket(sDiag);
return;
}
break;
default:
break;
}
this.printMessage(sDiag + ": ignored");
}
}
/**
* moveMouse(xDelta, yDelta, xDiag, yDiag)
*
* @this {Mouse}
* @param {number} xDelta
* @param {number} yDelta
* @param {number} [xDiag]
* @param {number} [yDiag]
*/
moveMouse(xDelta, yDelta, xDiag, yDiag)
{
if (this.isActive()) {
/*
* I would prefer to simply say "Math.round(xDelta * this.scale)", but JavaScript's round() function
* rounds negative numbers toward +infinity if the fraction is exactly 0.5. All positive numbers are
* rounded correctly, so we convert the value to positive and restore its sign afterward. Additionally,
* if the scaling factor turns a non-zero value into zero, we restore the value to its smallest legal
* non-zero value (thanks to Math.sign() again). This ensures that tiniest movement of the physical
* mouse always results in at least the tiniest movement of the virtual mouse.
*/
var xScaled = (Math.round(Math.abs(xDelta) * this.scale) * Math.sign(xDelta)) || Math.sign(xDelta);
var yScaled = (Math.round(Math.abs(yDelta) * this.scale) * Math.sign(yDelta)) || Math.sign(yDelta);
if (xScaled || yScaled) {
if (this.messageEnabled(Messages.MOUSE)) {
this.printMessage("moveMouse(" + xScaled + "," + yScaled + ")");
}
/*
* As sendPacket() indicates, any x and y coordinates we supply are for diagnostic purposes only.
* sendPacket() only cares about the xDelta and yDelta properties we provide above, which it then zeroes
* on completion.
*/
this.xDelta = xScaled;
this.yDelta = yScaled;
this.sendPacket(null, xDiag, yDiag);
}
}
}
/**
* sendPacket(sDiag, xDiag, yDiag)
*
* If we're called, something changed.
*
* Let's review the 3-byte packet format:
*
* D7 D6 D5 D4 D3 D2 D1 D0
* Byte 1 X 1 LB RB Y7 Y6 X7 X6
* Byte 2 X 0 X5 X4 X3 X2 X1 X0
* Byte 3 X 0 Y5 Y4 Y3 Y2 Y1 Y0
*
* @this {Mouse}
* @param {string|null} [sDiag] diagnostic message
* @param {number} [xDiag] original x-coordinate (optional; for diagnostic use only)
* @param {number} [yDiag] original y-coordinate (optional; for diagnostic use only)
*/
sendPacket(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;
if (this.messageEnabled(Messages.SERIAL)) {
this.printMessage((sDiag? (sDiag + ": ") : "") + (yDiag !== undefined? ("mouse (" + xDiag + "," + yDiag + "): ") : "") + "serial packet [" + Str.toHexByte(b1) + "," + Str.toHexByte(b2) + "," + Str.toHexByte(b3) + "]", 0, true);
}
this.componentDevice.receiveData([b1, b2, b3]);
this.xDelta = this.yDelta = 0;
}
/**
* receiveStatus(pins)
*
* The SerialPort notifies us whenever SerialPort.MCR.DTR or SerialPort.MCR.RTS changes.
*
* During normal serial mouse operation, both RTS and DTR must be "positive".
*
* Setting RTS "negative" for 100ms resets the mouse. Toggling DTR requests an identification byte (SERIAL.ID).
*
* NOTES: The above 3rd-party information notwithstanding, I've observed that Windows v1.01 initially writes 0x01
* to the MCR (DTR on, RTS off), spins in a loop that reads the RBR (probably to avoid a bogus identification byte
* sitting in the RBR), and then writes 0x0B to the MCR (DTR on, RTS on). This last step is consistent with making
* the mouse "active", but it is NOT consistent with "toggling DTR", so I conclude that a reset is ALSO sufficient
* for sending the identification byte. Right or wrong, this gets the ball rolling for Windows v1.01.
*
* @this {Mouse}
* @param {number} pins
*/
receiveStatus(pins)
{
var fActive = ((pins & (RS232.DTR.MASK | RS232.RTS.MASK)) == (RS232.DTR.MASK | RS232.RTS.MASK));
if (fActive) {
if (!this.fActive) {
var fIdentify = false;
if (!(this.pins & RS232.RTS.MASK)) {
this.reset();
this.printMessage("serial mouse reset");
fIdentify = true;
}
if (!(this.pins & RS232.DTR.MASK)) {
this.printMessage("serial mouse ID requested");
fIdentify = true;
}
if (fIdentify) {
/*
* HEADS UP: Everything I'd read about the (original) Microsoft Serial Mouse "reset" protocol says
* that the device sends a single byte (0x4D aka 'M'). It's not surprising to think that newer mice
* might send additional bytes, but you would think that newer mouse drivers (eg, MOUSE.COM v8.20)
* would always be able to deal with mice that sent only one byte.
*
* You would be wrong. On an INT 0x33 reset, the v8.20 driver looks for an 'M', then it waits for
* another byte (0x42 aka 'B'). If it doesn't receive a 'B', it will accept another 'M'. But if it
* receives something else (or nothing at all), it will spend a long time waiting for it, and then
* return an error.
*
* It's entirely possible that I've done something wrong and inadvertently "tricked" MOUSE.COM into
* using the wrong detection logic. But given the other problems I've seen in MOUSE.COM v8.20, including
* its failure to properly terminate-and-stay-resident when its initial INT 0x33 reset returns an error,
* I'm not in the mood to give it the benefit of the doubt.
*
* So, anyway, I solve the terminate-and-stay-resident bug in MOUSE.COM v8.20 by feeding it *two* SERIAL.ID
* bytes on a reset. This doesn't seem to adversely affect serial mouse emulation for Windows 1.01, so
* I'm calling this good enough for now.
*/
this.componentDevice.receiveData([Mouse.SERIAL.ID, Mouse.SERIAL.ID]);
this.printMessage("serial mouse ID sent");
}
this.captureAll();
this.setActive(fActive);
}
} else {
if (this.fActive) {
/*
* Although this would seem nice (ie, for the Windows v1.01 mouse driver to turn RTS off when its mouse
* driver shuts down and Windows exits, since it DID turn RTS on), that doesn't appear to actually happen.
* At the very least, Windows will have (re)masked the serial port's IRQ, so what does it matter? Not much,
* I just would have preferred that fActive properly reflect whether we should continue dispatching mouse
* events, displaying MOUSE messages, etc.
*
* We could ask the ChipSet component to notify the SerialPort component whenever its IRQ is masked/unmasked,
* and then have the SerialPort pass that notification on to us, but I'm assuming that in the real world,
* a mouse device that's still powered may still send event data to the serial port, and if there was software
* polling the serial port, it might expect to see that data. Unlikely, but not impossible.
*/
this.printMessage("serial mouse inactive");
this.releaseAll();
this.setActive(fActive);
}
}
this.pins = pins;
}
/**
* inBusData(port, addrFrom)
*
* @this {Mouse}
* @param {number} port (eg, 0x23C)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
inBusData(port, addrFrom)
{
var b = 0;
this.printMessageIO(port, null, addrFrom, "DATA", b);
return b;
}
/**
* inBusTPPI(port, addrFrom)
*
* @this {Mouse}
* @param {number} port (eg, 0x23D)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
inBusTPPI(port, addrFrom)
{
var b = 0;
this.printMessageIO(port, null, addrFrom, "TPPI", b);
return b;
}
/**
* inBusCtrl(port, addrFrom)
*
* @this {Mouse}
* @param {number} port (eg, 0x23E)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
inBusCtrl(port, addrFrom)
{
var b = 0;
this.printMessageIO(port, null, addrFrom, "CTRL", b);
return b;
}
/**
* inBusCPPI(port, addrFrom)
*
* @this {Mouse}
* @param {number} port (eg, 0x23F)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
inBusCPPI(port, addrFrom)
{
var b = 0;
this.printMessageIO(port, null, addrFrom, "CPPI", b);
return b;
}
/**
* outBusData(port, bOut, addrFrom)
*
* @this {Mouse}
* @param {number} port (eg, 0x23C)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
outBusData(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, "DATA");
}
/**
* outBusTPPI(port, bOut, addrFrom)
*
* @this {Mouse}
* @param {number} port (eg, 0x23D)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
outBusTPPI(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, "TPPI");
}
/**
* outBusCtrl(port, bOut, addrFrom)
*
* @this {Mouse}
* @param {number} port (eg, 0x23E)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
outBusCtrl(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, "CTRL");
}
/**
* outBusCPPI(port, bOut, addrFrom)
*
* @this {Mouse}
* @param {number} port (eg, 0x23F)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to write the specified port)
*/
outBusCPPI(port, bOut, addrFrom)
{
this.printMessageIO(port, bOut, addrFrom, "CPPI");
}
/**
* Mouse.init()
*
* This function operates on every HTML element of class "mouse", extracting the
* JSON-encoded parameters for the Mouse constructor from the element's "data-value"
* attribute, invoking the constructor to create a Mouse component, and then binding
* any associated HTML controls to the new component.
*/
static init()
{
var aeMouse = Component.getElementsByClass(document, PCX86.APPCLASS, "mouse");
for (var iMouse = 0; iMouse < aeMouse.length; iMouse++) {
var eMouse = aeMouse[iMouse];
var parmsMouse = Component.getComponentParms(eMouse);
var mouse = new Mouse(parmsMouse);
Component.bindComponentControls(mouse, eMouse, PCX86.APPCLASS);
}
}
}
Mouse.TYPE = {
BUS: "bus",
INPORT: "inport",
SERIAL: "serial"
};
Mouse.BUTTON = {
LEFT: 0,
RIGHT: 2
};
/*
* The Microsoft Bus Mouse supported only one base address: 0x23C.
*
* NOTE: Windows v1.01 probes ports 0x23D and 0x23F immediately prior to probing COM2 (and then COM1)
* for a serial mouse.
*/
Mouse.BUS = {
DATA: { // Mouse Data Register
PORT: 0x23C
},
TPPI: { // 8255 (PPI) Test Register
PORT: 0x23D
},
CTRL: { // Mouse Control Register
PORT: 0x23E
},
CPPI: { // 8255 (PPI) Control Register
PORT: 0x23F
}
};
Mouse.aBusInput = {
0x0: Mouse.prototype.inBusData,
0x1: Mouse.prototype.inBusTPPI,
0x2: Mouse.prototype.inBusCtrl,
0x3: Mouse.prototype.inBusCPPI
};
Mouse.aBusOutput = {
0x0: Mouse.prototype.outBusData,
0x1: Mouse.prototype.outBusTPPI,
0x2: Mouse.prototype.outBusCtrl,
0x3: Mouse.prototype.outBusCPPI
};
/*
* The retail Microsoft InPort card supported two base addresses, 0x23C and 0x238, through the primary and
* secondary jumpers, respectively. However, OEMs may have had InPorts on other base addresses.
*
* Here's a typical InPort Mouse detection sequence:
*
* S = IN(Mouse.INPORT.ID.PORT)
* ...
* VERIFY THAT S EQUALS Mouse.INPORT.ID.CHIP
* T = IN(Mouse.INPORT.ID.PORT)
* ...
* VERIFY ADDITIONAL PAIRS OF READS RETURN MATCHING S AND T VALUES
*
* Here's a typical InPort Mouse interrupt sequence:
*
* OUT(Mouse.INPORT.ADDR.PORT, Mouse.INPORT.ADDR.MODE)
* OUT(Mouse.INPORT.DATA.PORT, IN(Mouse.INPORT.DATA.PORT) | Mouse.INPORT.DATA.MODE.HOLD)
* ...
* OUT(Mouse.INPORT.ADDR.PORT, Mouse.INPORT.ADDR.X)
* X = IN(Mouse.INPORT.DATA.PORT)
* OUT(Mouse.INPORT.ADDR.PORT, Mouse.INPORT.ADDR.Y)
* Y = IN(Mouse.INPORT.DATA.PORT)
* OUT(Mouse.INPORT.ADDR.PORT, Mouse.INPORT.ADDR.STATUS)
* B = IN(Mouse.INPORT.DATA.PORT) & (Mouse.INPORT.DATA.STATUS.B1 | Mouse.INPORT.DATA.STATUS.B2 | Mouse.INPORT.DATA.STATUS.B3)
* ...
* OUT(Mouse.INPORT.ADDR.PORT, Mouse.INPORT.ADDR.MODE)
* OUT(Mouse.INPORT.DATA.PORT, IN(Mouse.INPORT.DATA.PORT) & ~Mouse.INPORT.DATA.MODE.HOLD)
*/
Mouse.INPORT = {
ADDR: {
PORT: 0x23C,
STATUS: 0x00, // InPort Status Register
X: 0x01, // InPort X Movement Register
Y: 0x02, // InPort Y Movement Register
ISTAT: 0x05, // InPort Interface Status Register
ICTRL: 0x06, // InPort Interface Control Register
MODE: 0x07 // InPort Mode Register
},
DATA: {
/*
* The internal register read or written via this port is determined by the value written to ADDR.PORT
*/
PORT: 0x23D,
STATUS: { // InPort Status Register (0)
B3: 0x01, // Status button 3
B2: 0x02, // Status button 2
B1: 0x04, // Status button 1
DB3: 0x08, // Delta button 3
DB2: 0x10, // Delta button 2
DB1: 0x20, // Delta button 1
MOVE: 0x40, // Movement
PACKET: 0x80 // Packet complete
},
MODE: { // InPort Mode Register (7)
HOLD: 0x20 // hold the status for reading
}
},
ID: {
/*
* The initial read returns the Chip ID; alternate reads return a byte containing the InPort revision number
* in the low nibble and the InPort version number in the high nibble.
*/
PORT: 0x23E,
CHIP: 0xDE // InPort Chip ID
},
TEST: {
PORT: 0x23F
}
};
/*
* From http://paulbourke.net/dataformats/serialmouse:
*
* The old MicroSoft serial mouse, while no longer in general use, can be employed to provide a low cost input device,
* for example, coupling the internal mechanism to other moving objects. The serial protocol for the mouse is:
*
* 1200 baud, 7 bit, 1 stop bit, no parity.
*
* The pinout of the connector follows the standard serial interface, as shown below:
*
* Pin Abbr Description
* 1 DCD Data Carrier Detect
* 2 RD Receive Data [serial data from mouse to host]
* 3 TD Transmit Data
* 4 DTR Data Terminal Ready [used to provide positive voltage to mouse, plus reset/detection]
* 5 SG Signal Ground
* 6 DSR Data Set Ready
* 7 RTS Request To Send [used to provide positive voltage to mouse]
* 8 CTS Clear To Send
* 9 RI Ring
*
* Every time the mouse changes state (moved or button pressed) a three byte "packet" is sent to the serial interface.
* For reasons known only to the engineers, the data is arranged as follows, most notably the two high order bits for the
* x and y coordinates share the first byte with the button status.
*
* D6 D5 D4 D3 D2 D1 D0
* 1st byte 1 LB RB Y7 Y6 X7 X6
* 2nd byte 0 X5 X4 X3 X2 X1 X0
* 3rd byte 0 Y5 Y4 Y3 Y2 Y1 Y0
*
* where:
*
* LB is the state of the left button, 1 = pressed, 0 = released.
* RB is the state of the right button, 1 = pressed, 0 = released
* X0-7 is movement of the mouse in the X direction since the last packet. Positive movement is toward the right.
* Y0-7 is movement of the mouse in the Y direction since the last packet. Positive movement is back, toward the user.
*
* From http://www.kryslix.com/nsfaq/Q.12.html:
*
* The Microsoft serial mouse is the most popular 2-button mouse. It is supported by all major operating systems.
* The maximum tracking rate for a Microsoft mouse is 40 reports/second * 127 counts per report, in other words, 5080 counts
* per second. The most common range for mice is is 100 to 400 CPI (counts per inch) but can be up to 1000 CPI. A 100 CPI mouse
* can discriminate motion up to 50.8 inches/second while a 400 CPI mouse can only discriminate motion up to 12.7 inches/second.
*
* 9-pin 25-pin Line Comments
* shell 1 GND
* 3 2 TD Serial data from host to mouse (only for power)
* 2 3 RD Serial data from mouse to host
* 7 4 RTS Positive voltage to mouse
* 8 5 CTS
* 6 6 DSR
* 5 7 SGND
* 4 20 DTR Positive voltage to mouse and reset/detection
*
* To function correctly, both the RTS and DTR lines must be positive. DTR/DSR and RTS/CTS must NOT be shorted.
* RTS may be toggled negative for at least 100ms to reset the mouse. (After a cold boot, the RTS line is usually negative.
* This provides an automatic toggle when RTS is brought positive). When DTR is toggled the mouse should send a single byte
* (0x4D, ASCII 'M').
*
* Serial data parameters: 1200bps, 7 data bits, 1 stop bit
*
* Data is sent in 3 byte packets for each event (a button is pressed or released, or the mouse moves):
*
* D7 D6 D5 D4 D3 D2 D1 D0
* Byte 1 X 1 LB RB Y7 Y6 X7 X6
* Byte 2 X 0 X5 X4 X3 X2 X1 X0
* Byte 3 X 0 Y5 Y4 Y3 Y2 Y1 Y0
*
* LB is the state of the left button (1 means down).
* RB is the state of the right button (1 means down).
* X7-X0 movement in X direction since last packet (signed byte).
* Y7-Y0 movement in Y direction since last packet (signed byte).
* The high order bit of each byte (D7) is ignored. Bit D6 indicates the start of an event, which allows the software to
* synchronize with the mouse.
*/
Mouse.SERIAL = {
ID: 0x4D
};
/*
* Initialize every Mouse module on the page.
*/
Web.onInit(Mouse.init);
if (NODE) module.exports = Mouse;