OSDN Git Service

Initial Import
[nethackexpress/trunk.git] / sys / vms / vmstty.c
1 /*      SCCS Id: @(#)vmstty.c   3.4     2003/09/18      */
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 int
106 vms_getchar()
107 {
108     short key;
109 #ifdef USE_QIO_INPUT
110     struct _rd_iosb iosb;
111     unsigned long sts;
112     unsigned char kb_buf;
113 #else   /* SMG input */
114     static volatile int recurse = 0;    /* SMG is not AST re-entrant! */
115 #endif
116
117     if (program_state.done_hup) {
118         /* hangup has occurred; do not attempt to get further user input */
119         return ESC;
120     }
121
122 #ifdef USE_QIO_INPUT
123     if (inc > 0) {
124         /* we have buffered character(s) from previous read */
125         kb_buf = *inp++;
126         --inc;
127         sts = SS$_NORMAL;
128     } else {
129         sts = sys$qiow(0, tt_chan, QIO_FUNC, &iosb, (void(*)())0, 0,
130                        &kb_buf, sizeof kb_buf, 0, 0, 0, 0);
131     }
132     if (vms_ok(sts)) {
133         if (kb_buf == CTRL('C')) {
134             if (intr_char) gsignal(SIGINT);
135             key = (short)kb_buf;
136         } else if (kb_buf == '\r') {    /* <return> */
137             key = (short)'\n';
138         } else if (kb_buf == ESC || kb_buf == CSI || kb_buf == SS3) {
139             switch(parse_function_key((int)kb_buf)) {
140               case SMG$K_TRM_UP:    key = iflags.num_pad ? '8' : 'k';  break;
141               case SMG$K_TRM_DOWN:  key = iflags.num_pad ? '2' : 'j';  break;
142               case SMG$K_TRM_LEFT:  key = iflags.num_pad ? '4' : 'h';  break;
143               case SMG$K_TRM_RIGHT: key = iflags.num_pad ? '6' : 'l';  break;
144               default:              key = ESC;  break;
145             }
146         } else {
147             key = (short)kb_buf;
148         }
149     } else if (sts == SS$_HANGUP || iosb.status == SS$_HANGUP
150             || sts == SS$_DEVOFFLINE) {
151         gsignal(SIGHUP);
152         key = ESC;
153     } else                      /*(this should never happen)*/
154         key = getchar();
155
156 #else   /*!USE_QIO_INPUT*/
157     if (recurse++ == 0 && kb != 0) {
158         smg$read_keystroke(&kb, &key);
159         switch (key) {
160           case SMG$K_TRM_UP:    iflags.num_pad ? '8' : key = 'k';  break;
161           case SMG$K_TRM_DOWN:  iflags.num_pad ? '2' : key = 'j';  break;
162           case SMG$K_TRM_LEFT:  iflags.num_pad ? '4' : key = 'h';  break;
163           case SMG$K_TRM_RIGHT: iflags.num_pad ? '6' : key = 'l';  break;
164           case '\r':            key = '\n'; break;
165           default:              if (key > 255)  key = ESC;
166                                 break;
167         }
168     } else {
169         /* abnormal input--either SMG didn't initialize properly or
170            vms_getchar() has been called recursively (via SIGINT handler).
171          */
172         if (kb != 0)                    /* must have been a recursive call */
173             smg$cancel_input(&kb);      /*  from an interrupt handler      */
174         key = getchar();
175     }
176     --recurse;
177 #endif  /* USE_QIO_INPUT */
178
179     return (int)key;
180 }
181
182 #ifdef USE_QIO_INPUT
183        /*
184         * We've just gotten an <escape> character.  Do a timed read to
185         * get any other characters, then try to parse them as an escape
186         * sequence.  This isn't perfect, since there's no guarantee
187         * that a full escape sequence will be available, or even if one
188         * is, it might actually by regular input from a fast typist or
189         * a stalled input connection.  {For packetized environments,
190         * cross plural(body_part(FINGER)) and hope for best. :-}
191         *
192         * This is needed to preserve compatability with SMG interface
193         * for two reasons:
194         *    1) retain support for arrow keys, and
195         *    2) treat other VTxxx function keys as <esc> for aborting
196         *       various NetHack prompts.
197         * The second reason is compelling; otherwise remaining chars of
198         * an escape sequence get treated as inappropriate user commands.
199         *
200         * SMG code values for these key sequences fall in the range of
201         * 256 thru 3xx.  The assignments are not particularly intuitive.
202         */
203 /*=
204      -- Summary of VTxxx-style keyboards and transmitted escape sequences. --
205 Keypad codes are prefixed by 7 bit (\033 O) or 8 bit SS3:
206         keypad:  PF1 PF2 PF3 PF4       codes:   P   Q   R   S
207                   7   8   9   -                 w   x   y   m
208                   4   5   6   .                 t   u   v   n
209                   1   2   3  :en-:              q   r   s  : :
210                  ...0...  ,  :ter:             ...p...  l  :M:
211 Arrows are prefixed by either SS3 or CSI (either 7 or 8 bit), depending on
212 whether the terminal is in application or numeric mode (ditto for PF keys):
213         arrows: <up> <dwn> <lft> <rgt>          A   B   D   C
214 Additional function keys (vk201/vk401) generate CSI nn ~ (nn is 1 or 2 digits):
215     vk201 keys:  F6 F7 F8 F9 F10   F11 F12 F13 F14  Help Do   F17 F18 F19 F20
216    'nn' digits:  17 18 19 20 21    23  24  25  26    28  29   31  32  33  34
217      alternate:  ^C                ^[  ^H  ^J           (when in VT100 mode)
218    edit keypad: <fnd> <ins> <rmv>     digits:   1   2   3
219                 <sel> <prv> <nxt>               4   5   6
220 VT52 mode:  arrows and PF keys send ESCx where x is in A-D or P-S.
221 =*/
222
223 static const char *arrow_or_PF = "ABCDPQRS",    /* suffix char */
224                   *smg_keypad_codes = "PQRSpqrstuvwxyMmlnABDC";
225         /* PF1..PF4,KP0..KP9,enter,dash,comma,dot,up-arrow,down,left,right */
226         /* Ultimate return value is (index into smg_keypad_codes[] + 256). */
227
228 static short
229 parse_function_key(c)
230 register int c;
231 {
232     struct _rd_iosb iosb;
233     unsigned long sts;
234     char seq_buf[15+1];         /* plenty room for escape sequence + slop */
235     short result = ESC;         /* translate to <escape> by default */
236
237     /*
238      * Read whatever we can from type-ahead buffer (1 second timeout).
239      * If the user typed an actual <escape> to deliberately abort
240      * something, he or she should be able to tolerate the necessary
241      * restriction of a negligible pause before typing anything else.
242      * We might already have [at least some of] an escape sequence from a
243      * previous read, particularly if user holds down the arrow keys...
244      */
245     if (inc > 0) strncpy(seq_buf, inp, inc);
246     if (inc < (int)(sizeof seq_buf) - 1) {
247         sts = sys$qiow(0, tt_chan, QIO_FUNC|IO$M_TIMED, &iosb, (void(*)())0, 0,
248                        seq_buf + inc, sizeof seq_buf - 1 - inc, 1, 0, 0, 0);
249         if (vms_ok(sts))  sts = iosb.status;
250     } else
251         sts = SS$_NORMAL;
252     if (vms_ok(sts) || sts == SS$_TIMEOUT) {
253         register int cnt = iosb.trm_offset + iosb.trm_siz + inc;
254         register char *p = seq_buf;
255         if (c == ESC)   /* check for 7-bit vt100/ANSI, or vt52 */
256             if (*p == '[' || *p == 'O') c = META(CTRL(*p++)),  cnt--;
257             else if (strchr(arrow_or_PF, *p)) c = SS3; /*CSI*/
258         if (cnt > 0 && (c == SS3 || (c == CSI && strchr(arrow_or_PF, *p)))) {
259             register char *q = strchr(smg_keypad_codes, *p);
260             if (q) result = 256 + (q - smg_keypad_codes);
261             p++,  --cnt;        /* one more char consumed */
262         } else if (cnt > 1 && c == CSI) {
263             static short        /* "CSI nn ~" -> F_keys[nn] */
264                 F_keys[35] = {  ESC,                            /*(filler)*/
265                                 311, 312, 313, 314, 315, 316,   /* E1-E6 */
266                                 ESC, ESC, ESC, ESC,        /*(more filler)*/
267                                 281, 282, 283, 284, 285, ESC,   /* F1-F5 */
268                                 286, 287, 288, 289, 290, ESC,   /* F6-F10*/
269                                 291, 292, 293, 294, ESC,        /*F11-F14*/
270                                 295, 296, ESC, /*<help>,<do>, aka F15,F16*/
271                                 297, 298, 299, 300              /*F17-F20*/
272                 };  /* note: there are several missing nn in CSI nn ~ values */
273             int nn;  char *q;
274             *(p + cnt) = '\0';  /* terminate string */
275             q = strchr(p, '~');
276             if (q && sscanf(p, "%d~", &nn) == 1) {
277                 if (nn > 0 && nn < SIZE(F_keys)) result = F_keys[nn];
278                 cnt -= (++q - p);
279                 p = q;
280             }
281         }
282         if (cnt > 0) strncpy((inp = inputbuf), p, (inc = cnt));
283         else         inc = 0,  inp = 0;
284     }
285     return result;
286 }
287 #endif  /* USE_QIO_INPUT */
288
289 static void
290 setctty()
291 {
292     struct _sm_iosb iosb;
293     unsigned long status;
294
295     status = sys$qiow(0, tt_chan, IO$_SETMODE, &iosb, (void(*)())0, 0,
296                       &sg.sm, sizeof sg.sm, 0, 0, 0, 0);
297     if (vms_ok(status))  status = iosb.status;
298     if (vms_ok(status)) {
299         /* try to force terminal into synch with TTDRIVER's setting */
300         number_pad((sg.sm.tt2_char & TT2$M_APP_KEYPAD) ? -1 : 1);
301     } else {
302         raw_print("");
303         errno = EVMSERR,  vaxc$errno = status;
304         perror("NetHack(setctty: setmode)");
305         wait_synch();
306     }
307 }
308
309 static void
310 resettty()                      /* atexit() routine */
311 {
312     if (settty_needed) {
313         bombing = TRUE;     /* don't clear screen; preserve traceback info */
314         settty((char *)0);
315     }
316     (void) sys$dassgn(tt_chan),  tt_chan = 0;
317 }
318
319 /*
320  * Get initial state of terminal, set ospeed (for termcap routines)
321  * and switch off tab expansion if necessary.
322  * Called by init_nhwindows() and resume_nhwindows() in wintty.c
323  * (for initial startup and for returning from '!' or ^Z).
324  */
325 void
326 gettty()
327 {
328     static char dev_tty[] = "TT:";
329     static $DESCRIPTOR(tty_dsc, dev_tty);
330     int err = 0;
331     unsigned long status, zero = 0;
332
333     if (tt_chan == 0) {         /* do this stuff once only */
334         iflags.cbreak = OFF,  iflags.echo = ON; /* until setup is complete */
335         status = sys$assign(&tty_dsc, &tt_chan, 0, 0);
336         if (!vms_ok(status)) {
337             raw_print(""),  err++;
338             errno = EVMSERR,  vaxc$errno = status;
339             perror("NetHack(gettty: $assign)");
340         }
341         atexit(resettty);   /* register an exit handler to reset things */
342     }
343     status = sys$qiow(0, tt_chan, IO$_SENSEMODE, &sg.io, (void(*)())0, 0,
344                       &sg.sm, sizeof sg.sm, 0, 0, 0, 0);
345     if (vms_ok(status))  status = sg.io.status;
346     if (!vms_ok(status)) {
347         raw_print(""),  err++;
348         errno = EVMSERR,  vaxc$errno = status;
349         perror("NetHack(gettty: sensemode)");
350     }
351     ospeed = sg.io.xmt_speed;
352     erase_char = '\177';        /* <rubout>, aka <delete> */
353     kill_char = CTRL('U');
354     intr_char = CTRL('C');
355     (void) lib$enable_ctrl(&zero, &ctrl_mask);
356     /* Use the systems's values for lines and columns if it has any idea. */
357     if (sg.sm.page_length)
358         LI = sg.sm.page_length;
359     if (sg.sm.page_width)
360         CO = sg.sm.page_width;
361     /* suppress tab and form-feed expansion, in case termcap uses them */
362     tt_char_restore  = sg.sm.tt_char;
363     tt_char_active   = sg.sm.tt_char |= TT_SPECIAL_HANDLING;
364     tt2_char_restore = sg.sm.tt2_char;
365     tt2_char_active  = sg.sm.tt2_char |= TT2_SPECIAL_HANDLING;
366 #if 0           /*[ defer until setftty() ]*/
367     setctty();
368 #endif
369
370     if (err) wait_synch();
371 }
372
373 /* reset terminal to original state */
374 void
375 settty(s)
376 const char *s;
377 {
378         if (!bombing) end_screen();
379         if (s) raw_print(s);
380         disable_broadcast_trapping();
381 #if 0           /* let SMG's exit handler do the cleanup (as per doc) */
382 /* #ifndef USE_QIO_INPUT */
383         if (kb)  smg$delete_virtual_keyboard(&kb),  kb = 0;
384 #endif  /* 0 (!USE_QIO_INPUT) */
385         if (ctrl_mask)
386             (void) lib$enable_ctrl(&ctrl_mask, 0);
387         iflags.echo = ON;
388         iflags.cbreak = OFF;
389         /* reset original tab, form-feed, broadcast settings */
390         sg.sm.tt_char  = tt_char_restore;
391         sg.sm.tt2_char = tt2_char_restore;
392         setctty();
393
394         settty_needed = FALSE;
395 }
396
397 /* same as settty, with no clearing of the screen */
398 void
399 shuttty(s)
400 const char *s;
401 {
402         bombing = TRUE;
403         settty(s);
404         bombing = FALSE;
405 }
406
407 void
408 setftty()
409 {
410         unsigned long mask = LIB$M_CLI_CTRLT | LIB$M_CLI_CTRLY;
411
412         (void) lib$disable_ctrl(&mask, 0);
413         if (kb == 0) {          /* do this stuff once only */
414 #ifdef USE_QIO_INPUT
415             kb = tt_chan;
416 #else   /*!USE_QIO_INPUT*/
417             smg$create_virtual_keyboard(&kb);
418 #endif  /*USE_QIO_INPUT*/
419             init_broadcast_trapping();
420         }
421         enable_broadcast_trapping();    /* no-op if !defined(MAIL) */
422         iflags.cbreak = (kb != 0) ? ON : OFF;
423         iflags.echo   = (kb != 0) ? OFF : ON;
424         /* disable tab & form-feed expansion; prepare for broadcast trapping */
425         sg.sm.tt_char  = tt_char_active;
426         sg.sm.tt2_char = tt2_char_active;
427         setctty();
428
429         start_screen();
430         settty_needed = TRUE;
431 }
432
433 void
434 intron()                /* enable kbd interupts if enabled when game started */
435 {
436         intr_char = CTRL('C');
437 }
438
439 void
440 introff()               /* disable kbd interrupts if required*/
441 {
442         intr_char = 0;
443 }
444
445 #ifdef TIMED_DELAY
446
447 extern unsigned long
448         FDECL(lib$emul, (const long *,const long *,const long *,long *));
449 extern unsigned long sys$schdwk(), sys$hiber();
450
451 #define VMS_UNITS_PER_SECOND 10000000L  /* hundreds of nanoseconds, 1e-7 */
452 /* constant for conversion from milliseconds to VMS delta time (negative) */
453 static const long mseconds_to_delta = VMS_UNITS_PER_SECOND / 1000L * -1L;
454
455 /* sleep for specified number of milliseconds (note: the timer used
456    generally only has 10-millisecond resolution at the hardware level...) */
457 void msleep(mseconds)
458 unsigned mseconds;      /* milliseconds */
459 {
460     long pid = 0L, zero = 0L, msec, qtime[2];
461
462     msec = (long) mseconds;
463     if (msec > 0 &&
464         /* qtime{0:63} = msec{0:31} * mseconds_to_delta{0:31} + zero{0:31} */
465         vms_ok(lib$emul(&msec, &mseconds_to_delta, &zero, qtime))) {
466         /* schedule a wake-up call, then go to sleep */
467         if (vms_ok(sys$schdwk(&pid, (genericptr_t)0, qtime, (long *)0)))
468             (void)sys$hiber();
469     }
470 }
471
472 #endif  /* TIMED_DELAY */
473
474
475 /* fatal error */
476 /*VARARGS1*/
477 void
478 error VA_DECL(const char *,s)
479         VA_START(s);
480         VA_INIT(s, const char *);
481         if(settty_needed)
482                 settty((char *)0);
483         Vprintf(s,VA_ARGS);
484         (void) putchar('\n');
485         VA_END();
486 #ifndef SAVE_ON_FATAL_ERROR
487         /* prevent vmsmain's exit handler byebye() from calling hangup() */
488         (void)signal(SIGHUP, SIG_DFL);
489 #endif
490         exit(EXIT_FAILURE);
491 }