First round of changes to map Host touch events to Guest mouse events

This commit is contained in:
Jeff Parsons 2015-08-14 23:04:22 -07:00
commit d2fb395e55
170 changed files with 9084 additions and 2096 deletions

View file

@ -169,6 +169,11 @@ Component.subclass(Mouse);
Mouse.ID_SERIAL = 0x4D;
Mouse.BUTTON = {
LEFT: 0,
RIGHT: 2
};
/**
* initBus(cmp, bus, cpu, dbg)
*
@ -430,21 +435,21 @@ Mouse.prototype.captureMouse = function(control)
control.addEventListener(
'mousemove',
function onMouseMove(event) {
mouse.moveMouse(event);
mouse.processMouseEvent(event);
},
false // we'll specify false for the 'useCapture' parameter for now...
);
control.addEventListener(
'mousedown',
function onMouseDown(event) {
mouse.clickMouse(event.button, true);
mouse.processMouseEvent(event, true);
},
false // we'll specify false for the 'useCapture' parameter for now...
);
control.addEventListener(
'mouseup',
function onMouseUp(event) {
mouse.clickMouse(event.button, false);
mouse.processMouseEvent(event, false);
},
false // we'll specify false for the 'useCapture' parameter for now...
);
@ -485,59 +490,15 @@ Mouse.prototype.releaseMouse = function(control)
};
/**
* moveMouse(event)
*
* MouseEvent objects contain, among other things, the following properties:
*
* clientX
* clientY
*
* I've selected the above properties because they're widely supported, not because I need
* 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.
*
* This is because all we care about are deltas. We record clientX and clientY (as xMouse and yMouse)
* merely to calculate xDelta and yDelta.
* processMouseEvent(event, fDown)
*
* @this {Mouse}
* @param {Object} event object from a 'mousemove' event (specifically, a MouseEvent object)
* @param {Object} event object from a 'mousemove', 'mousedown' or 'mouseup' event (specifically, a MouseEvent object)
* @param {boolean} [fDown] (undefined if neither a down nor up event)
*/
Mouse.prototype.moveMouse = function(event)
Mouse.prototype.processMouseEvent = function(event, fDown)
{
if (this.isActive()) {
if (this.xMouse < 0 || this.yMouse < 0) {
this.xMouse = event.clientX;
this.yMouse = event.clientY;
}
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, any x and y coordinates we supply are for diagnostic purposes only.
* sendPacket() only cares about the xDelta and yDelta properties, which it then zeroes on completion.
*/
this.sendPacket(null, event.clientX, event.clientY);
}
this.xMouse = event.clientX;
this.yMouse = event.clientY;
}
};
/**
* clickMouse(iButton, fDown)
*
* @this {Mouse}
* @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)
{
if (this.isActive()) {
if (fDown !== undefined) {
if (this.fLocked === false) {
/*
* If there's no support for automatic pointer locking in the Video component, then notifyPointerActive()
@ -547,15 +508,58 @@ Mouse.prototype.clickMouse = function(iButton, fDown)
this.fLocked = null;
}
}
this.clickMouse(event.button, fDown);
} else {
/*
* MouseEvent objects contain, among other things, the following properties:
*
* clientX
* clientY
*
* I've selected the above properties because they're widely supported, not because I need
* 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.
*
* This is because all we care about are deltas. We record clientX and clientY (as xMouse and yMouse)
* merely to calculate xDelta and yDelta.
*/
var xDelta, yDelta;
if (this.xMouse < 0 || this.yMouse < 0) {
this.xMouse = event.clientX;
this.yMouse = event.clientY;
}
if (this.fLocked) {
xDelta = event['movementX'] || event['mozMovementX'] || event['webkitMovementX'] || 0;
yDelta = event['movementY'] || event['mozMovementY'] || event['webkitMovementY'] || 0;
} else {
xDelta = event.clientX - this.xMouse;
yDelta = event.clientY - this.yMouse;
}
this.xMouse = event.clientX;
this.yMouse = event.clientY;
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
*/
Mouse.prototype.clickMouse = function(iButton, fDown)
{
if (this.isActive()) {
var sDiag = DEBUGGER? ("mouse button" + iButton + ' ' + (fDown? "dn" : "up")) : null;
switch (iButton) {
case 0:
case Mouse.BUTTON.LEFT:
if (this.fButton1 != fDown) {
this.fButton1 = fDown;
this.sendPacket(sDiag);
}
break;
case 2:
case Mouse.BUTTON.RIGHT:
if (this.fButton2 != fDown) {
this.fButton2 = fDown;
this.sendPacket(sDiag);
@ -567,6 +571,31 @@ Mouse.prototype.clickMouse = function(iButton, fDown)
}
};
/**
* moveMouse(xDelta, yDelta, xDiag, yDiag)
*
* @this {Mouse}
* @param {number} xDelta
* @param {number} yDelta
* @param {number} [xDiag]
* @param {number} [yDiag]
*/
Mouse.prototype.moveMouse = function(xDelta, yDelta, xDiag, yDiag)
{
if (this.isActive()) {
if (xDelta || yDelta) {
/*
* 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 = xDelta;
this.yDelta = yDelta;
this.sendPacket(null, xDiag, yDiag);
}
}
};
/**
* sendPacket(sDiag, xDiag, yDiag)
*

View file

@ -41,6 +41,7 @@ if (typeof module !== 'undefined') {
var Messages = require("./messages");
var ChipSet = require("./chipset");
var Keyboard = require("./keyboard");
var Mouse = require("./mouse");
var State = require("./state");
}
@ -59,6 +60,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 canvas (default is black)
* touchScreen: string specifying desired touch-screen support (default is ''); see initBus()
* autoLock: true to (attempt to) automatically lock the mouse to the canvas (default is false)
*
* An EGA may specify the following additional properties:
@ -144,13 +146,17 @@ function Video(parmsVideo, canvas, context, textarea, container)
this.fScaleFont = parmsVideo['scale'];
this.fDoubleFont = Math.round(this.cxScreen / this.nColsDefault) >= 12;
this.fTouchScreen = parmsVideo['touchScreen'];
this.canvasScreen = canvas;
this.contextScreen = context;
this.textareaScreen = textarea;
this.inputScreen = textarea || canvas || null;
/*
* initBus() will determine touch-screen support; for now, just record values and set defaults.
*/
this.sTouchScreen = parmsVideo['touchScreen'];
this.nTouchConfig = Video.TOUCH.NONE;
/*
* 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.
@ -2644,6 +2650,15 @@ Video.cardSpecs[Video.CARD.CGA] = ["CGA", Card.CGA.CRTC.INDX.PORT, 0xB8000, 0x04
Video.cardSpecs[Video.CARD.EGA] = ["EGA", Card.CGA.CRTC.INDX.PORT, 0xB8000, 0x04000, 0x10000, ChipSet.MONITOR.EGACOLOR];
Video.cardSpecs[Video.CARD.VGA] = ["VGA", Card.CGA.CRTC.INDX.PORT, 0xB8000, 0x04000, 0x40000, ChipSet.MONITOR.VGACOLOR];
/*
* Values for nTouchConfig; a value will be selected based on the sTouchScreen configuration parameter.
*/
Video.TOUCH = {
NONE: 0,
KEYGRID: 1,
MOUSE: 2
};
/**
* initBus(cmp, bus, cpu, dbg)
*
@ -2723,7 +2738,18 @@ Video.prototype.initBus = function(cmp, bus, cpu, dbg)
if (this.nCard == Video.CARD.EGA) this.bEGASwitches = this.chipset.parseSwitches(this.sSwitches, this.bEGASwitches);
}
if (this.kbd && this.fTouchScreen) this.captureTouch();
/*
* The default value for the 'touchScreen' parameter is an empty string; machine configs must explicitly
* select one of the following values, via the 'touchscreen' attribute in the <video> element, to enable any
* touch-screen support.
*/
if (this.sTouchScreen == "mouse") {
this.mouse = cmp.getComponentByType("Mouse");
if (this.mouse) this.captureTouch(Video.TOUCH.MOUSE);
}
else if (this.sTouchScreen == "keygrid") {
if (this.kbd) this.captureTouch(Video.TOUCH.KEYGRID);
}
};
/**
@ -2915,13 +2941,13 @@ Video.prototype.lockPointer = function(fLock)
if (fLock) {
if (this.inputScreen.lockPointer) {
this.inputScreen.lockPointer();
this.mouse.notifyPointerLocked(true);
if (this.mouse) this.mouse.notifyPointerLocked(true);
fSuccess = true;
}
} else {
if (this.inputScreen.unlockPointer) {
this.inputScreen.unlockPointer();
this.mouse.notifyPointerLocked(false);
if (this.mouse) this.mouse.notifyPointerLocked(false);
fSuccess = true;
}
}
@ -2962,16 +2988,17 @@ Video.prototype.notifyPointerLocked = function(fLocked)
};
/**
* captureTouch()
* captureTouch(nTouchConfig)
*
* @this {Video}
* @param {number} nTouchConfig (must be one of the supported Video.TOUCH values)
*/
Video.prototype.captureTouch = function()
Video.prototype.captureTouch = function(nTouchConfig)
{
var control = this.inputScreen;
if (control) {
var video = this;
if (!this.fCaptured) {
if (!this.nTouchConfig) {
control.addEventListener(
'touchstart',
function onTouchStart(event) { video.onTouchStart(event); },
@ -3009,7 +3036,8 @@ Video.prototype.captureTouch = function()
*/
}
// this.log("touch events captured");
this.fCaptured = true;
this.nTouchConfig = nTouchConfig;
this.xTouch = this.yTouch = this.timeTouch = -1;
}
}
};
@ -3066,7 +3094,7 @@ Video.prototype.onTouchStart = function(event)
Video.prototype.onTouchMove = function(event)
{
if (DEBUG) this.printMessage("onTouchMove()");
this.processTouchEvent(event, false);
this.processTouchEvent(event);
};
/**
@ -3078,17 +3106,29 @@ Video.prototype.onTouchMove = function(event)
Video.prototype.onTouchEnd = function(event)
{
if (DEBUG) this.printMessage("onTouchEnd()");
this.processTouchEvent(event, false);
};
/**
* processTouchEvent(event, fStart)
*
* If nTouchConfig is non-zero, touch event handlers are installed, which pass their events to this function.
*
* What we do with those events here depends on the value of nTouchConfig. Originally, the only supported
* configuration was the experimental conversion of touch events into arrow keys, based on an invisible grid
* that divided the screen into thirds; that configuration is now identified as Video.TOUCH.KEYGRID.
*
* The new preferred configuration is Video.TOUCH.MOUSE, which does little more than allow you to "push" the
* simulated mouse around. If Video.TOUCH.MOUSE is enabled, it's already been confirmed the machine has a mouse.
*
* @this {Video}
* @param {Event} event object from a 'touch' event
* @param {boolean} fStart if this is a 'touchstart' event
* @param {boolean} [fStart] (true if 'touchstart', false if 'touchend', undefined if 'touchmove')
*/
Video.prototype.processTouchEvent = function(event, fStart)
{
var xTouch, yTouch;
// if (!event) event = window.event;
/*
* My thinking here is that if the canvas does NOT yet have focus, then we should actually SKIP
@ -3133,34 +3173,73 @@ Video.prototype.processTouchEvent = function(event, fStart)
* @name Event
* @property {Array} targetTouches
*/
var xTouch, yTouch;
if (!event.targetTouches) {
if (!event.targetTouches || !event.targetTouches.length) {
xTouch = event.pageX;
yTouch = event.pageY;
} else {
xTouch = event.targetTouches[0].pageX;
yTouch = event.targetTouches[0].pageY;
}
xTouch = ((xTouch - xTouchOffset) * xScale);
yTouch = ((yTouch - yTouchOffset) * yScale);
var xThird = (xTouch / (this.cxScreen / 3)) | 0;
var yThird = (yTouch / (this.cyScreen / 3)) | 0;
/*
* At this point, xThird and yThird should both be one of 0, 1 or 2, indicating which horizontal and vertical
* third of the virtual screen the touch event occurred.
*/
if (/* xThird == 1 && */ yThird != 1) {
if (!yThird) {
this.kbd.addActiveKey(Keyboard.CLICKCODES.UP, true);
} else {
this.kbd.addActiveKey(Keyboard.CLICKCODES.DOWN, true);
if (this.nTouchConfig == Video.TOUCH.KEYGRID) {
var xThird = (xTouch / (this.cxScreen / 3)) | 0;
var yThird = (yTouch / (this.cyScreen / 3)) | 0;
/*
* At this point, xThird and yThird should both be one of 0, 1 or 2, indicating which horizontal and vertical
* third of the virtual screen the touch event occurred.
*/
if (/* xThird == 1 && */ yThird != 1) {
if (!yThird) {
this.kbd.addActiveKey(Keyboard.CLICKCODES.UP, true);
} else {
this.kbd.addActiveKey(Keyboard.CLICKCODES.DOWN, true);
}
} else if (/* yThird == 1 && */ xThird != 1) {
if (!xThird) {
this.kbd.addActiveKey(Keyboard.CLICKCODES.LEFT, true);
} else {
this.kbd.addActiveKey(Keyboard.CLICKCODES.RIGHT, true);
}
}
} else if (/* yThird == 1 && */ xThird != 1) {
if (!xThird) {
this.kbd.addActiveKey(Keyboard.CLICKCODES.LEFT, true);
} else {
this.kbd.addActiveKey(Keyboard.CLICKCODES.RIGHT, true);
} else {
if (this.mouse) {
if (fStart === true) {
this.timeTouch = event.timeStamp;
}
if (fStart === false) {
var timeDelta = event.timeStamp - this.timeTouch;
this.println("processTouchEvent(false," + timeDelta + ")");
/*
* NOTE: 200ms is merely my initial stab at a reasonable number of milliseconds to interpret a
* start/end touch sequence as a "tap"; I also make no note of any intervening move events (ie,
* events where fStart is undefined), and perhaps I should....
*/
if (timeDelta < 200) {
this.mouse.clickMouse(Mouse.BUTTON.LEFT, true);
this.mouse.clickMouse(Mouse.BUTTON.LEFT, false);
return;
}
}
/*
* This 'touchmove" code mimics the 'mousemove' event processing in processMouseEvent() in mouse.js, with
* one important difference: every time touching "restarts", we need to reset the variables used to calculate
* the deltas, so that the mere act of lifting and replacing your finger doesn't generate a delta by itself.
*/
if (fStart || this.xTouch < 0 || this.yTouch < 0) {
this.xTouch = xTouch;
this.yTouch = yTouch;
}
var xDelta = Math.round(xTouch - this.xTouch);
var yDelta = Math.round(yTouch - this.yTouch);
this.xTouch = xTouch;
this.yTouch = yTouch;
// this.println("moveMouse(" + xDelta + "," + yDelta + ")");
this.mouse.moveMouse(xDelta, yDelta, this.xTouch, this.yTouch);
}
}
};

View file

@ -905,7 +905,7 @@
<xsl:variable name="touchScreen">
<xsl:choose>
<xsl:when test="@touchscreen"><xsl:value-of select="@touchscreen"/></xsl:when>
<xsl:otherwise>false</xsl:otherwise>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="autoLock">
@ -917,7 +917,7 @@
<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"/>,autoLock:<xsl:value-of select="$autoLock"/></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>