/** * @fileoverview Implements the PC8080 SerialPort component. * @author Jeff Parsons * @version 1.0 * Created 2016-Aug-08 * * Copyright © 2012-2016 Jeff Parsons * * This file is part of PCjs, a computer emulation software project at . * * 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 . * * You are required to include the above copyright notice in every source code file of every * copy or modified version of this work, and to display that copyright notice on every screen * that loads or runs any version of this software (see COPYRIGHT in /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 Messages = require("./messages"); var ChipSet = require("./chipset"); var State = require("./state"); } /** * SerialPort(parmsSerial) * * The SerialPort component has the following component-specific (parmsSerial) properties: * * adapter: 0 if not defined * * binding: name of a control (based on its "binding" attribute) to bind to this port's I/O * * tabSize: set to a non-zero number to convert tabs to spaces (applies only to output to * the above binding); default is 0 (no conversion) * * In the future, we may support 'port' and 'irq' properties that allow the machine to define a * non-standard serial port configuration, instead of only our pre-defined 'adapter' configurations. * * NOTE: Since the XSL file defines 'adapter' as a number, not a string, there's no need to use * parseInt(), and as an added benefit, we don't need to worry about whether a hex or decimal format * was used. * * @constructor * @extends Component * @param {Object} parmsSerial */ function SerialPort(parmsSerial) { this.iAdapter = +parmsSerial['adapter']; switch (this.iAdapter) { case 0: this.portBase = 0; this.nIRQ = 2; break; default: Component.warning("Unrecognized serial adapter #" + this.iAdapter); return; } /** * consoleOutput becomes a string that records serial port output if the 'binding' property is set to the * reserved name "console". Nothing is written to the console, however, until a linefeed (0x0A) is output * or the string length reaches a threshold (currently, 1024 characters). * * @type {string|null} */ this.consoleOutput = null; /** * controlIOBuffer is a DOM element, if any, bound to the port (currently used for output only; see echoByte()). * * @type {Object} */ this.controlIOBuffer = null; /* * If controlIOBuffer is being used AND 'tabSize' is set, then we make an attempt to monitor the characters * being echoed via echoByte(), maintain a logical column position, and convert any tabs into the appropriate * number of spaces. * * charBOL, if nonzero, is a character to automatically output at the beginning of every line. This probably * isn't generally useful; I use it internally to preformat serial output. */ this.tabSize = parmsSerial['tabSize']; this.charBOL = parmsSerial['charBOL']; this.iLogicalCol = 0; Component.call(this, "SerialPort", parmsSerial, SerialPort, Messages.SERIAL); var sBinding = parmsSerial['binding']; if (sBinding == "console") { this.consoleOutput = ""; } else { /* * NOTE: If sBinding is not the name of a valid Control Panel DOM element, this call does nothing. */ Component.bindExternalControl(this, sBinding, SerialPort.sIOBuffer); } } /* * class SerialPort * property {number} iAdapter * property {number} portBase * property {number} nIRQ * property {Object} controlIOBuffer is a DOM element, if any, bound to the port (for rudimentary output; see echoByte()) * * NOTE: This class declaration started as a way of informing the code inspector of the controlIOBuffer property, * which remained undefined until a setBinding() call set it later, but I've since decided that explicitly * initializing such properties in the constructor is a better way to go -- even though it's more code -- because * JavaScript compilers are supposed to be happier when the underlying object structures aren't constantly changing. * * Besides, I'm not sure I want to get into documenting every property this way, for this or any/every other class, * let alone getting into which ones should be considered private or protected, because PCjs isn't really a library * for third-party apps. */ Component.subclass(SerialPort); /* * Internal name used for the I/O buffer control, if any, that we bind to the SerialPort. * * Alternatively, if SerialPort wants to use another component's control (eg, the Panel's * "print" control), it can specify the name of that control with the 'binding' property. * * For that binding to succeed, we also need to know the target component; for now, that's * been hard-coded to "Panel", in part because that's one of the few components we can rely * upon initializing before we do, but it would be a simple matter to include a component type * or ID as part of the 'binding' property as well, if we need more flexibility later. */ SerialPort.sIOBuffer = "buffer"; /** * setBinding(sHTMLType, sBinding, control, sValue) * * @this {SerialPort} * @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas") * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "buffer") * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement) * @param {string} [sValue] optional data value * @return {boolean} true if binding was successful, false if unrecognized binding request */ SerialPort.prototype.setBinding = function(sHTMLType, sBinding, control, sValue) { var serial = this; switch (sBinding) { case SerialPort.sIOBuffer: this.bindings[sBinding] = this.controlIOBuffer = control; /* * By establishing an onkeypress handler here, we make it possible for DOS commands like * "CTTY COM1" to more or less work (use "CTTY CON" to restore control to the DOS console). */ control.onkeydown = function onKeyDown(event) { /* * This is required in addition to onkeypress, because it's the only way to prevent * BACKSPACE (keyCode 8) from being interpreted by the browser as a "Back" operation; * moreover, not all browsers generate an onkeypress notification for BACKSPACE. * * A related problem exists for Ctrl-key combinations in most Windows-based browsers * (eg, IE, Edge, Chrome for Windows, etc), because keys like Ctrl-C and Ctrl-S have * special meanings (eg, Copy, Save). To the extent the browser will allow it, we * attempt to disable that default behavior when this control receives an onkeydown * event for one of those keys (probably the only event the browser generates for them). */ event = event || window.event; var keyCode = event.keyCode; if (keyCode === 0x08 || event.ctrlKey && keyCode >= 0x41 && keyCode <= 0x5A) { if (event.preventDefault) event.preventDefault(); if (keyCode > 0x40) keyCode -= 0x40; // serial.sendRBR([keyCode]); } return true; }; control.onkeypress = function onKeyPress(event) { /* * Browser-independent keyCode extraction; refer to onKeyPress() and the other key event * handlers in keyboard.js. */ event = event || window.event; var keyCode = event.which || event.keyCode; // serial.sendRBR([keyCode]); /* * Since we're going to remove the "readonly" attribute from the