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