Added more scripting support to PCx86 (eg, for loading disks, typing keys, and waiting for both to finish)

This commit is contained in:
Jeff Parsons 2017-07-25 10:30:29 -07:00 committed by Jeff Parsons
commit 1b987df6e2
9 changed files with 1251 additions and 1186 deletions

View file

@ -11,7 +11,16 @@ machines:
path: /disks/pcx86/dos/ibm/2.00/PCDOS200-DISK1.json path: /disks/pcx86/dos/ibm/2.00/PCDOS200-DISK1.json
B: B:
path: /disks/pcx86/apps/ibm/topview/1.01/TOPVIEW101-PROGRAM.json path: /disks/pcx86/apps/ibm/topview/1.01/TOPVIEW101-PROGRAM.json
autoType: \r\rb:\rtv\r commands:
autoRun: |
type Keyboard "$date\r$time\r";
wait Keyboard;
sleep 1000;
select FDC listDrives "A:";
select FDC listDisks "MS Mouse 5.00 (System)";
loadDisk FDC;
wait FDC;
type Keyboard "MOUSE\r";
--- ---
TopView 1.01 TopView 1.01
@ -28,6 +37,8 @@ paragraphs of English text will likely eliminate the problem.
{% include machine.html id="ibm5160" %} {% include machine.html id="ibm5160" %}
Click this button: {% include machine-command.html type='button' label='Load' machine='ibm5160' command='autoRun' %}
### Directory of TopView 1.01 (Program) ### Directory of TopView 1.01 (Program)
Volume in drive A is TV101032885 Volume in drive A is TV101032885

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -194,7 +194,8 @@ class FDC extends Component {
*/ */
this['exports'] = { this['exports'] = {
'loadDisk': this.loadSelectedDisk 'loadDisk': this.loadSelectedDisk,
'wait': this.waitDrives
}; };
} }
@ -274,24 +275,7 @@ class FDC extends Component {
} }
} }
controlSelect.onchange = function onChangeListDisks(event) { controlSelect.onchange = function onChangeListDisks(event) {
var controlDesc = fdc.bindings["descDisk"]; fdc.updateSelectedDiskette();
var controlOption = controlSelect.options[controlSelect.selectedIndex];
if (controlDesc && controlOption) {
var dataValue = {};
var sValue = controlOption.getAttribute("data-value");
if (sValue) {
try {
dataValue = eval("(" + sValue + ")");
} catch (e) {
Component.error("FDC option error: " + e.message);
}
}
var sHTML = dataValue['desc'];
if (sHTML === undefined) sHTML = "";
var sHRef = dataValue['href'];
if (sHRef !== undefined) sHTML = "<a href=\"" + sHRef + "\" target=\"_blank\">" + sHTML + "</a>";
controlDesc.innerHTML = sHTML;
}
}; };
return true; return true;
@ -749,6 +733,7 @@ class FDC extends Component {
drive.iDrive = iDrive; drive.iDrive = iDrive;
drive.fBusy = drive.fLocal = false; drive.fBusy = drive.fLocal = false;
drive.fnCallReady = null;
if (data === undefined) { if (data === undefined) {
/* /*
@ -1106,6 +1091,9 @@ class FDC extends Component {
/** /**
* loadSelectedDisk() * loadSelectedDisk()
* *
* NOTE: Since this can be called via script command (eg, 'loadDisk FDC'), additional parameters can be
* passed; use the arguments array to access them if necessary.
*
* @this {FDC} * @this {FDC}
* @return {boolean} * @return {boolean}
*/ */
@ -1331,7 +1319,7 @@ class FDC extends Component {
* WARNING: This conversion of drive number to drive letter, starting with A:, is very simplistic * WARNING: This conversion of drive number to drive letter, starting with A:, is very simplistic
* and will not match the drive mappings that DOS ultimately uses (ie, for drives beyond B:). * and will not match the drive mappings that DOS ultimately uses (ie, for drives beyond B:).
*/ */
this.notice("Mounted diskette \"" + sDisketteName + "\" in drive " + String.fromCharCode(0x41 + drive.iDrive), drive.fAutoMount || fAutoMount); if (!drive.fnCallReady) this.notice("Mounted diskette \"" + sDisketteName + "\" in drive " + String.fromCharCode(0x41 + drive.iDrive), drive.fAutoMount || fAutoMount);
/* /*
* Update the drive's current media parameters to match the disk's. * Update the drive's current media parameters to match the disk's.
@ -1356,6 +1344,11 @@ class FDC extends Component {
} }
this.displayDiskette(drive.iDrive); this.displayDiskette(drive.iDrive);
if (drive.fnCallReady) {
drive.fnCallReady();
drive.fnCallReady = null;
}
} }
/** /**
@ -1481,6 +1474,53 @@ class FDC extends Component {
} }
} }
/**
* updateSelectedDiskette()
*
* @this {FDC}
*/
updateSelectedDiskette()
{
var control = this.bindings["listDisks"];
var controlDesc = this.bindings["descDisk"];
var controlOption = control.options[control.selectedIndex];
if (controlDesc && controlOption) {
var dataValue = {};
var sValue = controlOption.getAttribute("data-value");
if (sValue) {
try {
dataValue = eval("(" + sValue + ")");
} catch (e) {
Component.error(this.type + " option error: " + e.message);
}
}
var sHTML = dataValue['desc'];
if (sHTML === undefined) sHTML = "";
var sHRef = dataValue['href'];
if (sHRef !== undefined) sHTML = "<a href=\"" + sHRef + "\" target=\"_blank\">" + sHTML + "</a>";
controlDesc.innerHTML = sHTML;
}
}
/**
* waitDrives(fnCallReady)
*
* @this {FDC}
* @param {function()|null} fnCallReady
* @return {boolean} false if wait required, true otherwise
*/
waitDrives(fnCallReady)
{
for (var iDrive = 0; iDrive < this.aDrives.length; iDrive++) {
var drive = this.aDrives[iDrive];
if (drive && drive.fBusy) {
if (!drive.fnCallReady) drive.fnCallReady = fnCallReady;
return false;
}
}
return true;
}
/** /**
* unloadDrive(iDrive, fAutoUnload, fQuiet) * unloadDrive(iDrive, fAutoUnload, fQuiet)
* *

View file

@ -159,6 +159,11 @@ class Keyboard extends Component {
*/ */
this.fAllDown = false; this.fAllDown = false;
this['exports'] = {
'type': this.injectKeys,
'wait': this.waitReady
};
this.setReady(); this.setReady();
} }
@ -380,9 +385,7 @@ class Keyboard extends Component {
intDOS(addr) intDOS(addr)
{ {
var AH = (this.cpu.regEAX >> 8) & 0xff; var AH = (this.cpu.regEAX >> 8) & 0xff;
if (AH == 0x0A) { if (AH == 0x0A) this.injectInit(this.autoType);
this.injectInit();
}
return true; return true;
} }
@ -402,7 +405,7 @@ class Keyboard extends Component {
} }
/** /**
* parseAutoType(sKeys) * parseKeys(sKeys)
* *
* The following special sequences are recognized: * The following special sequences are recognized:
* *
@ -438,7 +441,7 @@ class Keyboard extends Component {
* @param {string|undefined} sKeys * @param {string|undefined} sKeys
* @return {string|undefined} * @return {string|undefined}
*/ */
parseAutoType(sKeys) parseKeys(sKeys)
{ {
if (sKeys) { if (sKeys) {
var match, reSpecial = /(?:^|[^$])\$([a-z]+)/g; var match, reSpecial = /(?:^|[^$])\$([a-z]+)/g;
@ -862,7 +865,7 @@ class Keyboard extends Component {
data = []; data = [];
this.autoInject = null; this.autoInject = null;
} else { } else {
this.autoInject = this.parseAutoType(this.autoType); this.autoInject = this.autoType;
} }
this.fClock = this.fAdvance = data[i++]; this.fClock = this.fAdvance = data[i++];
this.fData = data[i]; this.fData = data[i];
@ -888,6 +891,8 @@ class Keyboard extends Component {
* Make sure the auto-injection buffer is empty (an injection could have been in progress on any reset after the first). * Make sure the auto-injection buffer is empty (an injection could have been in progress on any reset after the first).
*/ */
this.sInjectBuffer = ""; this.sInjectBuffer = "";
this.fnCallReady = null;
return true; return true;
} }
@ -950,16 +955,19 @@ class Keyboard extends Component {
} }
/** /**
* injectInit() * injectInit(sKeys)
* *
* @this {Keyboard} * @this {Keyboard}
* @param {string|undefined} sKeys
* @return {boolean}
*/ */
injectInit() injectInit(sKeys)
{ {
if (!this.autoInject && this.autoType) { if (!this.autoInject && sKeys) {
this.autoInject = this.parseAutoType(this.autoType); this.autoInject = sKeys;
this.injectKeys(this.autoInject); return this.injectKeys(this.autoInject);
} }
return false;
} }
/** /**
@ -968,14 +976,17 @@ class Keyboard extends Component {
* @this {Keyboard} * @this {Keyboard}
* @param {string|undefined} sKeys * @param {string|undefined} sKeys
* @param {number} [msDelay] is an optional injection delay (default is msInjectDelay) * @param {number} [msDelay] is an optional injection delay (default is msInjectDelay)
* @return {boolean}
*/ */
injectKeys(sKeys, msDelay) injectKeys(sKeys, msDelay)
{ {
if (sKeys && !this.sInjectBuffer) { if (sKeys && !this.sInjectBuffer) {
this.sInjectBuffer = sKeys; this.sInjectBuffer = this.parseKeys(sKeys);
if (!COMPILED) this.log("injectKeys(" + this.sInjectBuffer.split("\n").join("\\n") + ")"); if (!COMPILED) this.log("injectKeys(" + this.sInjectBuffer.split("\n").join("\\n") + ")");
this.injectKeysFromBuffer(msDelay || this.msInjectDelay); this.injectKeysFromBuffer(msDelay || this.msInjectDelay);
return true;
} }
return false;
} }
/** /**
@ -1014,7 +1025,12 @@ class Keyboard extends Component {
if (charCode == 0x0A) charCode = 0x0D; if (charCode == 0x0A) charCode = 0x0D;
this.addActiveKey(charCode, true); this.addActiveKey(charCode, true);
} }
if (this.sInjectBuffer.length > 0) { if (!this.sInjectBuffer.length) {
if (this.fnCallReady) {
this.fnCallReady();
this.fnCallReady = null;
}
} else {
setTimeout(function(kbd) { setTimeout(function(kbd) {
return function onInjectKeyTimeout() { return function onInjectKeyTimeout() {
kbd.injectKeysFromBuffer(msDelay); kbd.injectKeysFromBuffer(msDelay);
@ -1023,6 +1039,22 @@ class Keyboard extends Component {
} }
} }
/**
* waitReady(fnCallReady)
*
* @this {Keyboard}
* @param {function()|null} fnCallReady
* @return {boolean} false if wait required, true otherwise
*/
waitReady(fnCallReady)
{
if (this.sInjectBuffer.length > 0) {
if (!this.fnCallReady) this.fnCallReady = fnCallReady;
return false;
}
return true;
}
/** /**
* setLED(control, f) * setLED(control, f)
* *

View file

@ -81,6 +81,7 @@ class DriveController extends Component {
* *
* autoMount: one or more JSON-encoded objects, each containing 'name' and 'path' properties * autoMount: one or more JSON-encoded objects, each containing 'name' and 'path' properties
* *
* @this {DriveController}
* @param {string} type * @param {string} type
* @param {Object} parms * @param {Object} parms
* @param {number} bitsMessage * @param {number} bitsMessage
@ -123,10 +124,10 @@ class DriveController extends Component {
this.irq = null; this.irq = null;
this['exports'] = { this['exports'] = {
'bootDisk': this.bootSelectedDisk, 'bootDisk': this.bootSelectedDisk,
'loadDisk': this.loadSelectedDrive, 'loadDisk': this.loadSelectedDisk,
'selectDrive': this.selectDrive, 'selectDrive': this.selectDrive,
'wait': this.waitDrives 'wait': this.waitDrives
}; };
} }
@ -194,7 +195,7 @@ class DriveController extends Component {
case "loadDisk": case "loadDisk":
this.bindings[sBinding] = control; this.bindings[sBinding] = control;
control.onclick = function onClickLoadDrive(event) { control.onclick = function onClickLoadDrive(event) {
dc.loadSelectedDrive(); dc.loadSelectedDisk();
}; };
return true; return true;
@ -281,7 +282,7 @@ class DriveController extends Component {
if (file) { if (file) {
var sDiskPath = file.name; var sDiskPath = file.name;
var sDiskName = Str.getBaseName(sDiskPath, true); var sDiskName = Str.getBaseName(sDiskPath, true);
dc.loadSelectedDrive(sDiskName, sDiskPath, file); dc.loadSelectedDisk(sDiskName, sDiskPath, file);
} }
/* /*
* Prevent reloading of web page after form submission * Prevent reloading of web page after form submission
@ -543,7 +544,7 @@ class DriveController extends Component {
drive.iHeadBoot = configDrive[i++]; drive.iHeadBoot = configDrive[i++];
drive.iSectorBoot = configDrive[i++]; drive.iSectorBoot = configDrive[i++];
drive.cbSectorBoot = configDrive[i++]; drive.cbSectorBoot = configDrive[i++];
drive.status = configDrive[i++]; drive.status = configDrive[i];
/* /*
* The next group of properties are set by various controller command sequences. * The next group of properties are set by various controller command sequences.
@ -723,7 +724,7 @@ class DriveController extends Component {
} }
/** /**
* loadSelectedDrive(sDiskName, sDiskPath, file) * loadSelectedDisk(sDiskName, sDiskPath, file)
* *
* @this {DriveController} * @this {DriveController}
* @param {string} [sDiskName] * @param {string} [sDiskName]
@ -731,7 +732,7 @@ class DriveController extends Component {
* @param {File} [file] is set if there's an associated File object * @param {File} [file] is set if there's an associated File object
* @return {boolean} * @return {boolean}
*/ */
loadSelectedDrive(sDiskName, sDiskPath, file) loadSelectedDisk(sDiskName, sDiskPath, file)
{ {
if (!sDiskName && !sDiskPath) { if (!sDiskName && !sDiskPath) {
var controlDisks = this.bindings["listDisks"]; var controlDisks = this.bindings["listDisks"];
@ -836,7 +837,7 @@ class DriveController extends Component {
this.unloadDrive(iDrive, true); this.unloadDrive(iDrive, true);
drive.fLocal = true; drive.fLocal = true;
var disk = new DiskPDP11(this, drive, DiskAPI.MODE.PRELOAD); var disk = new DiskPDP11(this, drive, DiskAPI.MODE.PRELOAD);
this.finishLoadDrive(drive, disk, sDiskName, sDiskPath, true); this.doneLoadDrive(drive, disk, sDiskName, sDiskPath, true);
} }
/** /**
@ -875,7 +876,7 @@ class DriveController extends Component {
} }
drive.fLocal = !!file; drive.fLocal = !!file;
var disk = new DiskPDP11(this, drive, DiskAPI.MODE.PRELOAD); var disk = new DiskPDP11(this, drive, DiskAPI.MODE.PRELOAD);
if (disk.load(sDiskName, sDiskPath, file, this.finishLoadDrive)) { if (disk.load(sDiskName, sDiskPath, file, this.doneLoadDrive)) {
nResult++; nResult++;
} }
} }
@ -884,7 +885,7 @@ class DriveController extends Component {
} }
/** /**
* finishLoadDrive(drive, disk, sDiskName, sDiskPath, fAutoMount) * doneLoadDrive(drive, disk, sDiskName, sDiskPath, fAutoMount)
* *
* The disk parameter is set if the disk was successfully loaded, null if not. * The disk parameter is set if the disk was successfully loaded, null if not.
* *
@ -895,7 +896,7 @@ class DriveController extends Component {
* @param {string} sDiskPath * @param {string} sDiskPath
* @param {boolean} [fAutoMount] * @param {boolean} [fAutoMount]
*/ */
finishLoadDrive(drive, disk, sDiskName, sDiskPath, fAutoMount) doneLoadDrive(drive, disk, sDiskName, sDiskPath, fAutoMount)
{ {
drive.fBusy = false; drive.fBusy = false;

View file

@ -763,7 +763,7 @@ class Component {
* Component.processScript(idMachine, sScript) * Component.processScript(idMachine, sScript)
* *
* @param {string} idMachine * @param {string} idMachine
* @param {string} sScript * @param {string} [sScript]
* @return {boolean} * @return {boolean}
*/ */
static processScript(idMachine, sScript) static processScript(idMachine, sScript)
@ -848,7 +848,7 @@ class Component {
} }
if (!fSuccess) { if (!fSuccess) {
Component.alertUser("Script error: " + sCommand + (fnCommand? " failed" : " unrecognized")); Component.alertUser("Script error: '" + sCommand + "' command " + (fnCommand? " failed" : " unrecognized"));
break; break;
} }
} }

View file

@ -623,15 +623,47 @@ function findMachineComponent(idMachine, sType)
} }
/** /**
* processMachineScript(idMachine, sScript) * commandMachine(control, fSingle, idMachine, sComponent, sCommand, sValue)
* *
* Use Component methods to find the requested component for a specific machine, and if the component is found,
* then check its 'exports' table for an entry matching the specified command string, and if an entry is found, then
* the corresponding function is called with the specified data.
*
* @param {Object} control
* @param {boolean} fSingle
* @param {string} idMachine * @param {string} idMachine
* @param {string} sScript * @param {string} sComponent
* @param {string} sCommand
* @param {string} [sValue]
* @return {boolean} * @return {boolean}
*/ */
function processMachineScript(idMachine, sScript) function commandMachine(control, fSingle, idMachine, sComponent, sCommand, sValue)
{ {
return Component.processScript(idMachine, sScript); if (sCommand == "script") {
if (Component.processScript(idMachine, sValue)) {
if (fSingle) control.disabled = true;
return true;
}
return false;
}
if (sComponent) {
var component = Component.getComponentByType(sComponent, idMachine + ".machine");
if (component) {
var exports = component['exports'];
if (exports) {
var fnCommand = exports[sCommand];
if (fnCommand) {
if (fnCommand.call(component, sValue)) {
if (fSingle) control.disabled = true;
return true;
}
return false;
}
}
}
}
console.log("unimplemented: commandMachine('" + idMachine + "','" + sComponent + "','" + sCommand + "','" + sValue + "')");
return false;
} }
/** /**
@ -655,8 +687,7 @@ if (APPNAME == "PDPjs") {
window['embedPDP11'] = embedPDP11; window['embedPDP11'] = embedPDP11;
} }
window['findMachineComponent'] = findMachineComponent; window['commandMachine'] = commandMachine;
window['processMachineScript'] = processMachineScript;
window['enableEvents'] = Web.enablePageEvents; window['enableEvents'] = Web.enablePageEvents;
window['sendEvent'] = Web.sendPageEvent; window['sendEvent'] = Web.sendPageEvent;

View file

@ -87,59 +87,6 @@ function addStickyMachine(idMachine, sPosition)
}; };
} }
/**
* commandMachine(control, fSingle, idMachine, sComponent, sCommand, sValue)
*
* Note that this script is not compiled into any of the machines, since "sticky machines" are feature of the PCjs
* website rather than of the machines. And since the machines are compiled, all their code and data is completely
* opaque to us, except for those functions explicitly exported by embed.js.
*
* So now, in addition to the functions for embedding machines on a webpage, embed.js includes a few new functions:
*
* findMachineComponent()
* processMachineScript()
*
* which uses existing Component methods to find the requested component for a specific machine, and if the
* component is found, then its 'exports' table is checked for an entry matching the specified command string, and if
* an entry is found, then the corresponding function is called with the specified data.
*
* @param {Object} control
* @param {boolean} fSingle
* @param {string} idMachine
* @param {string} sComponent
* @param {string} sCommand
* @param {string} [sValue]
* @return {boolean}
*/
function commandMachine(control, fSingle, idMachine, sComponent, sCommand, sValue)
{
if (sCommand == "script" && window.processMachineScript) {
if (window.processMachineScript(idMachine, sValue)) {
if (fSingle) control.disabled = true;
return true;
}
return false;
}
if (sComponent && window.findMachineComponent) {
var component = window.findMachineComponent(idMachine, sComponent);
if (component) {
var exports = component['exports'];
if (exports) {
var fnCommand = exports[sCommand];
if (fnCommand) {
if (fnCommand.call(component, sValue)) {
if (fSingle) control.disabled = true;
return true;
}
return false;
}
}
}
}
console.log("unimplemented: commandMachine('" + idMachine + "','" + sComponent + "','" + sCommand + "','" + sValue + "')");
return false;
}
/** /**
* findTop(obj) * findTop(obj)
* *