/* scp_tty.c: operating system-dependent I/O routines | |
Copyright (c) 1993-2001, 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. | |
20-Jul-01 RMS Added Macintosh support (from Louis Chretien, Peter Schorn, | |
and 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 (from 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: | |
ttinit - called once to get initial terminal state | |
ttrunstate - called to put terminal into run state | |
ttcmdstate - called to return terminal to command state | |
ttclose - called once before the simulator exits | |
sim_poll_kbd - poll for keyboard input | |
sim_putchar - output character to terminal | |
This module implements the following routines to support clock calibration: | |
sim_os_msec - return elapsed time in msec | |
Versions are included for VMS, Windows, OS/2, Macintosh, BSD UNIX, and POSIX UNIX. | |
The POSIX UNIX version works with LINUX. | |
*/ | |
#undef USE_INT64 /* hack for Windows */ | |
#include "sim_defs.h" | |
int32 sim_int_char = 005; /* interrupt character */ | |
extern FILE *sim_log; | |
/* VMS routines, from Ben Thomas */ | |
#if defined (VMS) | |
#define __TTYROUTINES 0 | |
#include <descrip.h> | |
#include <ttdef.h> | |
#include <tt2def.h> | |
#include <iodef.h> | |
#include <ssdef.h> | |
#include <starlet.h> | |
#define EFN 0 | |
unsigned int32 tty_chan = 0; | |
typedef struct { | |
unsigned short sense_count; | |
unsigned char sense_first_char; | |
unsigned char sense_reserved; | |
unsigned int32 stat; | |
unsigned int32 stat2; } SENSE_BUF; | |
typedef struct { | |
unsigned int16 status; | |
unsigned int16 count; | |
unsigned int32 dev_status; } IOSB; | |
SENSE_BUF cmd_mode = { 0 }; | |
SENSE_BUF run_mode = { 0 }; | |
t_stat ttinit (void) | |
{ | |
unsigned int32 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 ttrunstate (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 ttcmdstate (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 ttclose (void) | |
{ | |
return ttcmdstate (); | |
} | |
t_stat sim_poll_kbd (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; | |
return (buf[0] | SCPE_KFLAG); | |
} | |
t_stat sim_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 (sim_log) fputc (c, sim_log); | |
if ((status != SS$_NORMAL) || (iosb.status != SS$_NORMAL)) return SCPE_TTOERR; | |
return SCPE_OK; | |
} | |
const t_bool rtc_avail = TRUE; | |
uint32 sim_os_msec () | |
{ | |
uint32 quo, htod, tod[2]; | |
int32 i; | |
sys$gettim (tod); /* time 0.1usec */ | |
/* To convert to msec, must divide 64b quantity by 10000. This is actually done | |
by dividing the 96b quantity 0'time by 10000, producing 64b of quotient, the | |
high 32b of which are discared. This can probably be done by a clever multiply... | |
*/ | |
quo = htod = 0; | |
for (i = 0; i < 64; i++) { /* 64b quo */ | |
htod = (htod << 1) | ((tod[1] >> 31) & 1); /* shift divd */ | |
tod[1] = (tod[1] << 1) | ((tod[0] >> 31) & 1); | |
tod[0] = tod[0] << 1; | |
quo = quo << 1; /* shift quo */ | |
if (htod >= 10000) { /* divd work? */ | |
htod = htod - 10000; /* subtract */ | |
quo = quo | 1; } } /* set quo bit */ | |
return quo; | |
} | |
#endif | |
/* Win32 routines */ | |
#if defined (WIN32) | |
#define __TTYROUTINES 0 | |
#include <conio.h> | |
#include <windows.h> | |
#include <signal.h> | |
static volatile int sim_win_ctlc = 0; | |
void win_handler (int sig) | |
{ | |
sim_win_ctlc = 1; | |
return; | |
} | |
t_stat ttinit (void) | |
{ | |
return SCPE_OK; | |
} | |
t_stat ttrunstate (void) | |
{ | |
sim_win_ctlc = 0; | |
if ((int) signal (SIGINT, win_handler) == -1) return SCPE_SIGERR; | |
return SCPE_OK; | |
} | |
t_stat ttcmdstate (void) | |
{ | |
return SCPE_OK; | |
} | |
t_stat ttclose (void) | |
{ | |
return SCPE_OK; | |
} | |
t_stat sim_poll_kbd (void) | |
{ | |
int c; | |
if (sim_win_ctlc) { | |
sim_win_ctlc = 0; | |
signal (SIGINT, win_handler); | |
return 003 | SCPE_KFLAG; } | |
if (!kbhit ()) return SCPE_OK; | |
c = _getch (); | |
if ((c & 0177) == '\b') c = 0177; | |
if ((c & 0177) == sim_int_char) return SCPE_STOP; | |
return c | SCPE_KFLAG; | |
} | |
t_stat sim_putchar (int32 c) | |
{ | |
if (c != 0177) { | |
_putch (c); | |
if (sim_log) fputc (c, sim_log); } | |
return SCPE_OK; | |
} | |
const t_bool rtc_avail = TRUE; | |
uint32 sim_os_msec () | |
{ | |
return GetTickCount (); | |
} | |
#endif | |
/* OS/2 routines, from Bruce Ray */ | |
#if defined (__OS2__) | |
#define __TTYROUTINES 0 | |
#include <conio.h> | |
t_stat ttinit (void) | |
{ | |
return SCPE_OK; | |
} | |
t_stat ttrunstate (void) | |
{ | |
return SCPE_OK; | |
} | |
t_stat ttcmdstate (void) | |
{ | |
return SCPE_OK; | |
} | |
t_stat ttclose (void) | |
{ | |
return SCPE_OK; | |
} | |
t_stat sim_poll_kbd (void) | |
{ | |
int c; | |
if (!kbhit ()) return SCPE_OK; | |
c = getch(); | |
if ((c & 0177) == '\b') c = 0177; | |
if ((c & 0177) == sim_int_char) return SCPE_STOP; | |
return c | SCPE_KFLAG; | |
} | |
t_stat sim_putchar (int32 c) | |
{ | |
if (c != 0177) { | |
putch (c); | |
fflush (stdout) ; | |
if (sim_log) fputc (c, sim_log); } | |
return SCPE_OK; | |
} | |
const t_bool rtc_avail = FALSE; | |
uint32 sim_os_msec () | |
{ | |
return 0; | |
} | |
#endif | |
/* Metrowerks CodeWarrior Macintosh routines, from Louis Chretien, | |
Peter Schorn, and Ben Supnik | |
*/ | |
#if defined (__MWERKS__) && defined (macintosh) | |
#define __TTYROUTINES 0 | |
#include <Timer.h> | |
#include <console.h> | |
#include <Mactypes.h> | |
#include <string.h> | |
#include <sioux.h> | |
#include <siouxglobals.h> | |
#include <Traps.h> | |
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); | |
GetGlobalMouse(&localMouse); | |
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; | |
} | |
t_stat ttinit (void) { | |
/* Note that this only works if the call to ttinit comes before any output to the console */ | |
int i; | |
char title[50] = " "; /* this blank will later be replaced by the number of characters */ | |
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 ttrunstate (void) | |
{ | |
return SCPE_OK; | |
} | |
t_stat ttcmdstate (void) | |
{ | |
return SCPE_OK; | |
} | |
t_stat ttclose (void) | |
{ | |
return SCPE_OK; | |
} | |
t_stat sim_poll_kbd (void) | |
{ | |
int c; | |
if (!ps_kbhit ()) return SCPE_OK; | |
c = ps_getch(); | |
if ((c & 0177) == '\b') c = 0177; | |
if ((c & 0177) == sim_int_char) return SCPE_STOP; | |
return c | SCPE_KFLAG; | |
} | |
t_stat sim_putchar (int32 c) | |
{ | |
if (c != 0177) { | |
putchar (c); | |
fflush (stdout) ; | |
if (sim_log) fputc (c, sim_log); } | |
return SCPE_OK; | |
} | |
const t_bool rtc_avail = TRUE; | |
uint32 sim_os_msec (void) | |
{ | |
unsigned long long micros; | |
UnsignedWide macMicros; | |
unsigned long millis; | |
Microseconds (&macMicros); | |
micros = *((unsigned long long *) &macMicros); | |
millis = micros / 1000LL; | |
return (uint32) millis; | |
} | |
#endif | |
/* BSD UNIX routines */ | |
#if defined (BSDTTY) | |
#define __TTYROUTINES 0 | |
#include <sgtty.h> | |
#include <fcntl.h> | |
#include <sys/time.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 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 ttrunstate (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; | |
return SCPE_OK; | |
} | |
t_stat ttcmdstate (void) | |
{ | |
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 ttclose (void) | |
{ | |
return ttcmdstate (); | |
} | |
t_stat sim_poll_kbd (void) | |
{ | |
int status; | |
unsigned char buf[1]; | |
status = read (0, buf, 1); | |
if (status != 1) return SCPE_OK; | |
else return (buf[0] | SCPE_KFLAG); | |
} | |
t_stat sim_putchar (int32 out) | |
{ | |
char c; | |
c = out; | |
write (1, &c, 1); | |
if (sim_log) fputc (c, sim_log); | |
return SCPE_OK; | |
} | |
const t_bool rtc_avail = TRUE; | |
uint32 sim_os_msec () | |
{ | |
struct timeval cur; | |
struct timezone foo; | |
uint32 msec; | |
gettimeofday (&cur, &foo); | |
msec = (((uint32) cur.tv_sec) * 1000) + (((uint32) cur.tv_usec) / 1000); | |
return msec; | |
} | |
#endif | |
/* POSIX UNIX routines, from Leendert Van Doorn */ | |
#if !defined (__TTYROUTINES) | |
#include <termios.h> | |
#include <sys/time.h> | |
struct termios cmdtty, runtty; | |
t_stat ttinit (void) | |
{ | |
if (tcgetattr (0, &cmdtty) < 0) return SCPE_TTIERR; /* get old flags */ | |
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 ttrunstate (void) | |
{ | |
runtty.c_cc[VINTR] = sim_int_char; /* in case changed */ | |
if (tcsetattr (0, TCSAFLUSH, &runtty) < 0) return SCPE_TTIERR; | |
return SCPE_OK; | |
} | |
t_stat ttcmdstate (void) | |
{ | |
if (tcsetattr (0, TCSAFLUSH, &cmdtty) < 0) return SCPE_TTIERR; | |
return SCPE_OK; | |
} | |
t_stat ttclose (void) | |
{ | |
return ttcmdstate (); | |
} | |
t_stat sim_poll_kbd (void) | |
{ | |
int status; | |
unsigned char buf[1]; | |
status = read (0, buf, 1); | |
if (status != 1) return SCPE_OK; | |
else return (buf[0] | SCPE_KFLAG); | |
} | |
t_stat sim_putchar (int32 out) | |
{ | |
char c; | |
c = out; | |
write (1, &c, 1); | |
if (sim_log) fputc (c, sim_log); | |
return SCPE_OK; | |
} | |
const t_bool rtc_avail = TRUE; | |
uint32 sim_os_msec () | |
{ | |
struct timeval cur; | |
uint32 msec; | |
gettimeofday (&cur, NULL); | |
msec = (((uint32) cur.tv_sec) * 1000) + (((uint32) cur.tv_usec) / 1000); | |
return msec; | |
} | |
#endif |