1 /* NetHack 3.6 vmstty.c $NHDT-Date: 1432512790 2015/05/25 00:13:10 $ $NHDT-Branch: master $:$NHDT-Revision: 1.15 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /* NetHack may be freely redistributed. See license for details. */
4 /* tty.c - (VMS) version */
17 #else /* values needed from missing include files */
18 #define SMG$K_TRM_UP 274
19 #define SMG$K_TRM_DOWN 275
20 #define SMG$K_TRM_LEFT 276
21 #define SMG$K_TRM_RIGHT 277
22 #define TT$M_MECHTAB 0x00000100 /* hardware tab support */
23 #define TT$M_MECHFORM 0x00080000 /* hardware form-feed support */
24 #define TT$M_NOBRDCST 0x00020000 /* disable broadcast messages, but */
25 #define TT2$M_BRDCSTMBX 0x00000010 /* catch them in associated mailbox */
26 #define TT2$M_APP_KEYPAD 0x00800000 /* application vs numeric keypad mode */
34 unsigned long lib$disable_ctrl(), lib$enable_ctrl();
35 unsigned long sys$assign(), sys$dassgn(), sys$qiow();
37 unsigned long smg$create_virtual_keyboard(), smg$delete_virtual_keyboard(),
38 smg$read_keystroke(), smg$cancel_input();
40 static short FDECL(parse_function_key, (int));
42 static void NDECL(setctty);
43 static void NDECL(resettty);
45 #define vms_ok(sts) ((sts) &1)
46 #define META(c) ((c) | 0x80) /* 8th bit */
47 #define CTRL(c) ((c) &0x1F)
48 #define CMASK(c) (1 << CTRL(c))
49 #define LIB$M_CLI_CTRLT CMASK('T') /* 0x00100000 */
50 #define LIB$M_CLI_CTRLY CMASK('Y') /* 0x02000000 */
52 #define CSI META(ESC) /* '\233' */
53 #define SS3 META(CTRL('O')) /* '\217' */
56 char erase_char, intr_char, kill_char;
57 static boolean settty_needed = FALSE, bombing = FALSE;
58 static unsigned long kb = 0;
60 static char inputbuf[15 + 1], *inp = 0;
64 #define QIO_FUNC IO$_TTYREADALL | IO$M_NOECHO | IO$M_TRMNOECHO
66 #define TT_SPECIAL_HANDLING (TT$M_MECHTAB | TT$M_MECHFORM | TT$M_NOBRDCST)
67 #define TT2_SPECIAL_HANDLING (TT2$M_BRDCSTMBX)
69 #define TT_SPECIAL_HANDLING (TT$M_MECHTAB | TT$M_MECHFORM)
70 #define TT2_SPECIAL_HANDLING (0)
72 #define Uword unsigned short
73 #define Ubyte unsigned char
74 struct _rd_iosb { /* i/o status block for read */
75 Uword status, trm_offset;
76 Uword terminator, trm_siz;
78 struct _wr_iosb { /* i/o status block for write */
79 Uword status, byte_cnt;
82 struct _sm_iosb { /* i/o status block for sense-mode qio */
84 Ubyte xmt_speed, rcv_speed;
85 Ubyte cr_fill, lf_fill, parity;
88 struct _sm_bufr { /* sense-mode characteristics buffer */
89 Ubyte class, type; /* class==DC$_TERM, type==(various) */
90 Uword buf_siz; /* aka page width */
91 #define page_width buf_siz /* number of columns */
92 unsigned tt_char : 24; /* primary characteristics */
93 unsigned page_length : 8; /* number of lines */
94 unsigned tt2_char : 32; /* secondary characteristics */
99 } sg = { { 0 }, { 0 } };
100 static unsigned short tt_chan = 0;
101 static unsigned long tt_char_restore = 0, tt_char_active = 0,
102 tt2_char_restore = 0, tt2_char_active = 0;
103 static unsigned long ctrl_mask = 0;
106 extern int NDECL(nh_vms_getchar);
108 /* rename the real vms_getchar and interpose this one in front of it */
112 static int althack = 0, altprefix;
118 nhalthack = nh_getenv("NH_ALTHACK");
119 althack = nhalthack ? 1 : -1;
121 altprefix = *nhalthack;
124 #define vms_getchar nh_vms_getchar
127 if (althack > 0 && res == altprefix) {
141 struct _rd_iosb iosb;
143 unsigned char kb_buf;
144 #else /* SMG input */
145 static volatile int recurse = 0; /* SMG is not AST re-entrant! */
148 if (program_state.done_hup) {
149 /* hangup has occurred; do not attempt to get further user input */
155 /* we have buffered character(s) from previous read */
160 sts = sys$qiow(0, tt_chan, QIO_FUNC, &iosb, (void (*) ()) 0, 0,
161 &kb_buf, sizeof kb_buf, 0, 0, 0, 0);
164 if (kb_buf == CTRL('C')) {
167 key = (short) kb_buf;
168 } else if (kb_buf == '\r') { /* <return> */
170 } else if (kb_buf == ESC || kb_buf == CSI || kb_buf == SS3) {
171 switch (parse_function_key((int) kb_buf)) {
181 case SMG$K_TRM_RIGHT:
189 key = (short) kb_buf;
191 } else if (sts == SS$_HANGUP || iosb.status == SS$_HANGUP
192 || sts == SS$_DEVOFFLINE) {
195 } else /*(this should never happen)*/
198 #else /*!USE_QIO_INPUT*/
199 if (recurse++ == 0 && kb != 0) {
200 smg$read_keystroke(&kb, &key);
211 case SMG$K_TRM_RIGHT:
223 /* abnormal input--either SMG didn't initialize properly or
224 vms_getchar() has been called recursively (via SIGINT handler).
226 if (kb != 0) /* must have been a recursive call */
227 smg$cancel_input(&kb); /* from an interrupt handler */
231 #endif /* USE_QIO_INPUT */
238 * We've just gotten an <escape> character. Do a timed read to
239 * get any other characters, then try to parse them as an escape
240 * sequence. This isn't perfect, since there's no guarantee
241 * that a full escape sequence will be available, or even if one
242 * is, it might actually by regular input from a fast typist or
243 * a stalled input connection. {For packetized environments,
244 * cross plural(body_part(FINGER)) and hope for best. :-}
246 * This is needed to preserve compatibility with SMG interface
248 * 1) retain support for arrow keys, and
249 * 2) treat other VTxxx function keys as <esc> for aborting
250 * various NetHack prompts.
251 * The second reason is compelling; otherwise remaining chars of
252 * an escape sequence get treated as inappropriate user commands.
254 * SMG code values for these key sequences fall in the range of
255 * 256 thru 3xx. The assignments are not particularly intuitive.
258 -- Summary of VTxxx-style keyboards and transmitted escape sequences. --
259 Keypad codes are prefixed by 7 bit (\033 O) or 8 bit SS3:
260 keypad: PF1 PF2 PF3 PF4 codes: P Q R S
263 1 2 3 :en-: q r s : :
264 ...0... , :ter: ...p... l :M:
265 Arrows are prefixed by either SS3 or CSI (either 7 or 8 bit), depending on
266 whether the terminal is in application or numeric mode (ditto for PF keys):
267 arrows: <up> <dwn> <lft> <rgt> A B D C
268 Additional function keys (vk201/vk401) generate CSI nn ~ (nn is 1 or 2
270 vk201 keys: F6 F7 F8 F9 F10 F11 F12 F13 F14 Help Do F17 F18 F19 F20
271 'nn' digits: 17 18 19 20 21 23 24 25 26 28 29 31 32 33 34
272 alternate: ^C ^[ ^H ^J (when in VT100 mode)
273 edit keypad: <fnd> <ins> <rmv> digits: 1 2 3
274 <sel> <prv> <nxt> 4 5 6
275 VT52 mode: arrows and PF keys send ESCx where x is in A-D or P-S.
278 static const char *arrow_or_PF = "ABCDPQRS", /* suffix char */
279 *smg_keypad_codes = "PQRSpqrstuvwxyMmlnABDC";
280 /* PF1..PF4,KP0..KP9,enter,dash,comma,dot,up-arrow,down,left,right */
281 /* Ultimate return value is (index into smg_keypad_codes[] + 256). */
284 parse_function_key(c)
287 struct _rd_iosb iosb;
289 char seq_buf[15 + 1]; /* plenty room for escape sequence + slop */
290 short result = ESC; /* translate to <escape> by default */
293 * Read whatever we can from type-ahead buffer (1 second timeout).
294 * If the user typed an actual <escape> to deliberately abort
295 * something, he or she should be able to tolerate the necessary
296 * restriction of a negligible pause before typing anything else.
297 * We might already have [at least some of] an escape sequence from a
298 * previous read, particularly if user holds down the arrow keys...
301 strncpy(seq_buf, inp, inc);
302 if (inc < (int) (sizeof seq_buf) - 1) {
303 sts = sys$qiow(0, tt_chan, QIO_FUNC | IO$M_TIMED, &iosb,
304 (void (*) ()) 0, 0, seq_buf + inc,
305 sizeof seq_buf - 1 - inc, 1, 0, 0, 0);
310 if (vms_ok(sts) || sts == SS$_TIMEOUT) {
311 register int cnt = iosb.trm_offset + iosb.trm_siz + inc;
312 register char *p = seq_buf;
313 if (c == ESC) /* check for 7-bit vt100/ANSI, or vt52 */
314 if (*p == '[' || *p == 'O')
315 c = META(CTRL(*p++)), cnt--;
316 else if (strchr(arrow_or_PF, *p))
318 if (cnt > 0 && (c == SS3 || (c == CSI && strchr(arrow_or_PF, *p)))) {
319 register char *q = strchr(smg_keypad_codes, *p);
321 result = 256 + (q - smg_keypad_codes);
322 p++, --cnt; /* one more char consumed */
323 } else if (cnt > 1 && c == CSI) {
324 static short /* "CSI nn ~" -> F_keys[nn] */
327 311, 312, 313, 314, 315, 316, /* E1-E6 */
328 ESC, ESC, ESC, ESC, /*(more filler)*/
329 281, 282, 283, 284, 285, ESC, /* F1-F5 */
330 286, 287, 288, 289, 290, ESC, /* F6-F10*/
331 291, 292, 293, 294, ESC, /*F11-F14*/
332 295, 296, ESC, /*<help>,<do>, aka F15,F16*/
333 297, 298, 299, 300 /*F17-F20*/
334 }; /* note: there are several missing nn in CSI nn ~ values */
337 *(p + cnt) = '\0'; /* terminate string */
339 if (q && sscanf(p, "%d~", &nn) == 1) {
340 if (nn > 0 && nn < SIZE(F_keys))
347 strncpy((inp = inputbuf), p, (inc = cnt));
353 #endif /* USE_QIO_INPUT */
358 struct _sm_iosb iosb;
359 unsigned long status;
361 status = sys$qiow(0, tt_chan, IO$_SETMODE, &iosb, (void (*) ()) 0, 0,
362 &sg.sm, sizeof sg.sm, 0, 0, 0, 0);
364 status = iosb.status;
365 if (vms_ok(status)) {
366 /* try to force terminal into synch with TTDRIVER's setting */
367 number_pad((sg.sm.tt2_char & TT2$M_APP_KEYPAD) ? -1 : 1);
370 errno = EVMSERR, vaxc$errno = status;
371 perror("NetHack(setctty: setmode)");
376 static void resettty() /* atexit() routine */
379 bombing = TRUE; /* don't clear screen; preserve traceback info */
382 (void) sys$dassgn(tt_chan), tt_chan = 0;
386 * Get initial state of terminal, set ospeed (for termcap routines)
387 * and switch off tab expansion if necessary.
388 * Called by init_nhwindows() and resume_nhwindows() in wintty.c
389 * (for initial startup and for returning from '!' or ^Z).
394 static char dev_tty[] = "TT:";
395 static $DESCRIPTOR(tty_dsc, dev_tty);
397 unsigned long status, zero = 0;
399 if (tt_chan == 0) { /* do this stuff once only */
400 iflags.cbreak = OFF, iflags.echo = ON; /* until setup is complete */
401 status = sys$assign(&tty_dsc, &tt_chan, 0, 0);
402 if (!vms_ok(status)) {
403 raw_print(""), err++;
404 errno = EVMSERR, vaxc$errno = status;
405 perror("NetHack(gettty: $assign)");
407 atexit(resettty); /* register an exit handler to reset things */
409 status = sys$qiow(0, tt_chan, IO$_SENSEMODE, &sg.io, (void (*) ()) 0, 0,
410 &sg.sm, sizeof sg.sm, 0, 0, 0, 0);
412 status = sg.io.status;
413 if (!vms_ok(status)) {
414 raw_print(""), err++;
415 errno = EVMSERR, vaxc$errno = status;
416 perror("NetHack(gettty: sensemode)");
418 ospeed = sg.io.xmt_speed;
419 erase_char = '\177'; /* <rubout>, aka <delete> */
420 kill_char = CTRL('U');
421 intr_char = CTRL('C');
422 (void) lib$enable_ctrl(&zero, &ctrl_mask);
423 /* Use the systems's values for lines and columns if it has any idea. */
424 if (sg.sm.page_length)
425 LI = sg.sm.page_length;
426 if (sg.sm.page_width)
427 CO = sg.sm.page_width;
428 /* suppress tab and form-feed expansion, in case termcap uses them */
429 tt_char_restore = sg.sm.tt_char;
430 tt_char_active = sg.sm.tt_char |= TT_SPECIAL_HANDLING;
431 tt2_char_restore = sg.sm.tt2_char;
432 tt2_char_active = sg.sm.tt2_char |= TT2_SPECIAL_HANDLING;
433 #if 0 /*[ defer until setftty() ]*/
441 /* reset terminal to original state */
451 disable_broadcast_trapping();
452 #if 0 /* let SMG's exit handler do the cleanup (as per doc) */
453 /* #ifndef USE_QIO_INPUT */
454 if (kb) smg$delete_virtual_keyboard(&kb), kb = 0;
455 #endif /* 0 (!USE_QIO_INPUT) */
457 (void) lib$enable_ctrl(&ctrl_mask, 0);
460 /* reset original tab, form-feed, broadcast settings */
461 sg.sm.tt_char = tt_char_restore;
462 sg.sm.tt2_char = tt2_char_restore;
465 settty_needed = FALSE;
469 /* same as settty, with no clearing of the screen */
482 unsigned long mask = LIB$M_CLI_CTRLT | LIB$M_CLI_CTRLY;
484 (void) lib$disable_ctrl(&mask, 0);
485 if (kb == 0) { /* do this stuff once only */
488 #else /*!USE_QIO_INPUT*/
489 smg$create_virtual_keyboard(&kb);
490 #endif /*USE_QIO_INPUT*/
491 init_broadcast_trapping();
493 enable_broadcast_trapping(); /* no-op if !defined(MAIL) */
494 iflags.cbreak = (kb != 0) ? ON : OFF;
495 iflags.echo = (kb != 0) ? OFF : ON;
496 /* disable tab & form-feed expansion; prepare for broadcast trapping */
497 sg.sm.tt_char = tt_char_active;
498 sg.sm.tt2_char = tt2_char_active;
502 settty_needed = TRUE;
505 void intron() /* enable kbd interupts if enabled when game started */
507 intr_char = CTRL('C');
510 void introff() /* disable kbd interrupts if required*/
517 extern unsigned long FDECL(lib$emul, (const long *, const long *,
518 const long *, long *));
519 extern unsigned long sys$schdwk(), sys$hiber();
521 #define VMS_UNITS_PER_SECOND 10000000L /* hundreds of nanoseconds, 1e-7 */
522 /* constant for conversion from milliseconds to VMS delta time (negative) */
523 static const long mseconds_to_delta = VMS_UNITS_PER_SECOND / 1000L * -1L;
525 /* sleep for specified number of milliseconds (note: the timer used
526 generally only has 10-millisecond resolution at the hardware level...) */
529 unsigned mseconds; /* milliseconds */
531 long pid = 0L, zero = 0L, msec, qtime[2];
533 msec = (long) mseconds;
535 /* qtime{0:63} = msec{0:31} * mseconds_to_delta{0:31} + zero{0:31} */
536 vms_ok(lib$emul(&msec, &mseconds_to_delta, &zero, qtime))) {
537 /* schedule a wake-up call, then go to sleep */
538 if (vms_ok(sys$schdwk(&pid, (genericptr_t) 0, qtime, (long *) 0)))
543 #endif /* TIMED_DELAY */
548 VA_DECL(const char *, s)
551 VA_INIT(s, const char *);
556 (void) putchar('\n');
558 #ifndef SAVE_ON_FATAL_ERROR
559 /* prevent vmsmain's exit handler byebye() from calling hangup() */
560 sethanguphandler((void FDECL((*), (int) )) SIG_DFL);