OSDN Git Service

import nethack-3.6.0
[jnethack/source.git] / sys / vms / vmstty.c
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 */
5
6 #define NEED_VARARGS
7 #include "hack.h"
8 #include "wintty.h"
9 #include "tcap.h"
10
11 #include <descrip.h>
12 #include <iodef.h>
13 #ifndef __GNUC__
14 #include <smgdef.h>
15 #include <ttdef.h>
16 #include <tt2def.h>
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 */
27 #endif                              /* __GNUC__ */
28 #ifdef USE_QIO_INPUT
29 #include <ssdef.h>
30 #endif
31 #include <errno.h>
32 #include <signal.h>
33
34 unsigned long lib$disable_ctrl(), lib$enable_ctrl();
35 unsigned long sys$assign(), sys$dassgn(), sys$qiow();
36 #ifndef USE_QIO_INPUT
37 unsigned long smg$create_virtual_keyboard(), smg$delete_virtual_keyboard(),
38     smg$read_keystroke(), smg$cancel_input();
39 #else
40 static short FDECL(parse_function_key, (int));
41 #endif
42 static void NDECL(setctty);
43 static void NDECL(resettty);
44
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 */
51 #define ESC '\033'
52 #define CSI META(ESC)       /* '\233' */
53 #define SS3 META(CTRL('O')) /* '\217' */
54
55 extern short ospeed;
56 char erase_char, intr_char, kill_char;
57 static boolean settty_needed = FALSE, bombing = FALSE;
58 static unsigned long kb = 0;
59 #ifdef USE_QIO_INPUT
60 static char inputbuf[15 + 1], *inp = 0;
61 static int inc = 0;
62 #endif
63
64 #define QIO_FUNC IO$_TTYREADALL | IO$M_NOECHO | IO$M_TRMNOECHO
65 #ifdef MAIL
66 #define TT_SPECIAL_HANDLING (TT$M_MECHTAB | TT$M_MECHFORM | TT$M_NOBRDCST)
67 #define TT2_SPECIAL_HANDLING (TT2$M_BRDCSTMBX)
68 #else
69 #define TT_SPECIAL_HANDLING (TT$M_MECHTAB | TT$M_MECHFORM)
70 #define TT2_SPECIAL_HANDLING (0)
71 #endif
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;
77 };
78 struct _wr_iosb { /* i/o status block for write */
79     Uword status, byte_cnt;
80     unsigned : 32;
81 };
82 struct _sm_iosb { /* i/o status block for sense-mode qio */
83     Uword status;
84     Ubyte xmt_speed, rcv_speed;
85     Ubyte cr_fill, lf_fill, parity;
86     unsigned : 8;
87 };
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 */
95 };
96 static struct {
97     struct _sm_iosb io;
98     struct _sm_bufr sm;
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;
104
105 #ifdef DEBUG
106 extern int NDECL(nh_vms_getchar);
107
108 /* rename the real vms_getchar and interpose this one in front of it */
109 int
110 vms_getchar()
111 {
112     static int althack = 0, altprefix;
113     char *nhalthack;
114     int res;
115
116     if (!althack) {
117         /* one-time init */
118         nhalthack = nh_getenv("NH_ALTHACK");
119         althack = nhalthack ? 1 : -1;
120         if (althack > 0)
121             altprefix = *nhalthack;
122     }
123
124 #define vms_getchar nh_vms_getchar
125
126     res = vms_getchar();
127     if (althack > 0 && res == altprefix) {
128         res = vms_getchar();
129         if (res != ESC)
130             res = META(res);
131     }
132     return res;
133 }
134 #endif /*DEBUG*/
135
136 int
137 vms_getchar()
138 {
139     short key;
140 #ifdef USE_QIO_INPUT
141     struct _rd_iosb iosb;
142     unsigned long sts;
143     unsigned char kb_buf;
144 #else /* SMG input */
145     static volatile int recurse = 0; /* SMG is not AST re-entrant! */
146 #endif
147
148     if (program_state.done_hup) {
149         /* hangup has occurred; do not attempt to get further user input */
150         return ESC;
151     }
152
153 #ifdef USE_QIO_INPUT
154     if (inc > 0) {
155         /* we have buffered character(s) from previous read */
156         kb_buf = *inp++;
157         --inc;
158         sts = SS$_NORMAL;
159     } else {
160         sts = sys$qiow(0, tt_chan, QIO_FUNC, &iosb, (void (*) ()) 0, 0,
161                        &kb_buf, sizeof kb_buf, 0, 0, 0, 0);
162     }
163     if (vms_ok(sts)) {
164         if (kb_buf == CTRL('C')) {
165             if (intr_char)
166                 gsignal(SIGINT);
167             key = (short) kb_buf;
168         } else if (kb_buf == '\r') { /* <return> */
169             key = (short) '\n';
170         } else if (kb_buf == ESC || kb_buf == CSI || kb_buf == SS3) {
171             switch (parse_function_key((int) kb_buf)) {
172             case SMG$K_TRM_UP:
173                 key = Cmd.move_N;
174                 break;
175             case SMG$K_TRM_DOWN:
176                 key = Cmd.move_S;
177                 break;
178             case SMG$K_TRM_LEFT:
179                 key = Cmd.move_W;
180                 break;
181             case SMG$K_TRM_RIGHT:
182                 key = Cmd.move_E;
183                 break;
184             default:
185                 key = ESC;
186                 break;
187             }
188         } else {
189             key = (short) kb_buf;
190         }
191     } else if (sts == SS$_HANGUP || iosb.status == SS$_HANGUP
192                || sts == SS$_DEVOFFLINE) {
193         gsignal(SIGHUP);
194         key = ESC;
195     } else /*(this should never happen)*/
196         key = getchar();
197
198 #else  /*!USE_QIO_INPUT*/
199     if (recurse++ == 0 && kb != 0) {
200         smg$read_keystroke(&kb, &key);
201         switch (key) {
202         case SMG$K_TRM_UP:
203             key = Cmd.move_N;
204             break;
205         case SMG$K_TRM_DOWN:
206             key = Cmd.move_S;
207             break;
208         case SMG$K_TRM_LEFT:
209             key = Cmd.move_W;
210             break;
211         case SMG$K_TRM_RIGHT:
212             key = Cmd.move_E;
213             break;
214         case '\r':
215             key = '\n';
216             break;
217         default:
218             if (key > 255)
219                 key = ESC;
220             break;
221         }
222     } else {
223         /* abnormal input--either SMG didn't initialize properly or
224            vms_getchar() has been called recursively (via SIGINT handler).
225          */
226         if (kb != 0)               /* must have been a recursive call */
227             smg$cancel_input(&kb); /*  from an interrupt handler           */
228         key = getchar();
229     }
230     --recurse;
231 #endif /* USE_QIO_INPUT */
232
233     return (int) key;
234 }
235
236 #ifdef USE_QIO_INPUT
237 /*
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. :-}
245  *
246  * This is needed to preserve compatibility with SMG interface
247  * for two reasons:
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.
253  *
254  * SMG code values for these key sequences fall in the range of
255  * 256 thru 3xx.  The assignments are not particularly intuitive.
256  */
257 /*=
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
261                   7   8   9   -                 w   x   y   m
262                   4   5   6   .                 t   u   v   n
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
269 digits):
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.
276 =*/
277
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). */
282
283 static short
284 parse_function_key(c)
285 register int c;
286 {
287     struct _rd_iosb iosb;
288     unsigned long sts;
289     char seq_buf[15 + 1]; /* plenty room for escape sequence + slop */
290     short result = ESC;   /* translate to <escape> by default */
291
292     /*
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...
299      */
300     if (inc > 0)
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);
306         if (vms_ok(sts))
307             sts = iosb.status;
308     } else
309         sts = SS$_NORMAL;
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))
317                 c = SS3; /*CSI*/
318         if (cnt > 0 && (c == SS3 || (c == CSI && strchr(arrow_or_PF, *p)))) {
319             register char *q = strchr(smg_keypad_codes, *p);
320             if (q)
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] */
325                 F_keys[35] = {
326                     ESC,                          /*(filler)*/
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 */
335             int nn;
336             char *q;
337             *(p + cnt) = '\0'; /* terminate string */
338             q = strchr(p, '~');
339             if (q && sscanf(p, "%d~", &nn) == 1) {
340                 if (nn > 0 && nn < SIZE(F_keys))
341                     result = F_keys[nn];
342                 cnt -= (++q - p);
343                 p = q;
344             }
345         }
346         if (cnt > 0)
347             strncpy((inp = inputbuf), p, (inc = cnt));
348         else
349             inc = 0, inp = 0;
350     }
351     return result;
352 }
353 #endif /* USE_QIO_INPUT */
354
355 static void
356 setctty()
357 {
358     struct _sm_iosb iosb;
359     unsigned long status;
360
361     status = sys$qiow(0, tt_chan, IO$_SETMODE, &iosb, (void (*) ()) 0, 0,
362                       &sg.sm, sizeof sg.sm, 0, 0, 0, 0);
363     if (vms_ok(status))
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);
368     } else {
369         raw_print("");
370         errno = EVMSERR, vaxc$errno = status;
371         perror("NetHack(setctty: setmode)");
372         wait_synch();
373     }
374 }
375
376 static void resettty() /* atexit() routine */
377 {
378     if (settty_needed) {
379         bombing = TRUE; /* don't clear screen; preserve traceback info */
380         settty((char *) 0);
381     }
382     (void) sys$dassgn(tt_chan), tt_chan = 0;
383 }
384
385 /*
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).
390  */
391 void
392 gettty()
393 {
394     static char dev_tty[] = "TT:";
395     static $DESCRIPTOR(tty_dsc, dev_tty);
396     int err = 0;
397     unsigned long status, zero = 0;
398
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)");
406         }
407         atexit(resettty); /* register an exit handler to reset things */
408     }
409     status = sys$qiow(0, tt_chan, IO$_SENSEMODE, &sg.io, (void (*) ()) 0, 0,
410                       &sg.sm, sizeof sg.sm, 0, 0, 0, 0);
411     if (vms_ok(status))
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)");
417     }
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() ]*/
434     setctty();
435 #endif
436
437     if (err)
438         wait_synch();
439 }
440
441 /* reset terminal to original state */
442 void
443 settty(s)
444 const char *s;
445 {
446     if (!bombing)
447         end_screen();
448     if (s)
449         raw_print(s);
450     if (settty_needed) {
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) */
456         if (ctrl_mask)
457             (void) lib$enable_ctrl(&ctrl_mask, 0);
458         iflags.echo = ON;
459         iflags.cbreak = OFF;
460         /* reset original tab, form-feed, broadcast settings */
461         sg.sm.tt_char = tt_char_restore;
462         sg.sm.tt2_char = tt2_char_restore;
463         setctty();
464
465         settty_needed = FALSE;
466     }
467 }
468
469 /* same as settty, with no clearing of the screen */
470 void
471 shuttty(s)
472 const char *s;
473 {
474     bombing = TRUE;
475     settty(s);
476     bombing = FALSE;
477 }
478
479 void
480 setftty()
481 {
482     unsigned long mask = LIB$M_CLI_CTRLT | LIB$M_CLI_CTRLY;
483
484     (void) lib$disable_ctrl(&mask, 0);
485     if (kb == 0) { /* do this stuff once only */
486 #ifdef USE_QIO_INPUT
487         kb = tt_chan;
488 #else  /*!USE_QIO_INPUT*/
489         smg$create_virtual_keyboard(&kb);
490 #endif /*USE_QIO_INPUT*/
491         init_broadcast_trapping();
492     }
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;
499     setctty();
500
501     start_screen();
502     settty_needed = TRUE;
503 }
504
505 void intron() /* enable kbd interupts if enabled when game started */
506 {
507     intr_char = CTRL('C');
508 }
509
510 void introff() /* disable kbd interrupts if required*/
511 {
512     intr_char = 0;
513 }
514
515 #ifdef TIMED_DELAY
516
517 extern unsigned long FDECL(lib$emul, (const long *, const long *,
518                                       const long *, long *));
519 extern unsigned long sys$schdwk(), sys$hiber();
520
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;
524
525 /* sleep for specified number of milliseconds (note: the timer used
526    generally only has 10-millisecond resolution at the hardware level...) */
527 void
528 msleep(mseconds)
529 unsigned mseconds; /* milliseconds */
530 {
531     long pid = 0L, zero = 0L, msec, qtime[2];
532
533     msec = (long) mseconds;
534     if (msec > 0 &&
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)))
539             (void) sys$hiber();
540     }
541 }
542
543 #endif /* TIMED_DELAY */
544
545 /* fatal error */
546 /*VARARGS1*/
547 void error
548 VA_DECL(const char *, s)
549 {
550     VA_START(s);
551     VA_INIT(s, const char *);
552
553     if (settty_needed)
554         settty((char *) 0);
555     Vprintf(s, VA_ARGS);
556     (void) putchar('\n');
557     VA_END();
558 #ifndef SAVE_ON_FATAL_ERROR
559     /* prevent vmsmain's exit handler byebye() from calling hangup() */
560     sethanguphandler((void FDECL((*), (int) )) SIG_DFL);
561 #endif
562     exit(EXIT_FAILURE);
563 }