| /* sim_console.c: simulator console I/O library | |
| Copyright (c) 1993-2012, Robert M Supnik | |
| Permission is hereby granted, free of charge, to any person obtaining a | |
| copy of this software and associated documentation files (the "Software"), | |
| to deal in the Software without restriction, including without limitation | |
| the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| and/or sell copies of the Software, and to permit persons to whom the | |
| Software is furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in | |
| all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
| ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| Except as contained in this notice, the name of Robert M Supnik shall not be | |
| used in advertising or otherwise to promote the sale, use or other dealings | |
| in this Software without prior written authorization from Robert M Supnik. | |
| 18-Mar-12 RMS Removed unused reference to sim_switches (Dave Bryan) | |
| 07-Dec-11 MP Added sim_ttisatty to support reasonable behaviour (i.e. | |
| avoid in infinite loop) in the main command input | |
| loop when EOF is detected and input is coming from | |
| a file (or a null device: /dev/null or NUL:) This may | |
| happen when a simulator is running in a background | |
| process. | |
| 17-Apr-11 MP Cleaned up to support running in a background/detached | |
| process | |
| 20-Jan-11 MP Fixed support for BREAK key on Windows to account | |
| for/ignore other keyboard Meta characters. | |
| 18-Jan-11 MP Added log file reference count support | |
| 17-Jan-11 MP Added support for a "Buffered" behaviors which include: | |
| - If Buffering is enabled and Telnet is enabled, a | |
| telnet connection is not required for simulator | |
| operation (instruction execution). | |
| - If Buffering is enabled, all console output is | |
| written to the buffer at all times (deleting the | |
| oldest buffer contents on overflow). | |
| - when a connection is established on the console | |
| telnet port, the whole contents of the Buffer is | |
| presented on the telnet session and connection | |
| will then proceed as if the connection had always | |
| been there. | |
| This concept allows a simulator to run in the background | |
| and when needed a console session to be established. | |
| The "when needed" case usually will be interested in | |
| what already happened before looking to address what | |
| to do, hence the buffer contents being presented. | |
| 28-Dec-10 MP Added support for BREAK key on Windows | |
| 30-Sep-06 RMS Fixed non-printable characters in KSR mode | |
| 22-Jun-06 RMS Implemented SET/SHOW PCHAR | |
| 31-May-06 JDB Fixed bug if SET CONSOLE DEBUG with no argument | |
| 22-Nov-05 RMS Added central input/output conversion support | |
| 05-Nov-04 RMS Moved SET/SHOW DEBUG under CONSOLE hierarchy | |
| 28-Oct-04 JDB Fixed SET CONSOLE to allow comma-separated parameters | |
| 20-Aug-04 RMS Added OS/2 EMX fixes (Holger Veit) | |
| 14-Jul-04 RMS Revised Windows console code (Dave Bryan) | |
| 28-May-04 RMS Added SET/SHOW CONSOLE | |
| RMS Added break, delete character maps | |
| 02-Jan-04 RMS Removed timer routines, added Telnet console routines | |
| RMS Moved console logging to OS-independent code | |
| 25-Apr-03 RMS Added long seek support (Mark Pizzolato) | |
| Added Unix priority control (Mark Pizzolato) | |
| 24-Sep-02 RMS Removed VT support, added Telnet console support | |
| Added CGI support (Brian Knittel) | |
| Added MacOS sleep (Peter Schorn) | |
| 14-Jul-02 RMS Added Windows priority control (Mark Pizzolato) | |
| 20-May-02 RMS Added Windows VT support (Fischer Franz) | |
| 01-Feb-02 RMS Added VAX fix (Robert Alan Byer) | |
| 19-Sep-01 RMS More MacOS changes | |
| 31-Aug-01 RMS Changed int64 to t_int64 for Windoze | |
| 20-Jul-01 RMS Added Macintosh support (Louis Chretien, Peter Schorn, Ben Supnik) | |
| 15-May-01 RMS Added logging support | |
| 05-Mar-01 RMS Added clock calibration support | |
| 08-Dec-00 BKR Added OS/2 support (Bruce Ray) | |
| 18-Aug-98 RMS Added BeOS support | |
| 13-Oct-97 RMS Added NetBSD terminal support | |
| 25-Jan-97 RMS Added POSIX terminal I/O support | |
| 02-Jan-97 RMS Fixed bug in sim_poll_kbd | |
| This module implements the following routines to support terminal I/O: | |
| sim_poll_kbd - poll for keyboard input | |
| sim_putchar - output character to console | |
| sim_putchar_s - output character to console, stall if congested | |
| sim_set_console - set console parameters | |
| sim_show_console - show console parameters | |
| sim_set_cons_buff - set console buffered | |
| sim_set_cons_unbuff -set console unbuffered | |
| sim_set_cons_log - set console log | |
| sim_set_cons_nolog - set console nolog | |
| sim_show_cons_buff - show console buffered | |
| sim_show_cons_log - show console log | |
| sim_tt_inpcvt - convert input character per mode | |
| sim_tt_outcvt - convert output character per mode | |
| sim_ttinit - called once to get initial terminal state | |
| sim_ttrun - called to put terminal into run state | |
| sim_ttcmd - called to return terminal to command state | |
| sim_ttclose - called once before the simulator exits | |
| sim_ttisatty - called to determine if running interactively | |
| sim_os_poll_kbd - poll for keyboard input | |
| sim_os_putchar - output character to console | |
| The first group is OS-independent; the second group is OS-dependent. | |
| The following routines are exposed but deprecated: | |
| sim_set_telnet - set console to Telnet port | |
| sim_set_notelnet - close console Telnet port | |
| sim_show_telnet - show console status | |
| */ | |
| #include "sim_defs.h" | |
| #include "sim_tmxr.h" | |
| #include "sim_timer.h" | |
| #include <ctype.h> | |
| /* Forward Declaraations of Platform specific routines */ | |
| t_stat sim_os_poll_kbd (void); | |
| t_bool sim_os_poll_kbd_ready (int ms_timeout); | |
| t_stat sim_os_putchar (int32 out); | |
| t_stat sim_os_ttinit (void); | |
| t_stat sim_os_ttrun (void); | |
| t_stat sim_os_ttcmd (void); | |
| t_stat sim_os_ttclose (void); | |
| t_bool sim_os_ttisatty (void); | |
| #define KMAP_WRU 0 | |
| #define KMAP_BRK 1 | |
| #define KMAP_DEL 2 | |
| #define KMAP_MASK 0377 | |
| #define KMAP_NZ 0400 | |
| int32 sim_int_char = 005; /* interrupt character */ | |
| int32 sim_brk_char = 000; /* break character */ | |
| int32 sim_tt_pchar = 0x00002780; | |
| #if defined (_WIN32) || defined (__OS2__) || (defined (__MWERKS__) && defined (macintosh)) | |
| int32 sim_del_char = '\b'; /* delete character */ | |
| #else | |
| int32 sim_del_char = 0177; | |
| #endif | |
| t_stat sim_con_poll_svc (UNIT *uptr); /* console connection poll routine */ | |
| t_stat sim_con_reset (DEVICE *dptr); /* console connection poll routine */ | |
| UNIT sim_con_unit = { UDATA (&sim_con_poll_svc, 0, 0) }; /* console connection unit */ | |
| /* debugging bitmaps */ | |
| #define DBG_TRC TMXR_DBG_TRC /* trace routine calls */ | |
| #define DBG_XMT TMXR_DBG_XMT /* display Transmitted Data */ | |
| #define DBG_RCV TMXR_DBG_RCV /* display Received Data */ | |
| #define DBG_ASY TMXR_DBG_ASY /* asynchronous thread activity */ | |
| DEBTAB sim_con_debug[] = { | |
| {"TRC", DBG_TRC}, | |
| {"XMT", DBG_XMT}, | |
| {"RCV", DBG_RCV}, | |
| {"ASY", DBG_ASY}, | |
| {0} | |
| }; | |
| MTAB sim_con_mod[] = { | |
| { 0 }, | |
| }; | |
| DEVICE sim_con_telnet = { | |
| "CON-TEL", &sim_con_unit, NULL, sim_con_mod, | |
| 1, 0, 0, 0, 0, 0, | |
| NULL, NULL, sim_con_reset, NULL, NULL, NULL, | |
| NULL, DEV_DEBUG, 0, sim_con_debug}; | |
| TMLN sim_con_ldsc = { 0 }; /* console line descr */ | |
| TMXR sim_con_tmxr = { 1, 0, 0, &sim_con_ldsc, NULL, &sim_con_telnet };/* console line mux */ | |
| /* Unit service for console connection polling */ | |
| t_stat sim_con_poll_svc (UNIT *uptr) | |
| { | |
| if ((sim_con_tmxr.master == 0) && /* not Telnet and not serial? */ | |
| (sim_con_ldsc.serport == 0)) | |
| return SCPE_OK; /* done */ | |
| if (tmxr_poll_conn (&sim_con_tmxr) >= 0) /* poll connect */ | |
| sim_con_ldsc.rcve = 1; /* rcv enabled */ | |
| sim_activate_after(uptr, 1000000); /* check again in 1 second */ | |
| tmxr_send_buffered_data (&sim_con_ldsc); /* try to flush any buffered data */ | |
| return SCPE_OK; | |
| } | |
| t_stat sim_con_reset (DEVICE *dptr) | |
| { | |
| return sim_con_poll_svc (&dptr->units[0]); /* establish polling as needed */ | |
| } | |
| extern volatile int32 stop_cpu; | |
| extern int32 sim_quiet; | |
| extern FILE *sim_log, *sim_deb; | |
| extern FILEREF *sim_log_ref, *sim_deb_ref; | |
| extern DEVICE *sim_devices[]; | |
| /* Set/show data structures */ | |
| static CTAB set_con_tab[] = { | |
| { "WRU", &sim_set_kmap, KMAP_WRU | KMAP_NZ }, | |
| { "BRK", &sim_set_kmap, KMAP_BRK }, | |
| { "DEL", &sim_set_kmap, KMAP_DEL |KMAP_NZ }, | |
| { "PCHAR", &sim_set_pchar, 0 }, | |
| { "TELNET", &sim_set_telnet, 0 }, | |
| { "NOTELNET", &sim_set_notelnet, 0 }, | |
| { "SERIAL", &sim_set_serial, 0 }, | |
| { "NOSERIAL", &sim_set_noserial, 0 }, | |
| { "LOG", &sim_set_logon, 0 }, | |
| { "NOLOG", &sim_set_logoff, 0 }, | |
| { "DEBUG", &sim_set_cons_debug, 1 }, | |
| { "NODEBUG", &sim_set_cons_debug, 0 }, | |
| { NULL, NULL, 0 } | |
| }; | |
| static SHTAB show_con_tab[] = { | |
| { "WRU", &sim_show_kmap, KMAP_WRU }, | |
| { "BRK", &sim_show_kmap, KMAP_BRK }, | |
| { "DEL", &sim_show_kmap, KMAP_DEL }, | |
| { "PCHAR", &sim_show_pchar, 0 }, | |
| { "LOG", &sim_show_cons_log, 0 }, | |
| { "TELNET", &sim_show_telnet, 0 }, | |
| { "DEBUG", &sim_show_cons_debug, 0 }, | |
| { "BUFFERED", &sim_show_cons_buff, 0 }, | |
| { NULL, NULL, 0 } | |
| }; | |
| static CTAB set_con_telnet_tab[] = { | |
| { "LOG", &sim_set_cons_log, 0 }, | |
| { "NOLOG", &sim_set_cons_nolog, 0 }, | |
| { "BUFFERED", &sim_set_cons_buff, 0 }, | |
| { "NOBUFFERED", &sim_set_cons_unbuff, 0 }, | |
| { "UNBUFFERED", &sim_set_cons_unbuff, 0 }, | |
| { NULL, NULL, 0 } | |
| }; | |
| static CTAB set_con_serial_tab[] = { | |
| { "LOG", &sim_set_cons_log, 0 }, | |
| { "NOLOG", &sim_set_cons_nolog, 0 }, | |
| { NULL, NULL, 0 } | |
| }; | |
| static int32 *cons_kmap[] = { | |
| &sim_int_char, | |
| &sim_brk_char, | |
| &sim_del_char | |
| }; | |
| /* Console I/O package. | |
| The console terminal can be attached to the controlling window | |
| or to a Telnet connection. If attached to a Telnet connection, | |
| the console is described by internal terminal multiplexor | |
| sim_con_tmxr and internal terminal line description sim_con_ldsc. | |
| */ | |
| /* SET CONSOLE command */ | |
| t_stat sim_set_console (int32 flag, char *cptr) | |
| { | |
| char *cvptr, gbuf[CBUFSIZE]; | |
| CTAB *ctptr; | |
| t_stat r; | |
| if ((cptr == NULL) || (*cptr == 0)) | |
| return SCPE_2FARG; | |
| while (*cptr != 0) { /* do all mods */ | |
| cptr = get_glyph_nc (cptr, gbuf, ','); /* get modifier */ | |
| if ((cvptr = strchr (gbuf, '='))) /* = value? */ | |
| *cvptr++ = 0; | |
| get_glyph (gbuf, gbuf, 0); /* modifier to UC */ | |
| if ((ctptr = find_ctab (set_con_tab, gbuf))) { /* match? */ | |
| r = ctptr->action (ctptr->arg, cvptr); /* do the rest */ | |
| if (r != SCPE_OK) | |
| return r; | |
| } | |
| else return SCPE_NOPARAM; | |
| } | |
| return SCPE_OK; | |
| } | |
| /* SHOW CONSOLE command */ | |
| t_stat sim_show_console (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr) | |
| { | |
| char gbuf[CBUFSIZE]; | |
| SHTAB *shptr; | |
| int32 i; | |
| if (*cptr == 0) { /* show all */ | |
| for (i = 0; show_con_tab[i].name; i++) | |
| show_con_tab[i].action (st, dptr, uptr, show_con_tab[i].arg, cptr); | |
| return SCPE_OK; | |
| } | |
| while (*cptr != 0) { | |
| cptr = get_glyph (cptr, gbuf, ','); /* get modifier */ | |
| if ((shptr = find_shtab (show_con_tab, gbuf))) | |
| shptr->action (st, dptr, uptr, shptr->arg, cptr); | |
| else return SCPE_NOPARAM; | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Set keyboard map */ | |
| t_stat sim_set_kmap (int32 flag, char *cptr) | |
| { | |
| DEVICE *dptr = sim_devices[0]; | |
| int32 val, rdx; | |
| t_stat r; | |
| if ((cptr == NULL) || (*cptr == 0)) | |
| return SCPE_2FARG; | |
| if (dptr->dradix == 16) rdx = 16; | |
| else rdx = 8; | |
| val = (int32) get_uint (cptr, rdx, 0177, &r); | |
| if ((r != SCPE_OK) || | |
| ((val == 0) && (flag & KMAP_NZ))) | |
| return SCPE_ARG; | |
| *(cons_kmap[flag & KMAP_MASK]) = val; | |
| return SCPE_OK; | |
| } | |
| /* Show keyboard map */ | |
| t_stat sim_show_kmap (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr) | |
| { | |
| if (sim_devices[0]->dradix == 16) | |
| fprintf (st, "%s = %X\n", show_con_tab[flag].name, *(cons_kmap[flag & KMAP_MASK])); | |
| else fprintf (st, "%s = %o\n", show_con_tab[flag].name, *(cons_kmap[flag & KMAP_MASK])); | |
| return SCPE_OK; | |
| } | |
| /* Set printable characters */ | |
| t_stat sim_set_pchar (int32 flag, char *cptr) | |
| { | |
| DEVICE *dptr = sim_devices[0]; | |
| uint32 val, rdx; | |
| t_stat r; | |
| if ((cptr == NULL) || (*cptr == 0)) | |
| return SCPE_2FARG; | |
| if (dptr->dradix == 16) rdx = 16; | |
| else rdx = 8; | |
| val = (uint32) get_uint (cptr, rdx, 0xFFFFFFFF, &r); | |
| if ((r != SCPE_OK) || | |
| ((val & 0x00002400) == 0)) | |
| return SCPE_ARG; | |
| sim_tt_pchar = val; | |
| return SCPE_OK; | |
| } | |
| /* Show printable characters */ | |
| t_stat sim_show_pchar (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr) | |
| { | |
| if (sim_devices[0]->dradix == 16) | |
| fprintf (st, "pchar mask = %X\n", sim_tt_pchar); | |
| else fprintf (st, "pchar mask = %o\n", sim_tt_pchar); | |
| return SCPE_OK; | |
| } | |
| /* Set log routine */ | |
| t_stat sim_set_logon (int32 flag, char *cptr) | |
| { | |
| char gbuf[CBUFSIZE]; | |
| t_stat r; | |
| if ((cptr == NULL) || (*cptr == 0)) /* need arg */ | |
| return SCPE_2FARG; | |
| cptr = get_glyph_nc (cptr, gbuf, 0); /* get file name */ | |
| if (*cptr != 0) /* now eol? */ | |
| return SCPE_2MARG; | |
| sim_set_logoff (0, NULL); /* close cur log */ | |
| r = sim_open_logfile (gbuf, FALSE, &sim_log, &sim_log_ref); /* open log */ | |
| if (r != SCPE_OK) /* error? */ | |
| return r; | |
| if (!sim_quiet) | |
| printf ("Logging to file \"%s\"\n", | |
| sim_logfile_name (sim_log, sim_log_ref)); | |
| fprintf (sim_log, "Logging to file \"%s\"\n", | |
| sim_logfile_name (sim_log, sim_log_ref)); /* start of log */ | |
| return SCPE_OK; | |
| } | |
| /* Set nolog routine */ | |
| t_stat sim_set_logoff (int32 flag, char *cptr) | |
| { | |
| if (cptr && (*cptr != 0)) /* now eol? */ | |
| return SCPE_2MARG; | |
| if (sim_log == NULL) /* no log? */ | |
| return SCPE_OK; | |
| if (!sim_quiet) | |
| printf ("Log file closed\n"); | |
| fprintf (sim_log, "Log file closed\n"); | |
| sim_close_logfile (&sim_log_ref); /* close log */ | |
| sim_log = NULL; | |
| return SCPE_OK; | |
| } | |
| /* Show log status */ | |
| t_stat sim_show_log (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr) | |
| { | |
| if (cptr && (*cptr != 0)) | |
| return SCPE_2MARG; | |
| if (sim_log) | |
| fprintf (st, "Logging enabled to \"%s\"\n", | |
| sim_logfile_name (sim_log, sim_log_ref)); | |
| else fprintf (st, "Logging disabled\n"); | |
| return SCPE_OK; | |
| } | |
| /* Set debug routine */ | |
| t_stat sim_set_debon (int32 flag, char *cptr) | |
| { | |
| char gbuf[CBUFSIZE]; | |
| t_stat r; | |
| if ((cptr == NULL) || (*cptr == 0)) /* need arg */ | |
| return SCPE_2FARG; | |
| cptr = get_glyph_nc (cptr, gbuf, 0); /* get file name */ | |
| if (*cptr != 0) /* now eol? */ | |
| return SCPE_2MARG; | |
| r = sim_open_logfile (gbuf, FALSE, &sim_deb, &sim_deb_ref); | |
| if (r != SCPE_OK) | |
| return r; | |
| if (!sim_quiet) | |
| printf ("Debug output to \"%s\"\n", | |
| sim_logfile_name (sim_deb, sim_deb_ref)); | |
| if (sim_log) | |
| fprintf (sim_log, "Debug output to \"%s\"\n", | |
| sim_logfile_name (sim_deb, sim_deb_ref)); | |
| return SCPE_OK; | |
| } | |
| /* Set nodebug routine */ | |
| t_stat sim_set_deboff (int32 flag, char *cptr) | |
| { | |
| if (cptr && (*cptr != 0)) /* now eol? */ | |
| return SCPE_2MARG; | |
| if (sim_deb == NULL) /* no log? */ | |
| return SCPE_OK; | |
| sim_close_logfile (&sim_deb_ref); | |
| sim_deb = NULL; | |
| if (!sim_quiet) | |
| printf ("Debug output disabled\n"); | |
| if (sim_log) | |
| fprintf (sim_log, "Debug output disabled\n"); | |
| return SCPE_OK; | |
| } | |
| /* Show debug routine */ | |
| t_stat sim_show_debug (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr) | |
| { | |
| if (cptr && (*cptr != 0)) | |
| return SCPE_2MARG; | |
| if (sim_deb) | |
| fprintf (st, "Debug output enabled to \"%s\"\n", | |
| sim_logfile_name (sim_deb, sim_deb_ref)); | |
| else fprintf (st, "Debug output disabled\n"); | |
| return SCPE_OK; | |
| } | |
| /* SET CONSOLE command */ | |
| /* Set console to Telnet port (and parameters) */ | |
| t_stat sim_set_telnet (int32 flag, char *cptr) | |
| { | |
| char *cvptr, gbuf[CBUFSIZE]; | |
| CTAB *ctptr; | |
| t_stat r; | |
| if ((cptr == NULL) || (*cptr == 0)) | |
| return SCPE_2FARG; | |
| while (*cptr != 0) { /* do all mods */ | |
| cptr = get_glyph_nc (cptr, gbuf, ','); /* get modifier */ | |
| if ((cvptr = strchr (gbuf, '='))) /* = value? */ | |
| *cvptr++ = 0; | |
| get_glyph (gbuf, gbuf, 0); /* modifier to UC */ | |
| if ((ctptr = find_ctab (set_con_telnet_tab, gbuf))) { /* match? */ | |
| r = ctptr->action (ctptr->arg, cvptr); /* do the rest */ | |
| if (r != SCPE_OK) | |
| return r; | |
| } | |
| else { | |
| r = sim_parse_addr (gbuf, NULL, 0, NULL, NULL, 0, NULL, NULL); | |
| if (r == SCPE_OK) { | |
| if (sim_con_tmxr.master) /* already open? */ | |
| sim_set_notelnet (0, NULL); /* close first */ | |
| r = tmxr_attach (&sim_con_tmxr, &sim_con_unit, gbuf);/* open master socket */ | |
| if (r == SCPE_OK) | |
| sim_activate_after(&sim_con_unit, 1000000); /* check for connection in 1 second */ | |
| return r; | |
| } | |
| return SCPE_NOPARAM; | |
| } | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Close console Telnet port */ | |
| t_stat sim_set_notelnet (int32 flag, char *cptr) | |
| { | |
| if (cptr && (*cptr != 0)) /* too many arguments? */ | |
| return SCPE_2MARG; | |
| if (sim_con_tmxr.master == 0) /* ignore if already closed */ | |
| return SCPE_OK; | |
| return tmxr_close_master (&sim_con_tmxr); /* close master socket */ | |
| } | |
| /* Show console Telnet status */ | |
| t_stat sim_show_telnet (FILE *st, DEVICE *dunused, UNIT *uunused, int32 flag, char *cptr) | |
| { | |
| if (cptr && (*cptr != 0)) | |
| return SCPE_2MARG; | |
| if ((sim_con_tmxr.master == 0) && | |
| (sim_con_ldsc.serport == 0)) | |
| fprintf (st, "Connected to console window\n"); | |
| else { | |
| if (sim_con_ldsc.serport) { | |
| fprintf (st, "Connected to "); | |
| tmxr_fconns (st, &sim_con_ldsc, -1); | |
| } | |
| else | |
| if (sim_con_ldsc.sock == 0) | |
| fprintf (st, "Listening on port %s\n", sim_con_tmxr.port); | |
| else { | |
| fprintf (st, "Listening on port %s, connection from %s\n", | |
| sim_con_tmxr.port, sim_con_ldsc.ipad); | |
| tmxr_fconns (st, &sim_con_ldsc, -1); | |
| } | |
| tmxr_fstats (st, &sim_con_ldsc, -1); | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Set console to Buffering */ | |
| t_stat sim_set_cons_buff (int32 flg, char *cptr) | |
| { | |
| char cmdbuf[CBUFSIZE]; | |
| sprintf(cmdbuf, "BUFFERED%c%s", cptr ? '=' : '\0', cptr ? cptr : ""); | |
| return tmxr_open_master (&sim_con_tmxr, cmdbuf); /* open master socket */ | |
| } | |
| /* Set console to NoBuffering */ | |
| t_stat sim_set_cons_unbuff (int32 flg, char *cptr) | |
| { | |
| char cmdbuf[CBUFSIZE]; | |
| sprintf(cmdbuf, "UNBUFFERED%c%s", cptr ? '=' : '\0', cptr ? cptr : ""); | |
| return tmxr_open_master (&sim_con_tmxr, cmdbuf); /* open master socket */ | |
| } | |
| /* Set console to Logging */ | |
| t_stat sim_set_cons_log (int32 flg, char *cptr) | |
| { | |
| char cmdbuf[CBUFSIZE]; | |
| sprintf(cmdbuf, "LOG%c%s", cptr ? '=' : '\0', cptr ? cptr : ""); | |
| return tmxr_open_master (&sim_con_tmxr, cmdbuf); /* open master socket */ | |
| } | |
| /* Set console to NoLogging */ | |
| t_stat sim_set_cons_nolog (int32 flg, char *cptr) | |
| { | |
| char cmdbuf[CBUFSIZE]; | |
| sprintf(cmdbuf, "NOLOG%c%s", cptr ? '=' : '\0', cptr ? cptr : ""); | |
| return tmxr_open_master (&sim_con_tmxr, cmdbuf); /* open master socket */ | |
| } | |
| t_stat sim_show_cons_log (FILE *st, DEVICE *dunused, UNIT *uunused, int32 flag, char *cptr) | |
| { | |
| if (cptr && (*cptr != 0)) | |
| return SCPE_2MARG; | |
| if (sim_con_tmxr.ldsc->txlog) | |
| fprintf (st, "Log File being written to %s\n", sim_con_tmxr.ldsc->txlogname); | |
| else | |
| fprintf (st, "No Logging\n"); | |
| return SCPE_OK; | |
| } | |
| t_stat sim_show_cons_buff (FILE *st, DEVICE *dunused, UNIT *uunused, int32 flag, char *cptr) | |
| { | |
| if (cptr && (*cptr != 0)) | |
| return SCPE_2MARG; | |
| if (!sim_con_tmxr.buffered) | |
| fprintf (st, "Unbuffered\n"); | |
| else | |
| fprintf (st, "Buffer Size = %d\n", sim_con_tmxr.buffered); | |
| return SCPE_OK; | |
| } | |
| /* Set console Debug Mode */ | |
| t_stat sim_set_cons_debug (int32 flg, char *cptr) | |
| { | |
| return set_dev_debug (&sim_con_telnet, &sim_con_unit, flg, cptr); | |
| } | |
| t_stat sim_show_cons_debug (FILE *st, DEVICE *dunused, UNIT *uunused, int32 flag, char *cptr) | |
| { | |
| if (cptr && (*cptr != 0)) | |
| return SCPE_2MARG; | |
| return show_dev_debug (st, &sim_con_telnet, &sim_con_unit, flag, cptr); | |
| } | |
| /* Set console to Serial port (and parameters) */ | |
| t_stat sim_set_serial (int32 flag, char *cptr) | |
| { | |
| char *cvptr, gbuf[CBUFSIZE], ubuf[CBUFSIZE]; | |
| CTAB *ctptr; | |
| t_stat r; | |
| if ((cptr == NULL) || (*cptr == 0)) | |
| return SCPE_2FARG; | |
| while (*cptr != 0) { /* do all mods */ | |
| cptr = get_glyph_nc (cptr, gbuf, ','); /* get modifier */ | |
| if ((cvptr = strchr (gbuf, '='))) /* = value? */ | |
| *cvptr++ = 0; | |
| get_glyph (gbuf, ubuf, 0); /* modifier to UC */ | |
| if ((ctptr = find_ctab (set_con_serial_tab, ubuf))) { /* match? */ | |
| r = ctptr->action (ctptr->arg, cvptr); /* do the rest */ | |
| if (r != SCPE_OK) | |
| return r; | |
| } | |
| else { | |
| SERHANDLE serport = sim_open_serial (gbuf, NULL, &r); | |
| if (serport != INVALID_HANDLE) { | |
| sim_close_serial (serport); | |
| if (r == SCPE_OK) { | |
| char cbuf[CBUFSIZE]; | |
| if ((sim_con_tmxr.master) || /* already open? */ | |
| (sim_con_ldsc.serport)) | |
| sim_set_noserial (0, NULL); /* close first */ | |
| sprintf(cbuf, "Connect=%s", gbuf); | |
| r = tmxr_attach (&sim_con_tmxr, &sim_con_unit, cbuf);/* open master socket */ | |
| sim_con_ldsc.rcve = 1; /* rcv enabled */ | |
| if (r == SCPE_OK) | |
| sim_activate_after(&sim_con_unit, 1000000); /* check for connection in 1 second */ | |
| return r; | |
| } | |
| } | |
| return SCPE_ARG; | |
| } | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Close console Serial port */ | |
| t_stat sim_set_noserial (int32 flag, char *cptr) | |
| { | |
| if (cptr && (*cptr != 0)) /* too many arguments? */ | |
| return SCPE_2MARG; | |
| if (sim_con_ldsc.serport == 0) /* ignore if already closed */ | |
| return SCPE_OK; | |
| return tmxr_close_master (&sim_con_tmxr); /* close master socket */ | |
| } | |
| /* Log File Open/Close/Show Support */ | |
| /* Open log file */ | |
| t_stat sim_open_logfile (char *filename, t_bool binary, FILE **pf, FILEREF **pref) | |
| { | |
| char *tptr, gbuf[CBUFSIZE]; | |
| if ((filename == NULL) || (*filename == 0)) /* too few arguments? */ | |
| return SCPE_2FARG; | |
| tptr = get_glyph (filename, gbuf, 0); | |
| if (*tptr != 0) /* now eol? */ | |
| return SCPE_2MARG; | |
| sim_close_logfile (pref); | |
| *pf = NULL; | |
| if (strcmp (gbuf, "LOG") == 0) { /* output to log? */ | |
| if (sim_log == NULL) /* any log? */ | |
| return SCPE_ARG; | |
| *pf = sim_log; | |
| *pref = sim_log_ref; | |
| if (*pref) | |
| ++(*pref)->refcount; | |
| } | |
| else if (strcmp (gbuf, "DEBUG") == 0) { /* output to debug? */ | |
| if (sim_deb == NULL) /* any debug? */ | |
| return SCPE_ARG; | |
| *pf = sim_deb; | |
| *pref = sim_deb_ref; | |
| if (*pref) | |
| ++(*pref)->refcount; | |
| } | |
| else if (strcmp (gbuf, "STDOUT") == 0) { /* output to stdout? */ | |
| *pf = stdout; | |
| *pref = NULL; | |
| } | |
| else if (strcmp (gbuf, "STDERR") == 0) { /* output to stderr? */ | |
| *pf = stderr; | |
| *pref = NULL; | |
| } | |
| else { | |
| *pref = calloc (1, sizeof(**pref)); | |
| if (!*pref) | |
| return SCPE_MEM; | |
| get_glyph_nc (filename, gbuf, 0); /* reparse */ | |
| strncpy ((*pref)->name, gbuf, sizeof((*pref)->name)-1); | |
| *pf = sim_fopen (gbuf, (binary ? "ab" : "a")); /* open file */ | |
| if (*pf == NULL) { /* error? */ | |
| free (*pref); | |
| *pref = NULL; | |
| return SCPE_OPENERR; | |
| } | |
| (*pref)->file = *pf; | |
| (*pref)->refcount = 1; /* need close */ | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Close log file */ | |
| t_stat sim_close_logfile (FILEREF **pref) | |
| { | |
| if (NULL == *pref) | |
| return SCPE_OK; | |
| (*pref)->refcount = (*pref)->refcount - 1; | |
| if ((*pref)->refcount > 0) | |
| return SCPE_OK; | |
| fclose ((*pref)->file); | |
| free (*pref); | |
| *pref = NULL; | |
| return SCPE_OK; | |
| } | |
| /* Show logfile support routine */ | |
| const char *sim_logfile_name (FILE *st, FILEREF *ref) | |
| { | |
| if (!st) | |
| return ""; | |
| if (st == stdout) | |
| return "STDOUT"; | |
| if (st == stderr) | |
| return "STDERR"; | |
| if (!ref) | |
| return ""; | |
| return ref->name; | |
| } | |
| /* Check connection before executing */ | |
| t_stat sim_check_console (int32 sec) | |
| { | |
| int32 c, i; | |
| if (sim_con_ldsc.serport) | |
| if (tmxr_poll_conn (&sim_con_tmxr) >= 0) | |
| sim_con_ldsc.rcve = 1; /* rcv enabled */ | |
| if ((sim_con_tmxr.master == 0) || /* serial console or not Telnet? done */ | |
| (sim_con_ldsc.serport)) | |
| return SCPE_OK; | |
| if (sim_con_ldsc.conn || sim_con_ldsc.txbfd) { /* connected or buffered ? */ | |
| tmxr_poll_rx (&sim_con_tmxr); /* poll (check disconn) */ | |
| if (sim_con_ldsc.conn || sim_con_ldsc.txbfd) { /* still connected? */ | |
| if (!sim_con_ldsc.conn) { | |
| printf ("Running with Buffered Console\r\n"); /* print transition */ | |
| fflush (stdout); | |
| if (sim_log) { /* log file? */ | |
| fprintf (sim_log, "Running with Buffered Console\n"); | |
| fflush (sim_log); | |
| } | |
| } | |
| return SCPE_OK; | |
| } | |
| } | |
| for (i = 0; i < sec; i++) { /* loop */ | |
| if (tmxr_poll_conn (&sim_con_tmxr) >= 0) { /* poll connect */ | |
| sim_con_ldsc.rcve = 1; /* rcv enabled */ | |
| if (i) { /* if delayed */ | |
| printf ("Running\r\n"); /* print transition */ | |
| fflush (stdout); | |
| if (sim_log) { /* log file? */ | |
| fprintf (sim_log, "Running\n"); | |
| fflush (sim_log); | |
| } | |
| } | |
| return SCPE_OK; /* ready to proceed */ | |
| } | |
| c = sim_os_poll_kbd (); /* check for stop char */ | |
| if ((c == SCPE_STOP) || stop_cpu) | |
| return SCPE_STOP; | |
| if ((i % 10) == 0) { /* Status every 10 sec */ | |
| printf ("Waiting for console Telnet connection\r\n"); | |
| fflush (stdout); | |
| if (sim_log) { /* log file? */ | |
| fprintf (sim_log, "Waiting for console Telnet connection\n"); | |
| fflush (sim_log); | |
| } | |
| } | |
| sim_os_sleep (1); /* wait 1 second */ | |
| } | |
| return SCPE_TTMO; /* timed out */ | |
| } | |
| /* Poll for character */ | |
| t_stat sim_poll_kbd (void) | |
| { | |
| int32 c; | |
| c = sim_os_poll_kbd (); /* get character */ | |
| if ((c == SCPE_STOP) || /* ^E or not Telnet? */ | |
| ((sim_con_tmxr.master == 0) && /* and not serial? */ | |
| (sim_con_ldsc.serport == 0))) | |
| return c; /* in-window */ | |
| if (!sim_con_ldsc.conn) { /* no telnet or serial connection? */ | |
| if (!sim_con_ldsc.txbfd) /* unbuffered? */ | |
| return SCPE_LOST; /* connection lost */ | |
| if (tmxr_poll_conn (&sim_con_tmxr) >= 0) /* poll connect */ | |
| sim_con_ldsc.rcve = 1; /* rcv enabled */ | |
| else /* fall through to poll reception */ | |
| return SCPE_OK; /* unconnected and buffered - nothing to receive */ | |
| } | |
| tmxr_poll_rx (&sim_con_tmxr); /* poll for input */ | |
| if ((c = tmxr_getc_ln (&sim_con_ldsc))) /* any char? */ | |
| return (c & (SCPE_BREAK | 0377)) | SCPE_KFLAG; | |
| return SCPE_OK; | |
| } | |
| /* Output character */ | |
| t_stat sim_putchar (int32 c) | |
| { | |
| if ((sim_con_tmxr.master == 0) && /* not Telnet? */ | |
| (sim_con_ldsc.serport == 0)) { /* and not serial port */ | |
| if (sim_log) /* log file? */ | |
| fputc (c, sim_log); | |
| return sim_os_putchar (c); /* in-window version */ | |
| } | |
| if (sim_log && !sim_con_ldsc.txlog) /* log file, but no line log? */ | |
| fputc (c, sim_log); | |
| if (!sim_con_ldsc.conn) { /* no Telnet or serial connection? */ | |
| if (!sim_con_ldsc.txbfd) /* unbuffered? */ | |
| return SCPE_LOST; /* connection lost */ | |
| if (tmxr_poll_conn (&sim_con_tmxr) >= 0) /* poll connect */ | |
| sim_con_ldsc.rcve = 1; /* rcv enabled */ | |
| } | |
| tmxr_putc_ln (&sim_con_ldsc, c); /* output char */ | |
| tmxr_poll_tx (&sim_con_tmxr); /* poll xmt */ | |
| return SCPE_OK; | |
| } | |
| t_stat sim_putchar_s (int32 c) | |
| { | |
| t_stat r; | |
| if ((sim_con_tmxr.master == 0) && /* not Telnet? */ | |
| (sim_con_ldsc.serport == 0)) { /* and not serial port */ | |
| if (sim_log) /* log file? */ | |
| fputc (c, sim_log); | |
| return sim_os_putchar (c); /* in-window version */ | |
| } | |
| if (sim_log && !sim_con_ldsc.txlog) /* log file, but no line log? */ | |
| fputc (c, sim_log); | |
| if (!sim_con_ldsc.conn) { /* no Telnet or serial connection? */ | |
| if (!sim_con_ldsc.txbfd) /* non-buffered Telnet connection? */ | |
| return SCPE_LOST; /* lost */ | |
| if (tmxr_poll_conn (&sim_con_tmxr) >= 0) /* poll connect */ | |
| sim_con_ldsc.rcve = 1; /* rcv enabled */ | |
| } | |
| if (sim_con_ldsc.xmte == 0) /* xmt disabled? */ | |
| r = SCPE_STALL; | |
| else r = tmxr_putc_ln (&sim_con_ldsc, c); /* no, Telnet output */ | |
| tmxr_poll_tx (&sim_con_tmxr); /* poll xmt */ | |
| return r; /* return status */ | |
| } | |
| /* Input character processing */ | |
| int32 sim_tt_inpcvt (int32 c, uint32 mode) | |
| { | |
| uint32 md = mode & TTUF_M_MODE; | |
| if (md != TTUF_MODE_8B) { | |
| c = c & 0177; | |
| if (md == TTUF_MODE_UC) { | |
| if (islower (c)) | |
| c = toupper (c); | |
| if (mode & TTUF_KSR) | |
| c = c | 0200; | |
| } | |
| } | |
| else c = c & 0377; | |
| return c; | |
| } | |
| /* Output character processing */ | |
| int32 sim_tt_outcvt (int32 c, uint32 mode) | |
| { | |
| uint32 md = mode & TTUF_M_MODE; | |
| if (md != TTUF_MODE_8B) { | |
| c = c & 0177; | |
| if (md == TTUF_MODE_UC) { | |
| if (islower (c)) | |
| c = toupper (c); | |
| if ((mode & TTUF_KSR) && (c >= 0140)) | |
| return -1; | |
| } | |
| if (((md == TTUF_MODE_UC) || (md == TTUF_MODE_7P)) && | |
| ((c == 0177) || | |
| ((c < 040) && !((sim_tt_pchar >> c) & 1)))) | |
| return -1; | |
| } | |
| else c = c & 0377; | |
| return c; | |
| } | |
| t_stat sim_ttinit (void) | |
| { | |
| sim_register_internal_device (&sim_con_telnet); | |
| tmxr_startup (); | |
| return sim_os_ttinit (); | |
| } | |
| t_stat sim_ttrun (void) | |
| { | |
| if (!sim_con_tmxr.ldsc->uptr) /* If simulator didn't declare its input polling unit */ | |
| sim_con_unit.flags &= ~UNIT_TM_POLL; /* we can't poll asynchronously */ | |
| #if defined(SIM_ASYNCH_IO) && defined(SIM_ASYNCH_MUX) | |
| pthread_mutex_lock (&sim_tmxr_poll_lock); | |
| if (sim_asynch_enabled) { | |
| pthread_attr_t attr; | |
| pthread_cond_init (&sim_console_startup_cond, NULL); | |
| pthread_attr_init (&attr); | |
| pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); | |
| pthread_create (&sim_console_poll_thread, &attr, _console_poll, NULL); | |
| pthread_attr_destroy( &attr); | |
| pthread_cond_wait (&sim_console_startup_cond, &sim_tmxr_poll_lock); /* Wait for thread to stabilize */ | |
| pthread_cond_destroy (&sim_console_startup_cond); | |
| sim_console_poll_running = TRUE; | |
| } | |
| pthread_mutex_unlock (&sim_tmxr_poll_lock); | |
| #endif | |
| tmxr_start_poll (); | |
| return sim_os_ttrun (); | |
| } | |
| t_stat sim_ttcmd (void) | |
| { | |
| #if defined(SIM_ASYNCH_IO) && defined(SIM_ASYNCH_MUX) | |
| pthread_mutex_lock (&sim_tmxr_poll_lock); | |
| if (sim_console_poll_running) { | |
| pthread_cond_signal (&sim_tmxr_poll_cond); | |
| pthread_mutex_unlock (&sim_tmxr_poll_lock); | |
| pthread_join (sim_console_poll_thread, NULL); | |
| sim_console_poll_running = FALSE; | |
| } | |
| else | |
| pthread_mutex_unlock (&sim_tmxr_poll_lock); | |
| #endif | |
| tmxr_stop_poll (); | |
| return sim_os_ttcmd (); | |
| } | |
| t_stat sim_ttclose (void) | |
| { | |
| tmxr_shutdown (); | |
| return sim_os_ttclose (); | |
| } | |
| t_bool sim_ttisatty (void) | |
| { | |
| return sim_os_ttisatty (); | |
| } | |
| /* Platform specific routine definitions */ | |
| /* VMS routines, from Ben Thomas, with fixes from Robert Alan Byer */ | |
| #if defined (VMS) | |
| #if defined(__VAX) | |
| #define sys$assign SYS$ASSIGN | |
| #define sys$qiow SYS$QIOW | |
| #define sys$dassgn SYS$DASSGN | |
| #endif | |
| #include <descrip.h> | |
| #include <ttdef.h> | |
| #include <tt2def.h> | |
| #include <iodef.h> | |
| #include <ssdef.h> | |
| #include <starlet.h> | |
| #include <unistd.h> | |
| #define EFN 0 | |
| uint32 tty_chan = 0; | |
| int buffered_character = 0; | |
| typedef struct { | |
| unsigned short sense_count; | |
| unsigned char sense_first_char; | |
| unsigned char sense_reserved; | |
| unsigned int stat; | |
| unsigned int stat2; } SENSE_BUF; | |
| typedef struct { | |
| unsigned short status; | |
| unsigned short count; | |
| unsigned int dev_status; } IOSB; | |
| SENSE_BUF cmd_mode = { 0 }; | |
| SENSE_BUF run_mode = { 0 }; | |
| t_stat sim_os_ttinit (void) | |
| { | |
| unsigned int status; | |
| IOSB iosb; | |
| $DESCRIPTOR (terminal_device, "tt"); | |
| status = sys$assign (&terminal_device, &tty_chan, 0, 0); | |
| if (status != SS$_NORMAL) | |
| return SCPE_TTIERR; | |
| status = sys$qiow (EFN, tty_chan, IO$_SENSEMODE, &iosb, 0, 0, | |
| &cmd_mode, sizeof (cmd_mode), 0, 0, 0, 0); | |
| if ((status != SS$_NORMAL) || (iosb.status != SS$_NORMAL)) | |
| return SCPE_TTIERR; | |
| run_mode = cmd_mode; | |
| run_mode.stat = cmd_mode.stat | TT$M_NOECHO & ~(TT$M_HOSTSYNC | TT$M_TTSYNC); | |
| run_mode.stat2 = cmd_mode.stat2 | TT2$M_PASTHRU; | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttrun (void) | |
| { | |
| unsigned int status; | |
| IOSB iosb; | |
| status = sys$qiow (EFN, tty_chan, IO$_SETMODE, &iosb, 0, 0, | |
| &run_mode, sizeof (run_mode), 0, 0, 0, 0); | |
| if ((status != SS$_NORMAL) || (iosb.status != SS$_NORMAL)) | |
| return SCPE_TTIERR; | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttcmd (void) | |
| { | |
| unsigned int status; | |
| IOSB iosb; | |
| status = sys$qiow (EFN, tty_chan, IO$_SETMODE, &iosb, 0, 0, | |
| &cmd_mode, sizeof (cmd_mode), 0, 0, 0, 0); | |
| if ((status != SS$_NORMAL) || (iosb.status != SS$_NORMAL)) | |
| return SCPE_TTIERR; | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttclose (void) | |
| { | |
| sim_ttcmd (); | |
| sys$dassgn (tty_chan); | |
| return SCPE_OK; | |
| } | |
| t_bool sim_os_ttisatty (void) | |
| { | |
| return isatty (fileno (stdin)); | |
| } | |
| t_stat sim_os_poll_kbd_data (void) | |
| { | |
| unsigned int status, term[2]; | |
| unsigned char buf[4]; | |
| IOSB iosb; | |
| SENSE_BUF sense; | |
| term[0] = 0; term[1] = 0; | |
| status = sys$qiow (EFN, tty_chan, IO$_SENSEMODE | IO$M_TYPEAHDCNT, &iosb, | |
| 0, 0, &sense, 8, 0, term, 0, 0); | |
| if ((status != SS$_NORMAL) || (iosb.status != SS$_NORMAL)) | |
| return SCPE_TTIERR; | |
| if (sense.sense_count == 0) return SCPE_OK; | |
| term[0] = 0; term[1] = 0; | |
| status = sys$qiow (EFN, tty_chan, | |
| IO$_READLBLK | IO$M_NOECHO | IO$M_NOFILTR | IO$M_TIMED | IO$M_TRMNOECHO, | |
| &iosb, 0, 0, buf, 1, 0, term, 0, 0); | |
| if ((status != SS$_NORMAL) || (iosb.status != SS$_NORMAL)) | |
| return SCPE_OK; | |
| if (buf[0] == sim_int_char) return SCPE_STOP; | |
| if (sim_brk_char && (buf[0] == sim_brk_char)) | |
| return SCPE_BREAK; | |
| return (buf[0] | SCPE_KFLAG); | |
| } | |
| t_stat sim_os_poll_kbd (void) | |
| { | |
| t_stat response; | |
| if (response = buffered_character) { | |
| buffered_character = 0; | |
| return response; | |
| } | |
| return sim_os_poll_kbd_data (); | |
| } | |
| t_bool sim_os_poll_kbd_ready (int ms_timeout) | |
| { | |
| unsigned int status, term[2]; | |
| unsigned char buf[4]; | |
| IOSB iosb; | |
| term[0] = 0; term[1] = 0; | |
| status = sys$qiow (EFN, tty_chan, | |
| IO$_READLBLK | IO$M_NOECHO | IO$M_NOFILTR | IO$M_TIMED | IO$M_TRMNOECHO, | |
| &iosb, 0, 0, buf, 1, (ms_timeout+999)/1000, term, 0, 0); | |
| if ((status != SS$_NORMAL) || (iosb.status != SS$_NORMAL)) | |
| return FALSE; | |
| if (buf[0] == sim_int_char) | |
| buffered_character = SCPE_STOP; | |
| else | |
| if (sim_brk_char && (buf[0] == sim_brk_char)) | |
| buffered_character = SCPE_BREAK; | |
| else | |
| buffered_character = (buf[0] | SCPE_KFLAG); | |
| return TRUE; | |
| } | |
| t_stat sim_os_putchar (int32 out) | |
| { | |
| unsigned int status; | |
| char c; | |
| IOSB iosb; | |
| c = out; | |
| status = sys$qiow (EFN, tty_chan, IO$_WRITELBLK | IO$M_NOFORMAT, | |
| &iosb, 0, 0, &c, 1, 0, 0, 0, 0); | |
| if ((status != SS$_NORMAL) || (iosb.status != SS$_NORMAL)) | |
| return SCPE_TTOERR; | |
| return SCPE_OK; | |
| } | |
| /* Win32 routines */ | |
| #elif defined (_WIN32) | |
| #include <fcntl.h> | |
| #include <io.h> | |
| #include <windows.h> | |
| #define RAW_MODE 0 | |
| static HANDLE std_input; | |
| static HANDLE std_output; | |
| static DWORD saved_mode; | |
| /* Note: This routine catches all the potential events which some aspect | |
| of the windows system can generate. The CTRL_C_EVENT won't be | |
| generated by a user typing in a console session since that | |
| session is in RAW mode. In general, Ctrl-C on a simulator's | |
| console terminal is a useful character to be passed to the | |
| simulator. This code does nothing to disable or affect that. */ | |
| static BOOL WINAPI | |
| ControlHandler(DWORD dwCtrlType) | |
| { | |
| DWORD Mode; | |
| extern void int_handler (int sig); | |
| switch (dwCtrlType) | |
| { | |
| case CTRL_BREAK_EVENT: // Use CTRL-Break or CTRL-C to simulate | |
| case CTRL_C_EVENT: // SERVICE_CONTROL_STOP in debug mode | |
| int_handler(0); | |
| return TRUE; | |
| case CTRL_CLOSE_EVENT: // Window is Closing | |
| case CTRL_LOGOFF_EVENT: // User is logging off | |
| if (!GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &Mode)) | |
| return TRUE; // Not our User, so ignore | |
| case CTRL_SHUTDOWN_EVENT: // System is shutting down | |
| int_handler(0); | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| t_stat sim_os_ttinit (void) | |
| { | |
| SetConsoleCtrlHandler( ControlHandler, TRUE ); | |
| std_input = GetStdHandle (STD_INPUT_HANDLE); | |
| std_output = GetStdHandle (STD_OUTPUT_HANDLE); | |
| if ((std_input) && /* Not Background process? */ | |
| (std_input != INVALID_HANDLE_VALUE)) | |
| GetConsoleMode (std_input, &saved_mode); /* Save Mode */ | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttrun (void) | |
| { | |
| if ((std_input) && /* If Not Background process? */ | |
| (std_input != INVALID_HANDLE_VALUE) && | |
| (!GetConsoleMode(std_input, &saved_mode) || /* Set mode to RAW */ | |
| !SetConsoleMode(std_input, RAW_MODE))) | |
| return SCPE_TTYERR; | |
| if (sim_log) { | |
| fflush (sim_log); | |
| _setmode (_fileno (sim_log), _O_BINARY); | |
| } | |
| SetThreadPriority (GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttcmd (void) | |
| { | |
| if (sim_log) { | |
| fflush (sim_log); | |
| _setmode (_fileno (sim_log), _O_TEXT); | |
| } | |
| SetThreadPriority (GetCurrentThread(), THREAD_PRIORITY_NORMAL); | |
| if ((std_input) && /* If Not Background process? */ | |
| (std_input != INVALID_HANDLE_VALUE) && | |
| (!SetConsoleMode(std_input, saved_mode))) /* Restore Normal mode */ | |
| return SCPE_TTYERR; | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttclose (void) | |
| { | |
| return SCPE_OK; | |
| } | |
| t_bool sim_os_ttisatty (void) | |
| { | |
| DWORD Mode; | |
| return (std_input) && (std_input != INVALID_HANDLE_VALUE) && GetConsoleMode (std_input, &Mode); | |
| } | |
| t_stat sim_os_poll_kbd (void) | |
| { | |
| int c = -1; | |
| DWORD nkbevents, nkbevent; | |
| INPUT_RECORD rec; | |
| sim_debug (DBG_TRC, &sim_con_telnet, "sim_os_poll_kbd()\n"); | |
| if ((std_input == NULL) || /* No keyboard for */ | |
| (std_input == INVALID_HANDLE_VALUE)) /* background processes */ | |
| return SCPE_OK; | |
| if (!GetNumberOfConsoleInputEvents(std_input, &nkbevents)) | |
| return SCPE_TTYERR; | |
| while (c == -1) { | |
| if (0 == nkbevents) | |
| return SCPE_OK; | |
| if (!ReadConsoleInput(std_input, &rec, 1, &nkbevent)) | |
| return SCPE_TTYERR; | |
| if (0 == nkbevent) | |
| return SCPE_OK; | |
| --nkbevents; | |
| if (rec.EventType == KEY_EVENT) { | |
| if (rec.Event.KeyEvent.bKeyDown) { | |
| if (0 == rec.Event.KeyEvent.uChar.UnicodeChar) { /* Special Character/Keys? */ | |
| if (rec.Event.KeyEvent.wVirtualKeyCode == VK_PAUSE) /* Pause/Break Key */ | |
| c = sim_brk_char | SCPE_BREAK; | |
| else | |
| if (rec.Event.KeyEvent.wVirtualKeyCode == '2') /* ^@ */ | |
| c = 0; /* return NUL */ | |
| } else | |
| c = rec.Event.KeyEvent.uChar.AsciiChar; | |
| } | |
| } | |
| } | |
| if ((c & 0177) == sim_del_char) | |
| c = 0177; | |
| if ((c & 0177) == sim_int_char) | |
| return SCPE_STOP; | |
| if ((sim_brk_char && ((c & 0177) == sim_brk_char)) || (c & SCPE_BREAK)) | |
| return SCPE_BREAK; | |
| return c | SCPE_KFLAG; | |
| } | |
| t_bool sim_os_poll_kbd_ready (int ms_timeout) | |
| { | |
| sim_debug (DBG_TRC, &sim_con_telnet, "sim_os_poll_kbd_ready()\n"); | |
| if ((std_input == NULL) || /* No keyboard for */ | |
| (std_input == INVALID_HANDLE_VALUE)) { /* background processes */ | |
| Sleep (ms_timeout); | |
| return FALSE; | |
| } | |
| return (WAIT_OBJECT_0 == WaitForSingleObject (std_input, ms_timeout)); | |
| } | |
| t_stat sim_os_putchar (int32 c) | |
| { | |
| DWORD unused; | |
| if (c != 0177) | |
| WriteConsoleA(std_output, &c, 1, &unused, NULL); | |
| return SCPE_OK; | |
| } | |
| /* OS/2 routines, from Bruce Ray and Holger Veit */ | |
| #elif defined (__OS2__) | |
| #include <conio.h> | |
| t_stat sim_os_ttinit (void) | |
| { | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttrun (void) | |
| { | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttcmd (void) | |
| { | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttclose (void) | |
| { | |
| return SCPE_OK; | |
| } | |
| t_bool sim_os_ttisatty (void) | |
| { | |
| return 1; | |
| } | |
| t_stat sim_os_poll_kbd (void) | |
| { | |
| int c; | |
| #if defined (__EMX__) | |
| switch (c = _read_kbd(0,0,0)) { /* EMX has _read_kbd */ | |
| case -1: /* no char*/ | |
| return SCPE_OK; | |
| case 0: /* char pending */ | |
| c = _read_kbd(0,1,0); | |
| break; | |
| default: /* got char */ | |
| break; | |
| } | |
| #else | |
| if (!kbhit ()) | |
| return SCPE_OK; | |
| c = getch(); | |
| #endif | |
| if ((c & 0177) == sim_del_char) | |
| c = 0177; | |
| if ((c & 0177) == sim_int_char) | |
| return SCPE_STOP; | |
| if (sim_brk_char && ((c & 0177) == sim_brk_char)) | |
| return SCPE_BREAK; | |
| return c | SCPE_KFLAG; | |
| } | |
| t_bool sim_os_poll_kbd_ready (int ms_timeout) /* Don't know how to do this on this platform */ | |
| { | |
| sim_os_ms_sleep (MIN(20,ms_timeout)); /* Wait a little */ | |
| return TRUE; /* force a poll */ | |
| } | |
| t_stat sim_os_putchar (int32 c) | |
| { | |
| if (c != 0177) { | |
| #if defined (__EMX__) | |
| putchar (c); | |
| #else | |
| putch (c); | |
| #endif | |
| fflush (stdout); | |
| } | |
| return SCPE_OK; | |
| } | |
| /* Metrowerks CodeWarrior Macintosh routines, from Louis Chretien and | |
| Peter Schorn */ | |
| #elif defined (__MWERKS__) && defined (macintosh) | |
| #include <console.h> | |
| #include <Mactypes.h> | |
| #include <string.h> | |
| #include <sioux.h> | |
| #include <unistd.h> | |
| #include <siouxglobals.h> | |
| #include <Traps.h> | |
| #include <LowMem.h> | |
| /* function prototypes */ | |
| Boolean SIOUXIsAppWindow(WindowPtr window); | |
| void SIOUXDoMenuChoice(long menuValue); | |
| void SIOUXUpdateMenuItems(void); | |
| void SIOUXUpdateScrollbar(void); | |
| int ps_kbhit(void); | |
| int ps_getch(void); | |
| extern char sim_name[]; | |
| extern pSIOUXWin SIOUXTextWindow; | |
| static CursHandle iBeamCursorH = NULL; /* contains the iBeamCursor */ | |
| static void updateCursor(void) { | |
| WindowPtr window; | |
| window = FrontWindow(); | |
| if (SIOUXIsAppWindow(window)) { | |
| GrafPtr savePort; | |
| Point localMouse; | |
| GetPort(&savePort); | |
| SetPort(window); | |
| #if TARGET_API_MAC_CARBON | |
| GetGlobalMouse(&localMouse); | |
| #else | |
| localMouse = LMGetMouseLocation(); | |
| #endif | |
| GlobalToLocal(&localMouse); | |
| if (PtInRect(localMouse, &(*SIOUXTextWindow->edit)->viewRect) && iBeamCursorH) { | |
| SetCursor(*iBeamCursorH); | |
| } | |
| else { | |
| SetCursor(&qd.arrow); | |
| } | |
| TEIdle(SIOUXTextWindow->edit); | |
| SetPort(savePort); | |
| } | |
| else { | |
| SetCursor(&qd.arrow); | |
| TEIdle(SIOUXTextWindow->edit); | |
| } | |
| return; | |
| } | |
| int ps_kbhit(void) { | |
| EventRecord event; | |
| int c; | |
| updateCursor(); | |
| SIOUXUpdateScrollbar(); | |
| while (GetNextEvent(updateMask | osMask | mDownMask | mUpMask | activMask | | |
| highLevelEventMask | diskEvt, &event)) { | |
| SIOUXHandleOneEvent(&event); | |
| } | |
| if (SIOUXQuitting) { | |
| exit(1); | |
| } | |
| if (EventAvail(keyDownMask,&event)) { | |
| c = event.message&charCodeMask; | |
| if ((event.modifiers & cmdKey) && (c > 0x20)) { | |
| GetNextEvent(keyDownMask, &event); | |
| SIOUXHandleOneEvent(&event); | |
| if (SIOUXQuitting) { | |
| exit(1); | |
| } | |
| return false; | |
| } | |
| return true; | |
| } | |
| else { | |
| return false; | |
| } | |
| } | |
| int ps_getch(void) { | |
| int c; | |
| EventRecord event; | |
| fflush(stdout); | |
| updateCursor(); | |
| while(!GetNextEvent(keyDownMask,&event)) { | |
| if (GetNextEvent(updateMask | osMask | mDownMask | mUpMask | activMask | | |
| highLevelEventMask | diskEvt, &event)) { | |
| SIOUXUpdateScrollbar(); | |
| SIOUXHandleOneEvent(&event); | |
| } | |
| } | |
| if (SIOUXQuitting) { | |
| exit(1); | |
| } | |
| c = event.message&charCodeMask; | |
| if ((event.modifiers & cmdKey) && (c > 0x20)) { | |
| SIOUXUpdateMenuItems(); | |
| SIOUXDoMenuChoice(MenuKey(c)); | |
| } | |
| if (SIOUXQuitting) { | |
| exit(1); | |
| } | |
| return c; | |
| } | |
| /* Note that this only works if the call to sim_ttinit comes before any output to the console */ | |
| t_stat sim_os_ttinit (void) { | |
| int i; | |
| /* this blank will later be replaced by the number of characters */ | |
| char title[50] = " "; | |
| unsigned char ptitle[50]; | |
| SIOUXSettings.autocloseonquit = TRUE; | |
| SIOUXSettings.asktosaveonclose = FALSE; | |
| SIOUXSettings.showstatusline = FALSE; | |
| SIOUXSettings.columns = 80; | |
| SIOUXSettings.rows = 40; | |
| SIOUXSettings.toppixel = 42; | |
| SIOUXSettings.leftpixel = 6; | |
| iBeamCursorH = GetCursor(iBeamCursor); | |
| strcat(title, sim_name); | |
| strcat(title, " Simulator"); | |
| title[0] = strlen(title) - 1; /* Pascal string done */ | |
| for (i = 0; i <= title[0]; i++) { /* copy to unsigned char */ | |
| ptitle[i] = title[i]; | |
| } | |
| SIOUXSetTitle(ptitle); | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttrun (void) | |
| { | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttcmd (void) | |
| { | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttclose (void) | |
| { | |
| return SCPE_OK; | |
| } | |
| t_bool sim_os_ttisatty (void) | |
| { | |
| return 1; | |
| } | |
| t_stat sim_os_poll_kbd (void) | |
| { | |
| int c; | |
| if (!ps_kbhit ()) | |
| return SCPE_OK; | |
| c = ps_getch(); | |
| if ((c & 0177) == sim_del_char) | |
| c = 0177; | |
| if ((c & 0177) == sim_int_char) return SCPE_STOP; | |
| if (sim_brk_char && ((c & 0177) == sim_brk_char)) | |
| return SCPE_BREAK; | |
| return c | SCPE_KFLAG; | |
| } | |
| t_bool sim_os_poll_kbd_ready (int ms_timeout) /* Don't know how to do this on this platform */ | |
| { | |
| sim_os_ms_sleep (MIN(20,ms_timeout)); /* Wait a little */ | |
| return TRUE; /* force a poll */ | |
| } | |
| t_stat sim_os_putchar (int32 c) | |
| { | |
| if (c != 0177) { | |
| putchar (c); | |
| fflush (stdout); | |
| } | |
| return SCPE_OK; | |
| } | |
| /* BSD UNIX routines */ | |
| #elif defined (BSDTTY) | |
| #include <sgtty.h> | |
| #include <fcntl.h> | |
| #include <unistd.h> | |
| struct sgttyb cmdtty,runtty; /* V6/V7 stty data */ | |
| struct tchars cmdtchars,runtchars; /* V7 editing */ | |
| struct ltchars cmdltchars,runltchars; /* 4.2 BSD editing */ | |
| int cmdfl,runfl; /* TTY flags */ | |
| t_stat sim_os_ttinit (void) | |
| { | |
| cmdfl = fcntl (0, F_GETFL, 0); /* get old flags and status */ | |
| runfl = cmdfl | FNDELAY; | |
| if (ioctl (0, TIOCGETP, &cmdtty) < 0) | |
| return SCPE_TTIERR; | |
| if (ioctl (0, TIOCGETC, &cmdtchars) < 0) | |
| return SCPE_TTIERR; | |
| if (ioctl (0, TIOCGLTC, &cmdltchars) < 0) | |
| return SCPE_TTIERR; | |
| runtty = cmdtty; /* initial run state */ | |
| runtty.sg_flags = cmdtty.sg_flags & ~(ECHO|CRMOD) | CBREAK; | |
| runtchars.t_intrc = sim_int_char; /* interrupt */ | |
| runtchars.t_quitc = 0xFF; /* no quit */ | |
| runtchars.t_startc = 0xFF; /* no host sync */ | |
| runtchars.t_stopc = 0xFF; | |
| runtchars.t_eofc = 0xFF; | |
| runtchars.t_brkc = 0xFF; | |
| runltchars.t_suspc = 0xFF; /* no specials of any kind */ | |
| runltchars.t_dsuspc = 0xFF; | |
| runltchars.t_rprntc = 0xFF; | |
| runltchars.t_flushc = 0xFF; | |
| runltchars.t_werasc = 0xFF; | |
| runltchars.t_lnextc = 0xFF; | |
| return SCPE_OK; /* return success */ | |
| } | |
| t_stat sim_os_ttrun (void) | |
| { | |
| runtchars.t_intrc = sim_int_char; /* in case changed */ | |
| fcntl (0, F_SETFL, runfl); /* non-block mode */ | |
| if (ioctl (0, TIOCSETP, &runtty) < 0) | |
| return SCPE_TTIERR; | |
| if (ioctl (0, TIOCSETC, &runtchars) < 0) | |
| return SCPE_TTIERR; | |
| if (ioctl (0, TIOCSLTC, &runltchars) < 0) | |
| return SCPE_TTIERR; | |
| nice (10); /* lower priority */ | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttcmd (void) | |
| { | |
| nice (-10); /* restore priority */ | |
| fcntl (0, F_SETFL, cmdfl); /* block mode */ | |
| if (ioctl (0, TIOCSETP, &cmdtty) < 0) | |
| return SCPE_TTIERR; | |
| if (ioctl (0, TIOCSETC, &cmdtchars) < 0) | |
| return SCPE_TTIERR; | |
| if (ioctl (0, TIOCSLTC, &cmdltchars) < 0) | |
| return SCPE_TTIERR; | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttclose (void) | |
| { | |
| return sim_ttcmd (); | |
| } | |
| t_bool sim_os_ttisatty (void) | |
| { | |
| return isatty (0); | |
| } | |
| t_stat sim_os_poll_kbd (void) | |
| { | |
| int status; | |
| unsigned char buf[1]; | |
| status = read (0, buf, 1); | |
| if (status != 1) return SCPE_OK; | |
| if (sim_brk_char && (buf[0] == sim_brk_char)) | |
| return SCPE_BREAK; | |
| else return (buf[0] | SCPE_KFLAG); | |
| } | |
| t_bool sim_os_poll_kbd_ready (int ms_timeout) | |
| { | |
| fd_set readfds; | |
| struct timeval timeout; | |
| if (!isatty (0)) { /* skip if !tty */ | |
| sim_os_ms_sleep (ms_timeout); | |
| return FALSE; | |
| } | |
| FD_ZERO (&readfds); | |
| FD_SET (0, &readfds); | |
| timeout.tv_sec = (ms_timeout*1000)/1000000; | |
| timeout.tv_usec = (ms_timeout*1000)%1000000; | |
| return (1 == select (1, &readfds, NULL, NULL, &timeout)); | |
| } | |
| t_stat sim_os_putchar (int32 out) | |
| { | |
| char c; | |
| c = out; | |
| write (1, &c, 1); | |
| return SCPE_OK; | |
| } | |
| /* POSIX UNIX routines, from Leendert Van Doorn */ | |
| #else | |
| #include <termios.h> | |
| #include <unistd.h> | |
| struct termios cmdtty, runtty; | |
| static int prior_norm = 1; | |
| t_stat sim_os_ttinit (void) | |
| { | |
| if (!isatty (fileno (stdin))) /* skip if !tty */ | |
| return SCPE_OK; | |
| if (tcgetattr (0, &cmdtty) < 0) /* get old flags */ | |
| return SCPE_TTIERR; | |
| runtty = cmdtty; | |
| runtty.c_lflag = runtty.c_lflag & ~(ECHO | ICANON); /* no echo or edit */ | |
| runtty.c_oflag = runtty.c_oflag & ~OPOST; /* no output edit */ | |
| runtty.c_iflag = runtty.c_iflag & ~ICRNL; /* no cr conversion */ | |
| runtty.c_cc[VINTR] = sim_int_char; /* interrupt */ | |
| runtty.c_cc[VQUIT] = 0; /* no quit */ | |
| runtty.c_cc[VERASE] = 0; | |
| runtty.c_cc[VKILL] = 0; | |
| runtty.c_cc[VEOF] = 0; | |
| runtty.c_cc[VEOL] = 0; | |
| runtty.c_cc[VSTART] = 0; /* no host sync */ | |
| runtty.c_cc[VSUSP] = 0; | |
| runtty.c_cc[VSTOP] = 0; | |
| #if defined (VREPRINT) | |
| runtty.c_cc[VREPRINT] = 0; /* no specials */ | |
| #endif | |
| #if defined (VDISCARD) | |
| runtty.c_cc[VDISCARD] = 0; | |
| #endif | |
| #if defined (VWERASE) | |
| runtty.c_cc[VWERASE] = 0; | |
| #endif | |
| #if defined (VLNEXT) | |
| runtty.c_cc[VLNEXT] = 0; | |
| #endif | |
| runtty.c_cc[VMIN] = 0; /* no waiting */ | |
| runtty.c_cc[VTIME] = 0; | |
| #if defined (VDSUSP) | |
| runtty.c_cc[VDSUSP] = 0; | |
| #endif | |
| #if defined (VSTATUS) | |
| runtty.c_cc[VSTATUS] = 0; | |
| #endif | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttrun (void) | |
| { | |
| if (!isatty (fileno (stdin))) /* skip if !tty */ | |
| return SCPE_OK; | |
| runtty.c_cc[VINTR] = sim_int_char; /* in case changed */ | |
| if (tcsetattr (0, TCSAFLUSH, &runtty) < 0) | |
| return SCPE_TTIERR; | |
| if (prior_norm) { /* at normal pri? */ | |
| errno = 0; | |
| (void)nice (10); /* try to lower pri */ | |
| prior_norm = errno; /* if no error, done */ | |
| } | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttcmd (void) | |
| { | |
| if (!isatty (fileno (stdin))) /* skip if !tty */ | |
| return SCPE_OK; | |
| if (!prior_norm) { /* priority down? */ | |
| errno = 0; | |
| (void)nice (-10); /* try to raise pri */ | |
| prior_norm = (errno == 0); /* if no error, done */ | |
| } | |
| if (tcsetattr (0, TCSAFLUSH, &cmdtty) < 0) | |
| return SCPE_TTIERR; | |
| return SCPE_OK; | |
| } | |
| t_stat sim_os_ttclose (void) | |
| { | |
| return sim_ttcmd (); | |
| } | |
| t_bool sim_os_ttisatty (void) | |
| { | |
| return isatty (fileno (stdin)); | |
| } | |
| t_stat sim_os_poll_kbd (void) | |
| { | |
| int status; | |
| unsigned char buf[1]; | |
| status = read (0, buf, 1); | |
| if (status != 1) return SCPE_OK; | |
| if (sim_brk_char && (buf[0] == sim_brk_char)) | |
| return SCPE_BREAK; | |
| else return (buf[0] | SCPE_KFLAG); | |
| } | |
| t_bool sim_os_poll_kbd_ready (int ms_timeout) | |
| { | |
| fd_set readfds; | |
| struct timeval timeout; | |
| if (!sim_os_ttisatty()) { /* skip if !tty */ | |
| sim_os_ms_sleep (ms_timeout); | |
| return FALSE; | |
| } | |
| FD_ZERO (&readfds); | |
| FD_SET (0, &readfds); | |
| timeout.tv_sec = (ms_timeout*1000)/1000000; | |
| timeout.tv_usec = (ms_timeout*1000)%1000000; | |
| return (1 == select (1, &readfds, NULL, NULL, &timeout)); | |
| } | |
| t_stat sim_os_putchar (int32 out) | |
| { | |
| char c; | |
| c = out; | |
| (void)write (1, &c, 1); | |
| return SCPE_OK; | |
| } | |
| #endif |