Added support for keyboard lock LEDs

This commit is contained in:
Jeff Parsons 2014-11-14 18:08:45 -08:00 committed by jeffpar
commit 501649f67b
37 changed files with 1970 additions and 1829 deletions

View file

@ -150,7 +150,7 @@ function ChipSet(parmsChipSet)
Component.call(this, "ChipSet", parmsChipSet, ChipSet);
this.model = parmsChipSet['model'];
this.model = (this.model !== undefined? parseInt(this.model, 10) : ChipSet.MODEL_5150);
this.model = (this.model? parseInt(this.model, 10) : ChipSet.MODEL_5150);
/*
* SW1 describes the number of floppy drives, the amount of base memory, the primary monitor type,
@ -173,7 +173,7 @@ function ChipSet(parmsChipSet)
} else {
this.aFloppyDrives = [360, 360];
var aFloppyDrives = parmsChipSet['floppies'];
if (aFloppyDrives && aFloppyDrives.length !== undefined) this.aFloppyDrives = aFloppyDrives;
if (aFloppyDrives && aFloppyDrives.length) this.aFloppyDrives = aFloppyDrives;
var nDrives = this.aFloppyDrives.length;
if (nDrives) {
this.sw1Init |= ChipSet.PPI_SW.FDRIVE.IPL;
@ -1030,19 +1030,23 @@ ChipSet.prototype.reset = function(fHard)
* DMA Controller initialization
*/
this.aDMACs = new Array(this.cDMACs);
for (i = 0; i < this.cDMACs; i++) this.initDMAController(i);
for (i = 0; i < this.cDMACs; i++) {
this.initDMAController(i);
}
/*
* PIC initialization
*/
this.aPICs = new Array(this.cPICs);
this.initPIC(ChipSet.PIC0.INDEX, ChipSet.PIC0.PORT_LO);
if (this.cPICs > 1) this.initPIC(ChipSet.PIC1.INDEX, ChipSet.PIC1.PORT_LO);
if (this.cPICs > 1) {
this.initPIC(ChipSet.PIC1.INDEX, ChipSet.PIC1.PORT_LO);
}
/*
* Timer initialization
*/
this.bTimerCtrl = undefined; // tracks writes to port 0x43
this.bTimerCtrl = null; // tracks writes to port 0x43
this.aTimers = new Array(3);
for (i = 0; i < this.aTimers.length; i++) {
this.initTimer(i);
@ -1051,10 +1055,10 @@ ChipSet.prototype.reset = function(fHard)
/*
* PPI and other misc ports
*/
this.bPPIA = undefined; // tracks writes to port 0x60, in case PPI_CTRL.A_IN is not set
this.bPPIB = undefined; // tracks writes to port 0x61, in case PPI_CTRL.B_IN is not set
this.bPPIC = undefined; // tracks writes to port 0x62, in case PPI_CTRL.C_IN_LO or PPI_CTRL.C_IN_HI is not set
this.bPPICtrl = undefined; // tracks writes to port 0x63 (eg, 0x99); read-only
this.bPPIA = null; // tracks writes to port 0x60, in case PPI_CTRL.A_IN is not set
this.bPPIB = null; // tracks writes to port 0x61, in case PPI_CTRL.B_IN is not set
this.bPPIC = null; // tracks writes to port 0x62, in case PPI_CTRL.C_IN_LO or PPI_CTRL.C_IN_HI is not set
this.bPPICtrl = null; // tracks writes to port 0x63 (eg, 0x99); read-only
this.bNMI = ChipSet.NMI.DISABLE;// tracks writes to the NMI Mask Register
/*
@ -1522,20 +1526,17 @@ ChipSet.prototype.restore = function(data)
this.sw2 = a[3];
a = data[1];
this.aDMACs = new Array(this.cDMACs);
for (i = 0; i < this.cDMACs; i++) {
this.initDMAController(i, a.length == 1? a[0][i] : a);
}
a = data[2];
this.aPICs = new Array(this.cPICs);
for (i = 0; i < this.cPICs; i++) {
this.initPIC(i, i === 0? ChipSet.PIC0.PORT_LO : ChipSet.PIC1.PORT_LO, a[0][i]);
}
a = data[3];
this.bTimerCtrl = a[0];
this.aTimers = new Array(3);
for (i = 0; i < this.aTimers.length; i++) {
this.initTimer(i, a[1][i]);
}
@ -1591,9 +1592,12 @@ ChipSet.aDMAControllerInit = [0, null, null, 0, new Array(4)];
ChipSet.prototype.initDMAController = function(iDMAC, aState)
{
var controller = this.aDMACs[iDMAC];
if (!controller) controller = {
aChannels: new Array(4)
};
if (!controller) {
Component.assert(!aState);
controller = {
aChannels: new Array(4)
};
}
var a = aState && aState.length == 5? aState : ChipSet.aDMAControllerInit;
controller.bStatus = a[0];
controller.bCmd = a[1];
@ -1618,28 +1622,25 @@ ChipSet.aDMAChannelInit = [true, [0,0], [0,0], [0,0], [0,0]];
*/
ChipSet.prototype.initDMAChannel = function(controller, iChannel, aState)
{
var a = ChipSet.aDMAChannelInit;
var channel = controller.aChannels[iChannel];
if (!channel) {
Component.assert(!aState);
channel = {
masked: true,
addrInit: [0,0],
countInit: [0,0],
addrCurrent: [0,0],
countCurrent: [0,0]
};
} else {
if (aState && aState.length == 8) a = aState;
channel.masked = a[0];
channel.addrInit[0] = a[1][0]; channel.addrInit[1] = a[1][1];
channel.countInit[0] = a[2][0]; channel.countInit[1] = a[2][1];
channel.addrCurrent[0] = a[3][0]; channel.addrCurrent[1] = a[3][1];
channel.countCurrent[0] = a[4][0]; channel.countCurrent[1] = a[4][1];
channel.mode = a[5];
channel.bPage = a[6];
// a[7] is deprecated
}
var a = aState && aState.length == 8? aState : ChipSet.aDMAChannelInit;
channel.masked = a[0];
channel.addrInit[0] = a[1][0]; channel.addrInit[1] = a[1][1];
channel.countInit[0] = a[2][0]; channel.countInit[1] = a[2][1];
channel.addrCurrent[0] = a[3][0]; channel.addrCurrent[1] = a[3][1];
channel.countCurrent[0] = a[4][0]; channel.countCurrent[1] = a[4][1];
channel.mode = a[5];
channel.bPage = a[6];
// a[7] is deprecated
channel.controller = controller;
channel.iChannel = iChannel;
this.initDMAFunction(channel, a[8], a[9]);
@ -1650,8 +1651,8 @@ ChipSet.prototype.initDMAChannel = function(controller, iChannel, aState)
* initDMAFunction(channel)
*
* @param {Object} channel
* @param {Component|string|undefined} component
* @param {string|undefined} sFunction
* @param {Component|string} [component]
* @param {string} [sFunction]
* @param {Object} [obj]
* @return {*}
*/
@ -1723,7 +1724,7 @@ ChipSet.prototype.saveDMAChannels = function(controller)
ChipSet.aPICInit = [0, new Array(4)];
/**
* initPIC(iPIC, aState)
* initPIC(iPIC, port, aState)
*
* @this {ChipSet}
* @param {number} iPIC
@ -1733,12 +1734,16 @@ ChipSet.aPICInit = [0, new Array(4)];
ChipSet.prototype.initPIC = function(iPIC, port, aState)
{
var pic = this.aPICs[iPIC];
if (!pic) pic = {};
if (!pic) {
pic = {
aICW: [null,null,null,null]
};
}
var a = aState && aState.length == 8? aState : ChipSet.aPICInit;
pic.port = port;
pic.nIRQBase = iPIC << 3;
pic.nDelay = a[0];
pic.aICW = a[1];
pic.aICW[0] = a[1][0]; pic.aICW[1] = a[1][1]; pic.aICW[2] = a[1][2]; pic.aICW[3] = a[1][3];
pic.nICW = a[2];
pic.bIMR = a[3];
pic.bIRR = a[4];
@ -1785,12 +1790,14 @@ ChipSet.aTimerInit = [[0,0], [0,0], [0,0], [0,0]];
ChipSet.prototype.initTimer = function(iTimer, aState)
{
var timer = this.aTimers[iTimer];
if (!timer) timer = {
countInit: [0,0],
countStart: [0,0],
countCurrent: [0,0],
countLatched: [0,0]
};
if (!timer) {
timer = {
countInit: [0,0],
countStart: [0,0],
countCurrent: [0,0],
countLatched: [0,0]
};
}
var a = aState && aState.length == 13? aState : ChipSet.aTimerInit;
timer.countInit[0] = a[0][0]; timer.countInit[1] = a[0][1];
timer.countStart[0] = a[1][0]; timer.countStart[1] = a[1][1];
@ -1842,7 +1849,7 @@ ChipSet.prototype.saveTimers = function()
* getSWMemorySize(fInit)
*
* @this {ChipSet}
* @param {boolean|undefined} [fInit] is true for init switch value(s) only, current value(s) otherwise
* @param {boolean} [fInit] is true for init switch value(s) only, current value(s) otherwise
* @return {number} number of Kb of specified memory (NOT necessarily the same as installed memory; see RAM component)
*/
ChipSet.prototype.getSWMemorySize = function(fInit)
@ -1856,7 +1863,7 @@ ChipSet.prototype.getSWMemorySize = function(fInit)
* getSWFloppyDrives(fInit)
*
* @this {ChipSet}
* @param {boolean|undefined} [fInit] is true for init switch value(s) only, current value(s) otherwise
* @param {boolean} [fInit] is true for init switch value(s) only, current value(s) otherwise
* @return {number} number of floppy drives specified by SW1 (range is 0 to 4)
*/
ChipSet.prototype.getSWFloppyDrives = function(fInit)
@ -1923,7 +1930,7 @@ ChipSet.prototype.getSWFloppyDriveSize = function(iDrive)
* getSWVideoMonitor(fInit)
*
* @this {ChipSet}
* @param {boolean|undefined} [fInit] is true for init switch value(s) only, current value(s) otherwise
* @param {boolean} [fInit] is true for init switch value(s) only, current value(s) otherwise
* @return {number} one of ChipSet.MONITOR.*
*/
ChipSet.prototype.getSWVideoMonitor = function(fInit)
@ -1956,7 +1963,7 @@ ChipSet.prototype.addSwitches = function(s, control, n, v, oTips)
var aeCells = Component.getElementsByClass(control, sCellClass);
var sTip = null;
for (i = 0; i < aeCells.length; i++) {
if (oTips !== undefined && oTips[i] !== undefined) {
if (oTips != null && oTips[i] != null) {
sTip = oTips[i];
}
if (sTip) aeCells[i].setAttribute("title", sTip);
@ -1992,7 +1999,7 @@ ChipSet.prototype.getSwitch = function(control)
*
* @this {ChipSet}
* @param {Object} control is an HTML control DOM object
* @param {boolean} f is true if the switch represented by e should be "on", false if "off"
* @param {boolean} f is true if the switch represented by control should be "on", false if "off"
*/
ChipSet.prototype.setSwitch = function(control, f)
{
@ -2047,12 +2054,12 @@ ChipSet.prototype.updateSwitchDesc = function()
2: "Color",
3: "Monochrome"
};
if (controlDesc !== undefined) {
if (controlDesc != null) {
var sHTML = "";
sHTML += this.getSWMemorySize(true) + "Kb";
sHTML += ", " + asMonitorTypes[this.getSWVideoMonitor(true)] + " Monitor";
sHTML += ", " + this.getSWFloppyDrives(true) + " Floppy Drives";
if (this.sw1 !== undefined && this.sw1 != this.sw1Init || this.sw2 !== undefined && this.sw2 != this.sw2Init)
if (this.sw1 != null && this.sw1 != this.sw1Init || this.sw2 != null && this.sw2 != this.sw2Init)
sHTML += " (Reset required)";
controlDesc.innerHTML = sHTML;
}
@ -2092,7 +2099,7 @@ ChipSet.prototype.dumpTimer = function()
var timer = this.aTimers[iTimer];
var sDump = "TIMER" + iTimer + ":";
var count = 0;
if (timer.countBytes !== undefined) {
if (timer.countBytes != null) {
for (var i = 0; i <= timer.countBytes; i++) {
count |= (timer.countCurrent[i] << (i * 8));
}
@ -2735,7 +2742,7 @@ ChipSet.prototype.inPICLo = function(iPIC, addrFrom)
{
var b = 0;
var pic = this.aPICs[iPIC];
if (pic.bOCW3 !== undefined) {
if (pic.bOCW3 != null) {
var bReadReg = pic.bOCW3 & ChipSet.PIC_LO.OCW3_READ_CMD;
switch (bReadReg) {
case ChipSet.PIC_LO.OCW3_READ_IRR:
@ -3425,7 +3432,7 @@ ChipSet.prototype.resetTimerIndex = function(iTimer)
* 0: Time-of-Day interrupt (~18.2 interrupts/second)
* 1: DMA refresh
* 2: Sound/Cassette
* @param {boolean|undefined} [fCycleReset] is true if a cycle-count reset is about to occur
* @param {boolean} [fCycleReset] is true if a cycle-count reset is about to occur
* @return {Object} timer
*/
ChipSet.prototype.updateTimer = function(iTimer, fCycleReset)
@ -3593,7 +3600,7 @@ ChipSet.prototype.updateTimer = function(iTimer, fCycleReset)
* updateAllTimers(fCycleReset)
*
* @this {ChipSet}
* @param {boolean|undefined} [fCycleReset] is true if a cycle-count reset is about to occur
* @param {boolean} [fCycleReset] is true if a cycle-count reset is about to occur
*/
ChipSet.prototype.updateAllTimers = function(fCycleReset)
{
@ -4483,7 +4490,7 @@ ChipSet.prototype.setSpeaker = function(fOn)
* @this {ChipSet}
* @param {string} sMessage is any caller-defined message string
* @param {number} [bitsMessage] is one or more Debugger MESSAGE_* category flag(s)
* @param {number|undefined} [nIRQ] if the message is associated with a particular IRQ #
* @param {number} [nIRQ] if the message is associated with a particular IRQ #
*
* This is a combination of the Debugger's messageEnabled() and message() functions, for convenience.
*

View file

@ -172,6 +172,12 @@ function FDC(parmsFDC) {
*/
this.aDiskHistory = [];
/*
* If setBinding() sees a binding for loading local disks, it will set this flag, and then initBus()
* can intelligently update the "listDisks" control accordingly.
*/
this.fLocalDisks = false;
/*
* The remainder of FDC initialization now takes place in our initBus() handler, largely because we
* want initController() to have access to the ChipSet component, so that it can query switches and/or CMOS
@ -419,9 +425,6 @@ FDC.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
case "listDisks":
this.bindings[sBinding] = control;
this.addDiskette("Local Disk", "?");
this.addDiskette("Remote Disk", "??");
control.onchange = function onChangeListDisks(event) {
var controlDesc = fdc.bindings["descDisk"];
var controlOption = control.options[control.selectedIndex];
@ -475,6 +478,7 @@ FDC.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
* Check for availability of FileReader
*/
if (window && 'FileReader' in window) {
this.fLocalDisks = true;
this.bindings[sBinding] = control;
/*
@ -540,6 +544,11 @@ FDC.prototype.initBus = function(cmp, bus, cpu, dbg)
bus.addPortInputTable(this, FDC.aPortInput);
bus.addPortOutputTable(this, FDC.aPortOutput);
if (this.fLocalDisks) {
this.addDiskette("Local Disk", "?");
}
this.addDiskette("Remote Disk", "??");
if (!this.autoMount()) this.setReady();
};
@ -1190,7 +1199,7 @@ FDC.prototype.loadSelectedDrive = function(sDisketteName, sDiskettePath, file)
}
if (sDiskettePath == "?") {
this.notice("Use 'Load' to load local disks");
this.notice('Use "Choose File" and "Load" to select and load a local disk.');
return;
}

View file

@ -173,6 +173,8 @@ Keyboard.KEYCODE = {
/* 0x79 */ F10: 121,
/* 0x7A */ F11: 122,
/* 0x7B */ F12: 123,
/* 0x90 */ NUM_LOCK: 144,
/* 0x91 */ SCROLL_LOCK:145,
//
// ONDOWN is a bias we add to browser keyCodes that we want to handle on "down" rather than "press".
//
@ -208,12 +210,12 @@ Keyboard.LOCATION = {
};
/**
* These internal "shift key" states are stored in bitsShift, as well as in aKeyCodes; note that the
* right-hand versions of selected shift bits are shifted 1 bit right.
* These internal "shift key" states are used to indicate the current simulated shift/modifier key states
* in shiftStates; note that right-hand shift bits match the corresponding left-hand bit shifted 1 bit right.
*
* @enum {number}
*/
Keyboard.STATE = {
Keyboard.STATES = {
RSHIFT: 0x0001,
SHIFT: 0x0002,
RCTRL: 0x0004, // 101-key keyboard only
@ -223,21 +225,34 @@ Keyboard.STATE = {
COMMAND: 0x0040, // 101-key keyboard only (TODO: Treat this like the 'Windows' key)
INSERT: 0x0080, // TODO: Placeholder
CAPS_LOCK: 0x0100,
NUM_LOCK: 0x0200, // TODO: Placeholder
SCROLL_LOCK: 0x0400, // TODO: Placeholder
SIMULATE: 0x003f // STATE.RSHIFT | STATE.SHIFT | STATE.RCTRL | STATE.CTRL | STATE.RALT | STATE.ALT
NUM_LOCK: 0x0200,
SCROLL_LOCK: 0x0400,
LOCK: 0x0700, // STATES.CAPS_LOCK | STATES.NUM_LOCK | STATES.SCROLL_LOCK
SIMULATE: 0x003f // STATES.RSHIFT | STATES.SHIFT | STATES.RCTRL | STATES.CTRL | STATES.RALT | STATES.ALT
};
/**
* Maps the KEYCODE of a "shift key" to its corresponding (default) STATE bit above
* Maps KEYCODES of shift/modifier keys to their corresponding (default) STATES bit above
*
* @enum {number}
*/
Keyboard.STATEKEYS = {
16: Keyboard.STATE.SHIFT,
17: Keyboard.STATE.CTRL,
18: Keyboard.STATE.ALT,
20: Keyboard.STATE.CAPS_LOCK
Keyboard.KEYSTATES = {};
Keyboard.KEYSTATES[Keyboard.KEYCODE.SHIFT] = Keyboard.STATES.SHIFT;
Keyboard.KEYSTATES[Keyboard.KEYCODE.CTRL] = Keyboard.STATES.CTRL;
Keyboard.KEYSTATES[Keyboard.KEYCODE.ALT] = Keyboard.STATES.ALT;
Keyboard.KEYSTATES[Keyboard.KEYCODE.CAPS_LOCK] = Keyboard.STATES.CAPS_LOCK;
Keyboard.KEYSTATES[Keyboard.KEYCODE.NUM_LOCK] = Keyboard.STATES.NUM_LOCK;
Keyboard.KEYSTATES[Keyboard.KEYCODE.SCROLL_LOCK]= Keyboard.STATES.SCROLL_LOCK;
/**
* Maps "soft-key" definitions of shift/modifier keys to their corresponding (default) STATES bit above
*
* @enum {number}
*/
Keyboard.LEDSTATES = {
'caps-lock': Keyboard.STATES.CAPS_LOCK,
'num-lock': Keyboard.STATES.NUM_LOCK,
'scroll-lock': Keyboard.STATES.SCROLL_LOCK
};
/**
@ -255,27 +270,23 @@ Keyboard.STATEKEYS = {
* @enum {number}
*/
Keyboard.aButtonCodes = {
'tab': Keyboard.KEYCODE.TAB + Keyboard.KEYCODE.ONDOWN,
'esc': Keyboard.KEYCODE.ESC + Keyboard.KEYCODE.ONDOWN,
'right-shift': Keyboard.KEYCODE.SHIFT + Keyboard.KEYCODE.ONDOWN + Keyboard.KEYCODE.ONRIGHT,
'shift': Keyboard.KEYCODE.SHIFT + Keyboard.KEYCODE.ONDOWN,
'ctrl': Keyboard.KEYCODE.CTRL + Keyboard.KEYCODE.ONDOWN,
'alt': Keyboard.KEYCODE.ALT + Keyboard.KEYCODE.ONDOWN,
'caps-lock': Keyboard.KEYCODE.CAPS_LOCK + Keyboard.KEYCODE.ONDOWN,
'f1': Keyboard.KEYCODE.F1 + Keyboard.KEYCODE.ONDOWN,
'f2': Keyboard.KEYCODE.F2 + Keyboard.KEYCODE.ONDOWN,
'f3': Keyboard.KEYCODE.F3 + Keyboard.KEYCODE.ONDOWN,
'f4': Keyboard.KEYCODE.F4 + Keyboard.KEYCODE.ONDOWN,
'f5': Keyboard.KEYCODE.F5 + Keyboard.KEYCODE.ONDOWN,
'f6': Keyboard.KEYCODE.F6 + Keyboard.KEYCODE.ONDOWN,
'f7': Keyboard.KEYCODE.F7 + Keyboard.KEYCODE.ONDOWN,
'f8': Keyboard.KEYCODE.F8 + Keyboard.KEYCODE.ONDOWN,
'f9': Keyboard.KEYCODE.F9 + Keyboard.KEYCODE.ONDOWN,
'f10': Keyboard.KEYCODE.F10 + Keyboard.KEYCODE.ONDOWN,
'left': Keyboard.KEYCODE.LEFT + Keyboard.KEYCODE.ONDOWN, // formerly "left-arrow"
'up': Keyboard.KEYCODE.UP + Keyboard.KEYCODE.ONDOWN, // formerly "up-arrow"
'right': Keyboard.KEYCODE.RIGHT + Keyboard.KEYCODE.ONDOWN, // formerly "right-arrow"
'down': Keyboard.KEYCODE.DOWN + Keyboard.KEYCODE.ONDOWN, // formerly "down-arrow"
'tab': Keyboard.KEYCODE.TAB + Keyboard.KEYCODE.ONDOWN,
'esc': Keyboard.KEYCODE.ESC + Keyboard.KEYCODE.ONDOWN,
'caps-lock': Keyboard.KEYCODE.CAPS_LOCK + Keyboard.KEYCODE.ONDOWN,
'f1': Keyboard.KEYCODE.F1 + Keyboard.KEYCODE.ONDOWN,
'f2': Keyboard.KEYCODE.F2 + Keyboard.KEYCODE.ONDOWN,
'f3': Keyboard.KEYCODE.F3 + Keyboard.KEYCODE.ONDOWN,
'f4': Keyboard.KEYCODE.F4 + Keyboard.KEYCODE.ONDOWN,
'f5': Keyboard.KEYCODE.F5 + Keyboard.KEYCODE.ONDOWN,
'f6': Keyboard.KEYCODE.F6 + Keyboard.KEYCODE.ONDOWN,
'f7': Keyboard.KEYCODE.F7 + Keyboard.KEYCODE.ONDOWN,
'f8': Keyboard.KEYCODE.F8 + Keyboard.KEYCODE.ONDOWN,
'f9': Keyboard.KEYCODE.F9 + Keyboard.KEYCODE.ONDOWN,
'f10': Keyboard.KEYCODE.F10 + Keyboard.KEYCODE.ONDOWN,
'left': Keyboard.KEYCODE.LEFT + Keyboard.KEYCODE.ONDOWN, // formerly "left-arrow"
'up': Keyboard.KEYCODE.UP + Keyboard.KEYCODE.ONDOWN, // formerly "up-arrow"
'right': Keyboard.KEYCODE.RIGHT + Keyboard.KEYCODE.ONDOWN, // formerly "right-arrow"
'down': Keyboard.KEYCODE.DOWN + Keyboard.KEYCODE.ONDOWN, // formerly "down-arrow"
/*
* These bindings are for convenience (common key combinations that can be bound to a single control)
*/
@ -546,8 +557,8 @@ 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: keySimulateUpOrDown() has the ability to "undo" any states in bitsShift
* that conflict with the state(s) required for the character in question.
* we've inherited the same solution: keySimulateUpOrDown() has the ability to "undo" any states in
* shiftStates that conflict with the state(s) required for the character in question.
*
* @enum {number}
*/
@ -656,7 +667,7 @@ Keyboard.aKeyCodes[Keyboard.KEYCODE.SHIFT + Keyboard.KEYCODE.ONDOWN + Keyboard
// TODO: 0x37 ('prtsc')
Keyboard.aKeyCodes[Keyboard.KEYCODE.ALT + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.ALT;
Keyboard.aKeyCodes[Keyboard.ASCII[' ']] = Keyboard.SCANCODE.SPACE;
Keyboard.aKeyCodes[Keyboard.KEYCODE.CAPS_LOCK + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.CAPS_LOCK;
Keyboard.aKeyCodes[Keyboard.KEYCODE.CAPS_LOCK + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.CAPS_LOCK;
Keyboard.aKeyCodes[Keyboard.KEYCODE.F1 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F1;
Keyboard.aKeyCodes[Keyboard.KEYCODE.F2 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F2;
Keyboard.aKeyCodes[Keyboard.KEYCODE.F3 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F3;
@ -667,7 +678,9 @@ Keyboard.aKeyCodes[Keyboard.KEYCODE.F7 + Keyboard.KEYCODE.ONDOWN] = Keyboard
Keyboard.aKeyCodes[Keyboard.KEYCODE.F8 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F8;
Keyboard.aKeyCodes[Keyboard.KEYCODE.F9 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F9;
Keyboard.aKeyCodes[Keyboard.KEYCODE.F10 + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.F10;
// TODO: 0x45 ('num-lock'), 0x46 ('scroll-lock'), 0x4a ('num-sub') and 0x4e ('num-add')
Keyboard.aKeyCodes[Keyboard.KEYCODE.NUM_LOCK + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_LOCK;
Keyboard.aKeyCodes[Keyboard.KEYCODE.SCROLL_LOCK + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.SCROLL_LOCK;
// TODO: 0x4a ('num-sub') and 0x4e ('num-add')
Keyboard.aKeyCodes[Keyboard.KEYCODE.HOME + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_HOME;
Keyboard.aKeyCodes[Keyboard.KEYCODE.UP + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_UP;
Keyboard.aKeyCodes[Keyboard.KEYCODE.PGUP + Keyboard.KEYCODE.ONDOWN] = Keyboard.SCANCODE.NUM_PGUP;
@ -788,6 +801,7 @@ Keyboard.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, contro
*/
var kbd = this;
var id = sHTMLType + '-' + sBinding;
if (this.bindings[id] === undefined) {
switch (sBinding) {
case "kbd":
@ -802,6 +816,28 @@ Keyboard.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, contro
return kbd.keyUpDown(event, false);
};
return true;
case "caps-lock":
this.bindings[id] = control;
control.onclick = function onClickCapsLock(event) {
return kbd.toggleCapsLock(event);
};
return true;
case "num-lock":
this.bindings[id] = control;
control.onclick = function onClickNumLock(event) {
return kbd.toggleNumLock(event);
};
return true;
case "scroll-lock":
this.bindings[id] = control;
control.onclick = function onClickScrollLock(event) {
return kbd.toggleScrollLock(event);
};
return true;
default:
if (Keyboard.aButtonCodes[sBinding] !== undefined && sHTMLType == "button") {
this.bindings[id] = control;
@ -809,6 +845,7 @@ Keyboard.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, contro
return function onClickKeyboard(event) {
if (DEBUG) kbd.messageDebugger(sKey + " clicked", Debugger.MESSAGE.KEYS);
if (kbd.cpu) kbd.cpu.setFocus();
kbd.checkShiftState(keyCode);
return !kbd.keySimulatePress(keyCode);
};
}(this, sBinding, Keyboard.aButtonCodes[sBinding]);
@ -841,28 +878,28 @@ Keyboard.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, contro
};
/**
* findBinding(bKey, t, fDown)
* findBinding(bKey, sType, fDown)
*
* @this {Keyboard}
* @param {number} bKey
* @param {string} t is the type of control (eg, "button" or "key")
* @param {string} sType is the type of control (eg, "button" or "key")
* @param {boolean} [fDown] is true if the key is going down, false if up, or undefined if unchanged
* @return {Object} is the HTML control DOM object (eg, HTMLButtonElement), or undefined if no such control exists
*/
Keyboard.prototype.findBinding = function(bKey, t, fDown)
Keyboard.prototype.findBinding = function(bKey, sType, fDown)
{
var e;
for (var s in Keyboard.aSoftCodes) {
if (Keyboard.aSoftCodes[s] == bKey) {
var id = t + '-' + s;
e = this.bindings[id];
if (e && fDown !== undefined) {
this.setSoftKeyState(e, fDown);
var control;
for (var sBinding in Keyboard.aSoftCodes) {
if (Keyboard.aSoftCodes[sBinding] == bKey) {
var id = sType + '-' + sBinding;
control = this.bindings[id];
if (control && fDown !== undefined) {
this.setSoftKeyState(control, fDown);
}
break;
}
}
return e;
return control;
};
/**
@ -1108,11 +1145,11 @@ Keyboard.prototype.reset = function()
this.initState();
/*
* The physical (not virtual) state of various shift/lock keys.
* The current (assumed) physical state of the various shift/lock keys.
*
* TODO: Determine how (or whether) we can query the browser's initial shift/lock key states.
*/
this.bitsShift = 0;
this.shiftStates = 0;
/*
* New scan codes are "pushed" onto abScanBuffer and then "shifted" off.
@ -1344,6 +1381,100 @@ Keyboard.prototype.injectKeysFromBuffer = function(msDelay)
}
};
/**
* setLED(control, f)
*
* @this {Keyboard}
* @param {Object} control is an HTML control DOM object
* @param {boolean} f is true if the LED represented by control should be "on", false if "off"
*/
Keyboard.prototype.setLED = function(control, f)
{
control.style.backgroundColor = (f? "#00ff00" : "#000000");
};
/**
* updateLEDs(bitState)
*
* Updates any and all shift-related LEDs with the corresponding state in shiftStates.
*
* @this {Keyboard}
* @param {number} [bitState] is the bit in shiftStates that may have changed, if known; undefined if not
*/
Keyboard.prototype.updateLEDs = function(bitState)
{
var control;
for (var sBinding in Keyboard.LEDSTATES) {
var id = "led-" + sBinding;
var bitLED = Keyboard.LEDSTATES[sBinding];
if ((!bitState || bitState == bitLED) && (control = this.bindings[id])) {
this.setLED(control, !!(this.shiftStates & bitLED));
}
}
};
/**
* toggleCapsLock(event)
*
* @this {Keyboard}
* @param {Object} event
*/
Keyboard.prototype.toggleCapsLock = function(event)
{
if (this.cpu) this.cpu.setFocus();
this.keySimulatePress(Keyboard.KEYCODE.CAPS_LOCK + Keyboard.KEYCODE.ONDOWN, true);
};
/**
* toggleNumLock(event)
*
* @this {Keyboard}
* @param {Object} event
*/
Keyboard.prototype.toggleNumLock = function(event)
{
if (this.cpu) this.cpu.setFocus();
this.keySimulatePress(Keyboard.KEYCODE.NUM_LOCK + Keyboard.KEYCODE.ONDOWN, true);
};
/**
* toggleScrollLock(event)
*
* @this {Keyboard}
* @param {Object} event
*/
Keyboard.prototype.toggleScrollLock = function(event)
{
if (this.cpu) this.cpu.setFocus();
this.keySimulatePress(Keyboard.KEYCODE.SCROLL_LOCK + Keyboard.KEYCODE.ONDOWN, true);
};
/**
* checkShiftState(keyCode, fDown)
*
* @this {Keyboard}
* @param {number} keyCode (should already include any ONDOWN and/or ONRIGHT modifiers)
* @param {boolean} [fDown] is true for down, false for up, undefined for TBD
*/
Keyboard.prototype.checkShiftState = function(keyCode, fDown)
{
if (Keyboard.aKeyCodes[keyCode]) {
var bCode = Math.floor(keyCode % 1000);
var fRight = (Math.floor(keyCode / 1000) & 2);
var bitState = Keyboard.KEYSTATES[bCode] || 0;
if (bitState) {
if (fRight) bitState >>= 1;
if (bitState & Keyboard.STATES.LOCK) {
fDown = !(this.shiftStates & bitState);
}
this.shiftStates &= ~bitState;
if (fDown) this.shiftStates |= bitState;
this.updateLEDs(bitState);
}
}
};
/**
* keyUpDown(event, fDown)
*
@ -1365,20 +1496,26 @@ Keyboard.prototype.keyUpDown = function(event, fDown)
if (Keyboard.aKeyCodes[keyCode + Keyboard.KEYCODE.ONDOWN]) {
keyCodeSim += Keyboard.KEYCODE.ONDOWN;
var bShift = Keyboard.STATEKEYS[keyCode] || 0;
var bitState = Keyboard.KEYSTATES[keyCode] || 0;
if (bShift) {
if (bitState) {
if (event.location == Keyboard.LOCATION.RIGHT) {
bShift >>= 1;
bitState >>= 1;
keyCodeSim += Keyboard.KEYCODE.ONRIGHT;
}
this.bitsShift &= ~bShift;
if (fDown) this.bitsShift |= bShift;
if (keyCode == Keyboard.KEYCODE.CAPS_LOCK) {
this.shiftStates &= ~bitState;
if (fDown) this.shiftStates |= bitState;
this.updateLEDs(bitState);
if (keyCode == Keyboard.KEYCODE.CAPS_LOCK || keyCode == Keyboard.KEYCODE.NUM_LOCK || keyCode == Keyboard.KEYCODE.SCROLL_LOCK) {
/*
* FYI, CAPS_LOCK generates a "down" event ONLY when getting locked, and an "up" event ONLY
* FYI, "lock" keys generate a "down" event ONLY when getting locked, and an "up" event ONLY
* when getting unlocked--which is exactly what I want, even though that may seem a little
* counter-intuitive (since the key itself actually went down AND up for each event).
* counter-intuitive.
*
* However, since the key DID actually go down AND up on each event, we must make sure both make
* and break scan codes are delivered; keySimulatePress() will take care of that.
*/
fPass = this.keySimulatePress(keyCodeSim);
} else {
@ -1389,7 +1526,7 @@ Keyboard.prototype.keyUpDown = function(event, fDown)
/*
* HACK for simulating Ctrl-Break using Ctrl-Del (Mac) / Ctrl-Backspace (Windows)
*/
if (keyCode == Keyboard.KEYCODE.BS && (this.bitsShift & (Keyboard.STATE.CTRL|Keyboard.STATE.ALT)) == Keyboard.STATE.CTRL) {
if (keyCode == Keyboard.KEYCODE.BS && (this.shiftStates & (Keyboard.STATES.CTRL|Keyboard.STATES.ALT)) == Keyboard.STATES.CTRL) {
keyCodeSim = Keyboard.KEYCODE.FAKE_CTRLBREAK;
}
/*
@ -1427,8 +1564,8 @@ Keyboard.prototype.keyUpDown = function(event, fDown)
/*
* Avoid interfering with useful Browser key commands, like COMMAND-Q, COMMAND-T, etc.
*/
this.bitsShift &= ~Keyboard.STATE.COMMAND;
if (fDown) this.bitsShift |= Keyboard.STATE.COMMAND;
this.shiftStates &= ~Keyboard.STATES.COMMAND;
if (fDown) this.shiftStates |= Keyboard.STATES.COMMAND;
fAutoClear = false;
fPass = true;
}
@ -1476,7 +1613,7 @@ Keyboard.prototype.keyUpDown = function(event, fDown)
* above that explicitly clear fAutoClear -- such as the COMMAND key itself -- are exceptions
* to the rule).
*/
this.bitsShift &= ~Keyboard.STATE.COMMAND;
this.shiftStates &= ~Keyboard.STATES.COMMAND;
/*
* I don't reliably get keyDown/keyUp events for all keys on all devices, but for those devices that
* I DO, it seems like a good idea to cancel any pending key "up" simulation on receipt of the actual
@ -1529,11 +1666,11 @@ Keyboard.prototype.keyPress = function(event)
*/
fPass = false;
} else {
if (this.bitsShift & Keyboard.STATE.COMMAND) {
this.bitsShift &= ~Keyboard.STATE.COMMAND;
if (this.shiftStates & Keyboard.STATES.COMMAND) {
this.shiftStates &= ~Keyboard.STATES.COMMAND;
} 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.shiftStates & (Keyboard.STATES.CTRL | Keyboard.STATES.ALT)) {
fPass = false;
} else {
if (this.fEscapeDisabled && keyCode == Keyboard.ASCII['`']) keyCode = Keyboard.KEYCODE.ESC;
@ -1547,14 +1684,15 @@ Keyboard.prototype.keyPress = function(event)
};
/**
* keySimulatePress(keyCode)
* keySimulatePress(keyCode, fCheckShift, fQuickRelease)
*
* @this {Keyboard}
* @param {number} keyCode
* @param {boolean} [fCheckShift]
* @param {boolean} [fQuickRelease] is true to simulate the press and release immediately
* @return {boolean} true if successfully simulated, false if unrecognized/unsupported key
*/
Keyboard.prototype.keySimulatePress = function(keyCode, fQuickRelease)
Keyboard.prototype.keySimulatePress = function(keyCode, fCheckShift, fQuickRelease)
{
var fSimulated = false;
@ -1563,6 +1701,8 @@ Keyboard.prototype.keySimulatePress = function(keyCode, fQuickRelease)
*/
this.autoClear(keyCode);
if (fCheckShift) this.checkShiftState(keyCode);
if (this.keySimulateUpOrDown(keyCode, true, Keyboard.SIMCODE.KEYPRESS)) {
/*
* If fQuickRelease is set, we switch to an alternate approach, which is to immediately queue a
@ -1627,7 +1767,7 @@ Keyboard.prototype.keySimulateUpOrDown = function(keyCode, fDown, simCode)
* whenever both CTRL and ALT are pressed as well, so that it's easier to simulate that old favorite: CTRL-ALT-DEL
*/
if (wCode == Keyboard.SCANCODE.BS) {
if ((this.bitsShift & (Keyboard.STATE.CTRL | Keyboard.STATE.ALT)) == (Keyboard.STATE.CTRL | Keyboard.STATE.ALT)) {
if ((this.shiftStates & (Keyboard.STATES.CTRL | Keyboard.STATES.ALT)) == (Keyboard.STATES.CTRL | Keyboard.STATES.ALT)) {
wCode = Keyboard.SCANCODE.NUM_DEL;
}
}
@ -1651,17 +1791,17 @@ Keyboard.prototype.keySimulateUpOrDown = function(keyCode, fDown, simCode)
continue;
}
if (bScan == Keyboard.SCANCODE.SHIFT) {
if (!(this.bitsShift & (Keyboard.STATE.SHIFT | Keyboard.STATE.RSHIFT))) {
if (!(this.bitsShift & Keyboard.STATE.CAPS_LOCK) || !fAlpha) {
if (!(this.shiftStates & (Keyboard.STATES.SHIFT | Keyboard.STATES.RSHIFT))) {
if (!(this.shiftStates & Keyboard.STATES.CAPS_LOCK) || !fAlpha) {
bShift = bScan;
}
}
} else if (bScan == Keyboard.SCANCODE.CTRL) {
if (!(this.bitsShift & (Keyboard.STATE.CTRL | Keyboard.STATE.RCTRL))) {
if (!(this.shiftStates & (Keyboard.STATES.CTRL | Keyboard.STATES.RCTRL))) {
bShift = bScan;
}
} else if (bScan == Keyboard.SCANCODE.ALT) {
if (!(this.bitsShift & (Keyboard.STATE.ALT | Keyboard.STATE.RALT))) {
if (!(this.shiftStates & (Keyboard.STATES.ALT | Keyboard.STATES.RALT))) {
bShift = bScan;
}
} else {

View file

@ -87,8 +87,9 @@ if (typeof module !== 'undefined') {
* or setPixel().
*
* Thanks to the CPU's new block-based memory manager that allows us to sparse-allocate memory
* in 4K increments, updateScreen() can also ask the CPU for the "dirty" state of all the blocks
* underlying the video buffer, bypassing the update completely if the buffer is still clean.
* (in 4Kb increments on 20-bit buses, 16Kb increments on 24-bit buses), updateScreen()
* can also ask the CPU for the "dirty" state of all the blocks underlying the video buffer,
* bypassing the update completely if the buffer is still clean.
*
* Unfortunately, that optimization is defeated if our count of active blink elements is non-zero,
* because we must rescan the entire buffer to locate and redraw them all; I'm assuming for now
@ -499,8 +500,8 @@ Video.MODES.UNKNOWN = 0xFF;
* Supported Fonts
*
* Once we've finished loading the standard 8K font file, aFonts[] should contain one or more of the
* fonts listed below. For the standard MDA/CGA font ROM, the first (MDA) font resides in the first 4K,
* and the second and third (CGA) fonts reside in the two 2K halves of the second 4K.
* fonts listed below. For the standard MDA/CGA font ROM, the first (MDA) font resides in the first 4Kb,
* and the second and third (CGA) fonts reside in the two 2K halves of the second 4Kb.
*
* It may seem odd that the cell size for FONT_CGAD is *larger* than the cell size for FONT_CGA,
* since 40-column mode is actually lower resolution, but since we don't shrink the window when we shrink
@ -1018,7 +1019,7 @@ Card.MISC.IO_SELECT = 0x01; // 0 sets CRT ports to 0x3Bn, 1 sets CRT
Card.MISC.ENABLE_RAM = 0x02; // 0 disables video RAM, 1 enables
Card.MISC.CLK_SELECT = 0x0C; // 0x0: 14Mhz I/O clock, 0x4: 16Mhz on-board clock, 0x8: external clock, 0xC: unused
Card.MISC.DISABLE_DRV = 0x10; // 0 activates internal video drivers, 1 activates feature connector direct drive outputs
Card.MISC.PAGE_ODD_EVEN = 0x20; // 0 selects the low 64K page of video RAM for text modes, 1 selects the high page
Card.MISC.PAGE_ODD_EVEN = 0x20; // 0 selects the low 64Kb page of video RAM for text modes, 1 selects the high page
Card.MISC.HORZ_POLARITY = 0x40; // 0 selects positive horizontal retrace
Card.MISC.VERT_POLARITY = 0x80; // 0 selects positive vertical retrace
@ -1981,6 +1982,9 @@ Video.prototype.initBus = function(cmp, bus, cpu, dbg)
*/
this.kbd = cmp.getComponentByType("Keyboard");
if (this.kbd && this.canvasScreen) {
for (var s in this.bindings) {
if (s.indexOf("lock") > 0) this.kbd.setBinding("input", "led", s, this.bindings[s]);
}
this.kbd.setBinding("input", this.textareaScreen? "textarea" : "canvas", "kbd", this.textareaScreen || this.canvasScreen);
}
@ -2008,32 +2012,39 @@ Video.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
var video = this;
var canvas, lockPointer;
switch (sBinding) {
if (!this.bindings[sBinding]) {
case "lockPointer":
/*
* We now save every binding that comes in, so that if there are bindings for "caps-lock' and the like,
* we can forward them to the Keyboard.
*/
this.bindings[sBinding] = control;
this.sLockMessage = control.innerHTML;
if (this.canvasScreen && this.canvasScreen.lockPointer) {
control.onclick = function onClickLockPointer() {
if (DEBUG) video.messageDebugger("lockPointer()");
video.lockPointer(true);
switch (sBinding) {
case "lockPointer":
this.sLockMessage = control.innerHTML;
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":
control.onclick = function onClickRefresh() {
if (DEBUG) video.messageDebugger("refreshScreen()");
video.updateScreen(true);
};
} else {
if (DEBUG) this.log("Pointer Lock API not available");
control.parentNode.removeChild(control);
return true;
default:
break;
}
return true;
case "refresh":
this.bindings[sBinding] = control;
control.onclick = function onClickRefresh() {
if (DEBUG) video.messageDebugger("refreshScreen()");
video.updateScreen(true);
};
return true;
default:
break;
}
return false;
};

View file

@ -99,6 +99,16 @@
line-height: 34px; /* the equivalent of "vertical-align: middle" for single-line elements */
background-color: #ffffff;
}
.pcjs-led {
float: left;
width: 8px;
height: 8px;
margin: 4px;
border: 1px solid black;
text-align: center;
line-height: 19px; /* the equivalent of "vertical-align: middle" for single-line elements */
background-color: #000000;
}
.pcjs-reference {
float: left;
font-size: x-small;

View file

@ -378,6 +378,9 @@
</fieldset>
</form>
</xsl:when>
<xsl:when test="@type = 'led'">
<div class="{$APPCLASS}-{@class} {$APPCLASS}-{@type}" data-value="{$type},{$binding}"><xsl:value-of select="."/></div>
</xsl:when>
<xsl:when test="@type = 'separator'">
<hr/>
</xsl:when>

View file

@ -509,21 +509,21 @@ if (window && !window.document.ELEMENT_NODE) window.document.ELEMENT_NODE = 1;
*/
Component.bindComponentControls = function(component, element, sAppClass)
{
var iControl, aeControls;
aeControls = Component.getElementsByClass(element.parentNode, sAppClass + "-control");
for (iControl = 0; iControl < aeControls.length; iControl++) {
var iNode, aeChildNodes;
aeChildNodes = aeControls[iControl].childNodes;
for (iNode = 0; iNode < aeChildNodes.length; iNode++) {
var aeControls = Component.getElementsByClass(element.parentNode, sAppClass + "-control");
for (var iControl = 0; iControl < aeControls.length; iControl++) {
var aeChildNodes = aeControls[iControl].childNodes;
for (var iNode = 0; iNode < aeChildNodes.length; iNode++) {
var control = aeChildNodes[iNode];
if (control.nodeType !== window.document.ELEMENT_NODE) {
continue;
}
var sClass = control.getAttribute("class");
if (!sClass) continue;
var iClass, aClasses;
aClasses = sClass.split(" ");
for (iClass = 0; iClass < aClasses.length; iClass++) {
var aClasses = sClass.split(" ");
for (var iClass = 0; iClass < aClasses.length; iClass++) {
var parms;
sClass = aClasses[iClass];
switch (sClass) {
@ -535,7 +535,7 @@ Component.bindComponentControls = function(component, element, sAppClass)
} else {
Component.log('Component.bindComponentControls("' + component.toString() + '"): missing binding' + (parms? ' for ' + parms['type'] : ''), "warning");
}
aClasses = [];
iClass = aClasses.length;
break;
default:
// Component.log("Component.bindComponentControls(" + component.toString() + "): unrecognized control class \"" + sClass + "\"", "warning");