/** * @fileoverview Implements the PDP-11 SerialPort component (eg, DL11) * @author Jeff Parsons * @copyright © Jeff Parsons 2012-2017 * * This file is part of PCjs, a computer emulation software project at . * * It has been adapted from the JavaScript PDP 11/70 Emulator written by Paul Nankervis * (paulnank@hotmail.com) at . This code may be used * freely provided the original authors are acknowledged in any modified source code. * * 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 modified copy of this work * and to display that copyright notice when the software starts running; see COPYRIGHT in * . * * 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 Keys = require("../../shared/lib/keys"); var State = require("../../shared/lib/state"); var PDP11 = require("./defines"); var MessagesPDP11 = require("./messages"); } /** * Since the Closure Compiler treats ES6 classes as @struct rather than @dict by default, * it deters us from defining named properties on our components; eg: * * this['exports'] = {...} * * results in an error: * * Cannot do '[]' access on a struct * * So, in order to define 'exports', we must override the @struct assumption by annotating * the class as @unrestricted (or @dict). Note that this must be done both here and in the * Component class, because otherwise the Compiler won't allow us to *reference* the named * property either. * * TODO: Consider marking ALL our classes unrestricted, because otherwise it forces us to * define every single property the class uses in its constructor, which results in a fair * bit of redundant initialization, since many properties aren't (and don't need to be) fully * initialized until the appropriate init(), reset(), restore(), etc. function is called. * * The upside, however, may be that since the structure of the class is completely defined by * the constructor, JavaScript engines may be able to optimize and run more efficiently. * * @unrestricted */ class SerialPortPDP11 extends Component { /** * SerialPortPDP11(parmsSerial) * * The SerialPort component has the following component-specific (parmsSerial) properties: * * adapter: adapter number; 0 if not defined (the PCx86 SerialPort component uses this * value to set the device's internal COM number, which in turn determines other properties, * such as I/O ports and IRQ; for the PDP-11, this currently has no defined use) * * baudReceive: the default number of bits/second that the device should receive data at; * 0 means use the device default (PDP11.DL11.RCSR.BAUD) * * baudTransmit: the default number of bits/second that the device should transmit data at; * 0 means use the device default (PDP11.DL11.XCSR.BAUD) * * 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) * * upperCase: if true, all received input is upper-cased; it is normally the responsibility * of the sending device to ensure this, but sometimes it's more convenient to enforce * on the receiving end. * * NOTE: Since the XSL file defines the 'adapter' and 'baud' properties as numbers, not strings, * 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. * * @param {Object} parmsSerial */ constructor(parmsSerial) { super("SerialPort", parmsSerial, MessagesPDP11.SERIAL); this.iAdapter = +parmsSerial['adapter']; this.nBaudReceive = +parmsSerial['baudReceive'] || PDP11.DL11.RCSR.BAUD; this.nBaudTransmit = +parmsSerial['baudTransmit'] || PDP11.DL11.XCSR.BAUD; this.fUpperCase = parmsSerial['upperCase']; if (typeof this.fUpperCase == "string") this.fUpperCase = (this.fUpperCase == "true"); /** * 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 bound to the port (currently used for output only; see transmitByte()). * * Example: CTTY COM2 * * The CTTY DOS command redirects all CON I/O to the specified serial port (eg, COM2), which it assumes is * connected to a serial terminal, and therefore anything it *transmits* via COM2 will be displayed by the * terminal. It further assumes that anything typed on such a terminal is NOT displayed, so as DOS *receives* * serial input, DOS *transmits* the appropriate characters back to the terminal via COM2. * * As a result, controlIOBuffer only needs to be updated by the transmitByte() function. * * @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 transmitByte(), 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; this.fNullModem = true; this.irqReceiver = this.irqTransmitter = null; this.timerReceiveInterrupt = this.timerTransmitInterrupt = -1; this.regRBUF = this.regRCSR = this.regXCSR = 0; this.abReceive = []; 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, SerialPortPDP11.sIOBuffer); } /* * No connection until initConnection() is called. */ this.sDataReceived = ""; this.connection = this.sendData = this.updateStatus = null; /* * Export all functions required by initConnection(). */ this['exports'] = { 'connect': this.initConnection, 'receiveData': this.receiveData, 'receiveStatus': this.receiveStatus, 'setConnection': this.setConnection }; } /** * setBinding(sType, sBinding, control, sValue) * * @this {SerialPortPDP11} * @param {string|null} sType is the type of the HTML control (eg, "button", "textarea", "register", "flag", "rled", etc) * @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 */ setBinding(sType, sBinding, control, sValue) { var serial = this; switch (sBinding) { case SerialPortPDP11.sIOBuffer: this.bindings[sBinding] = this.controlIOBuffer = control; /* * An onkeydown handler is required for certain keys that browsers tend to consume themselves; * for example, BACKSPACE is often defined as going back to the previous web page, and certain * CTRL keys are often used for browser shortcuts (usually on Windows-based browsers). * * NOTE: We don't bother with a keyUp handler, because for the most part, we're only intercepting * keys that require special treatment; in general, we're content with keyPress events. */ control.onkeydown = function onKeyDown(event) { event = event || window.event; var bASCII = 0; var keyCode = event.keyCode; /* * Perform the same remapping of BACKSPACE and DELETE that our VT100 emulation performs, * for PCjs-wide consistency; see the KEYMAP table in /modules/pc8080/lib/keyboard.js for * the rationale. Ditto for ALT-DELETE; see onKeyDown() in /modules/pc8080/lib/keyboard.js * for details. * * NOTE: keyDown (and keyUp) events supply us with KEYCODE values, which are NOT the same as * ASCII values, which is why we are comparing with KEYCODE values but assigning ASCII values, * because receiveData() requires ASCII values. */ if (keyCode == Keys.KEYCODE.BS) { bASCII = event.altKey? Keys.ASCII.CTRL_H : Keys.ASCII.DEL; } else if (keyCode == Keys.KEYCODE.DEL) { bASCII = Keys.ASCII.CTRL_H; } else if (event.ctrlKey && keyCode >= Keys.ASCII.A && keyCode <= Keys.ASCII.Z) { bASCII = keyCode - (Keys.ASCII.A - Keys.ASCII.CTRL_A); } if (bASCII) { if (event.preventDefault) event.preventDefault(); serial.receiveData(bASCII); } return true; }; control.onkeypress = function onKeyPress(event) { /* * NOTE: Unlike keyDown events, keyPress events generally supply us with ASCII values, * despite the fact that, as above, they come to us via the keyCode property. Yes, it's * brilliant (or rather, the opposite of brilliant), but that's life. */ event = event || window.event; /* * Not sure why COMMAND-key combinations are coming through here (on Safari at least), * but in any case, let's make sure we don't act on them. */ if (!event.metaKey) { var bASCII = event.which || event.keyCode; /* * Perform the same remapping of ALT-ENTER (to LINE-FEED) that our VT100 emulation performs, * for PCjs-wide consistency; see onKeyDown() in /modules/pc8080/lib/keyboard.js for details. */ if (event.altKey) { if (bASCII == Keys.ASCII.CTRL_M) { bASCII = Keys.ASCII.CTRL_J; } } serial.receiveData(bASCII); /* * Since we're going to remove the "readonly" attribute from the