1 /*
2  * ux_input.c - Unix interface, input functions
3  *
4  * This file is part of Frotz.
5  *
6  * Frotz is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Frotz is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  * Or visit http://www.fsf.org/
20  */
21 
22 
23 #define __UNIX_PORT_FILE
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <limits.h>
29 #include <libgen.h>
30 #include <ctype.h>
31 
32 #include <errno.h>
33 #include <sys/time.h>
34 #include <sys/select.h>
35 
36 #include "ux_defines.h"
37 
38 #ifdef USE_NCURSES_H
39 #include <ncurses.h>
40 #else
41 #include <curses.h>
42 #endif
43 
44 #include "ux_frotz.h"
45 
46 #ifdef USE_UTF8
47 #include <wctype.h>
48 #endif
49 
50 #ifndef NO_SOUND
51 #include "ux_sema.h"
52 extern ux_sem_t sound_done;
53 #endif
54 
55 static int start_of_prev_word(int, const zchar*);
56 static int end_of_next_word(int, const zchar*, int);
57 
58 static struct timeval global_timeout;
59 
60 /* Some special characters. */
61 #define MOD_CTRL 0x40
62 #define MOD_META 0x80
63 #define CHR_DEL (MOD_CTRL ^'?')
64 
65 /* These are useful for circular buffers.
66  */
67 #define RING_DEC( ptr, beg, end) (ptr > (beg) ? --ptr : (ptr = (end)))
68 #define RING_INC( ptr, beg, end) (ptr < (end) ? ++ptr : (ptr = (beg)))
69 
70 #define MAX_HISTORY 20
71 static zchar *history_buffer[MAX_HISTORY];
72 static zchar **history_next = history_buffer; /* Next available slot. */
73 static zchar **history_view = history_buffer; /* What the user is looking at. */
74 #define history_end (history_buffer + MAX_HISTORY - 1)
75 
76 extern bool is_terminator (zchar);
77 extern void read_string (int, zchar *);
78 extern int completion (const zchar *, zchar *);
79 
80 
81 /*
82  * unix_set_global_timeout
83  *
84  * This sets up a time structure to determine when unix_read_char should
85  * return zero (representing input timeout).  When current system time
86  * equals global_timeout, boom.
87  *
88  */
unix_set_global_timeout(int timeout)89 static void unix_set_global_timeout(int timeout)
90 {
91 	if (!timeout)
92 		global_timeout.tv_sec = 0;
93 	else {
94 		gettimeofday(&global_timeout, NULL);
95 		global_timeout.tv_sec += (timeout/10);
96 		global_timeout.tv_usec += ((timeout%10)*100000);
97 		if (global_timeout.tv_usec > 999999) {
98 			global_timeout.tv_sec++;
99 			global_timeout.tv_usec -= 1000000;
100 		}
101 	}
102 	return;
103 } /* unix_set_global_timeout */
104 
105 
106 /*
107  * Time left until input timeout.  Return whether an input timeout
108  * is in effect and it it is, set diff to the time left until the
109  * timeout elapses or zero if it has already elapsed.  If false is returned,
110  * diff is not modified, otherwise it is set to a non-negative value.
111  */
timeout_left(struct timeval * diff)112 static bool timeout_left(struct timeval *diff)
113 {
114 	struct timeval now;
115 
116 	if (global_timeout.tv_sec == 0)
117 		return false;
118 	gettimeofday( &now, NULL);
119 	diff->tv_usec = global_timeout.tv_usec - now.tv_usec;
120 	if (diff->tv_usec < 0) { /* Carry */
121 		now.tv_sec++;
122 		diff->tv_usec += 1000000;
123 	}
124 	diff->tv_sec = global_timeout.tv_sec - now.tv_sec;
125 	if (diff->tv_sec < 0)
126 		diff->tv_sec = diff->tv_usec = 0;
127 	return true;
128 } /* timeout_left */
129 
130 
131 /*
132  * os_tick
133  *
134  * This is something of a catch-all for things that need to be done
135  * perodically.  Currently it turn off audio when a sound is finished.
136  * It will eventually rewrite the output when the terminal is resized.
137  */
os_tick()138 void os_tick()
139 {
140 	while (terminal_resized) {
141 		terminal_resized = 0;
142 		unix_resize_display();
143 	}
144 
145 #ifndef NO_SOUND
146 
147 	if (f_setup.sound) {
148 		if (ux_sem_trywait(&sound_done) == 0)
149 			end_of_sound();
150 	}
151 #endif
152 } /* os_tick */
153 
154 
155 /*
156  * unix_read_char
157  *
158  * This uses the curses getch() routine to get the next character
159  * typed, and returns it.  It returns values which the standard
160  * considers to be legal input, and also returns editing and frotz hot
161  * keys.  If called with extkeys set it will also return line-editing
162  * keys like INSERT etc.
163  *
164  * If unix_set_global_timeout has been used to set a global timeout
165  * this routine may also return ZC_TIME_OUT if input times out.
166  */
unix_read_char(int extkeys)167 static int unix_read_char(int extkeys)
168 {
169 #ifdef USE_UTF8
170 	wint_t c;
171 #else
172 	int c;
173 #endif
174 	int sel, fd = fileno(stdin);
175 	fd_set rsel;
176 	struct timeval tval, *t_left, maxwait;
177 
178 
179 	while(1) {
180 		/* Wait with select so that we get interrupted on SIGWINCH. */
181         	FD_ZERO(&rsel);
182 		FD_SET(fd, &rsel);
183 		os_tick();
184 		refresh();
185 
186 		/*
187 		 * If the timeout is 0, we still want to call os_tick
188 		 * periodically.
189 		 *
190 		 * Based on experimentation, 10 msec seems to strike a balance
191 		 * between cpu usage and not having pauses between sounds
192 		 */
193 		maxwait.tv_sec=0;
194 		maxwait.tv_usec=10000;
195 
196 		t_left = timeout_left(&tval) ? &tval : NULL;
197 		/*
198 		 * if the timeout is zero, we wait forever for input, but if
199 		 * we are playing a sequence of sounds, we need to periodically
200 		 * call os_tick().  So if the timeout is zero, wait up to
201 		 * maxwait for input, but if we get no input continue the
202 		 * while loop.
203 		 */
204 		if (t_left)
205 			sel = select(fd + 1, &rsel, NULL, NULL, t_left);
206 		else
207 			sel = select(fd + 1, &rsel, NULL, NULL, &maxwait);
208 
209 		if (terminal_resized)
210 			continue;
211 		switch (sel) {
212 		case -1:
213 			if (errno != EINTR)
214 				os_fatal(strerror(errno));
215 			continue;
216 		case 0:
217 			if (t_left == NULL)
218 				/*
219 				 * The timeout was 0 (wait forever)
220 				 * but we need to call  os_tick to handle
221 				 * sound sequences
222 				 */
223 				continue;
224             		return ZC_TIME_OUT;
225         	}
226 
227 		timeout(0);
228 #ifdef USE_UTF8
229 		sel = get_wch(&c);
230 #else
231 		c = getch();
232 #endif
233 
234 		/* Catch 98% of all input right here... */
235 		if ((c >= ZC_ASCII_MIN && c <= ZC_ASCII_MAX) || (!u_setup.plain_ascii
236 			&& c >= ZC_LATIN1_MIN && c <= ZC_LATIN1_MAX))
237 			return c;
238 
239 		/* ...and the other 2% makes up 98% of the code. :( */
240 #ifdef USE_UTF8
241 		if (sel != KEY_CODE_YES && c >= ZC_LATIN1_MIN) {
242 			if (c > 0xffff) continue;
243 			return c;
244 		}
245 #endif
246 
247 		/* On many terminals the backspace key returns DEL. */
248 #ifdef USE_UTF8
249 		if (c == (wint_t)erasechar()) return ZC_BACKSPACE;;
250 #else
251 		if (c == (int)erasechar()) return ZC_BACKSPACE;;
252 #endif
253 		switch(c) {
254 		/* This should not happen because select said we have input. */
255 		case ERR:
256 		/* Ignore NUL (ctrl+space or ctrl+@ on many terminals)
257 		 * because it would be misinterpreted as timeout
258 		 * (ZC_TIME_OUT == 0).
259 		 */
260 		case 0:
261 		/* Ncurses appears to produce KEY_RESIZE even if we handle
262 		 * SIGWINCH ourselves.
263 		 */
264 #ifdef KEY_RESIZE
265 	case KEY_RESIZE:
266 #endif
267 		continue;
268 
269 		/* Screen decluttering. */
270 		case MOD_CTRL ^ 'L':
271 		case MOD_CTRL ^ 'R':
272 			clearok(curscr, 1);
273 			refresh();
274 			clearok(curscr, 0);
275 			continue;
276 		/* Lucian P. Smith reports KEY_ENTER on Irix 5.3.  LF has never
277 		 * been reported, but I'm leaving it in just in case.
278 		 */
279 		case '\n':
280 		case '\r':
281 		case KEY_ENTER:
282 		return ZC_RETURN;
283 		/* I've seen KEY_BACKSPACE returned on some terminals. */
284 		case KEY_BACKSPACE:
285 		case '\b':
286 			return ZC_BACKSPACE;
287 		/* On terminals with 8-bit character sets or 7-bit connections
288 		 * "Alt-Foo" may be returned as an escape followed by the ASCII
289 		 * value of the letter.  We have to decide here whether to
290 		 * return a single escape or a frotz hot key.
291 		 */
292 		case ZC_ESCAPE:
293 #ifdef USE_UTF8
294 		nodelay(stdscr, TRUE);
295 		if (get_wch(&c) == ERR){
296 			c = ERR;
297 		}
298 		nodelay(stdscr, FALSE);
299 #else
300 		nodelay(stdscr, TRUE); c = getch(); nodelay(stdscr, FALSE);
301 #endif
302 			switch(c) {
303 			case ERR: return ZC_ESCAPE;
304 			case 'p': return ZC_HKEY_PLAYBACK;
305 			case 'r': return ZC_HKEY_RECORD;
306 			case 's': return ZC_HKEY_SEED;
307 			case 'u': return ZC_HKEY_UNDO;
308 			case 'n': return ZC_HKEY_RESTART;
309 			case 'x': return ZC_HKEY_QUIT;
310 			case 'd': return ZC_HKEY_DEBUG;
311 			case 'h': return ZC_HKEY_HELP;
312 			case 'f': return ZC_WORD_RIGHT;
313 			case 'b': return ZC_WORD_LEFT;
314 			default: continue;	/* Ignore unknown combinations. */
315 			}
316 		/* The standard function key block. */
317 		case KEY_UP: return ZC_ARROW_UP;
318 		case KEY_DOWN: return ZC_ARROW_DOWN;
319 		case KEY_LEFT: return ZC_ARROW_LEFT;
320 		case KEY_RIGHT: return ZC_ARROW_RIGHT;
321 		case KEY_F(1): return ZC_FKEY_MIN;
322 		case KEY_F(2): return ZC_FKEY_MIN + 1;
323 		case KEY_F(3): return ZC_FKEY_MIN + 2;
324 		case KEY_F(4): return ZC_FKEY_MIN + 3;
325 		case KEY_F(5): return ZC_FKEY_MIN + 4;
326 		case KEY_F(6): return ZC_FKEY_MIN + 5;
327 		case KEY_F(7): return ZC_FKEY_MIN + 6;
328 		case KEY_F(8): return ZC_FKEY_MIN + 7;
329 		case KEY_F(9): return ZC_FKEY_MIN + 8;
330 		case KEY_F(10): return ZC_FKEY_MIN + 9;
331 		case KEY_F(11): return ZC_FKEY_MIN + 10;
332 		case KEY_F(12): return ZC_FKEY_MIN + 11;
333 		/* Curses can't differentiate keypad numbers from cursor keys,
334 		 * which is annoying, as cursor and keypad keys have
335 		 * nothing to do with each other on, say, a vt200.
336 		 * So we can only read 1, 3, 5, 7 and 9 from the keypad.  This
337 		 * would be so silly that we choose not to provide keypad
338 		 * keys at all.
339 		 */
340 
341 		/* Catch the meta key on those plain old ASCII terminals where
342 		 * it sets the high bit.  This only works in
343 		 * u_setup.plain_ascii mode: otherwise these character codes
344 		 * would have been interpreted according to ISO-Latin-1
345 		 * earlier.
346 		 */
347 		case MOD_META | 'p': return ZC_HKEY_PLAYBACK;
348 		case MOD_META | 'r': return ZC_HKEY_RECORD;
349 		case MOD_META | 's': return ZC_HKEY_SEED;
350 		case MOD_META | 'u': return ZC_HKEY_UNDO;
351 		case MOD_META | 'n': return ZC_HKEY_RESTART;
352 		case MOD_META | 'x': return ZC_HKEY_QUIT;
353 		case MOD_META | 'd': return ZC_HKEY_DEBUG;
354 		case MOD_META | 'h': return ZC_HKEY_HELP;
355 		case MOD_META | 'f': return ZC_WORD_RIGHT;
356 		case MOD_META | 'b': return ZC_WORD_LEFT;
357 
358 		/* these are the UNIX line-editing characters */
359 		case MOD_CTRL ^ 'B': return ZC_ARROW_LEFT;
360 		/* use ^C to clear line anywhere it doesn't send SIGINT */
361 		case MOD_CTRL ^ 'C': return ZC_ESCAPE;
362 		case MOD_CTRL ^ 'F': return ZC_ARROW_RIGHT;
363 		case MOD_CTRL ^ 'P': return ZC_ARROW_UP;
364 		case MOD_CTRL ^ 'N': return ZC_ARROW_DOWN;
365 
366 		case MOD_CTRL ^ 'A': c = KEY_HOME; break;
367 		case MOD_CTRL ^ 'E': c = KEY_END; break;
368 		case MOD_CTRL ^ 'D': c = KEY_DC; break;
369 		case MOD_CTRL ^ 'K': c = KEY_EOL; break;
370 		case MOD_CTRL ^ 'U': c = ZC_DEL_TO_BOL; break;
371 		case MOD_CTRL ^ 'W': c = ZC_DEL_WORD; break;
372 
373 		/* In raw mode we need to take care of this as well. */
374 		case MOD_CTRL ^ 'Z':
375 			unix_suspend_program();
376 			continue;
377 
378 		/* use ^Q to immediately exit. */
379 		case MOD_CTRL ^ 'Q': os_quit(EXIT_SUCCESS);
380 
381 		default: break; /* Who knows? */
382 		}
383 
384 		/* Control-N through Control-U happen to map to the frotz hot
385 		 * key codes, but not in any mnemonic manner.  It annoys an
386 		 * emacs user (or this one anyway) when he tries out of habit
387 		 * to use one of the emacs keys that isn't implemented and he
388 		 * gets a random hot key function.  It's less jarring to catch
389 		 * them and do nothing.  [APP]
390 		 */
391 		if ((c >= ZC_HKEY_MIN) && (c <= ZC_HKEY_MAX))
392 			continue;
393 
394 		/* Finally, if we're in full line mode (os_read_line), we
395 		 * might return codes which aren't legal Z-machine keys but
396 		 * are used by the editor.
397 		 */
398 		if (extkeys) return c;
399 	}
400 } /* unix_read_char */
401 
402 
403 
404 /*
405  * zcharstrlen
406  *
407  * A version of strlen() for dealing with ZSCII strings
408  */
zcharstrlen(zchar * str)409 size_t zcharstrlen(zchar *str)
410 {
411 	size_t ret = 0;
412 
413 	while (str[ret] != 0)
414 		ret++;
415 	return ret;
416 } /* zcharstrlen */
417 
418 
419 /* zcharstrncpy
420  *
421  * A version of strncpy() for dealing with ZSCII strings
422  */
zcharstrncpy(zchar * dest,zchar * src,size_t n)423 zchar *zcharstrncpy(zchar *dest, zchar *src, size_t n)
424 {
425 	size_t i;
426 
427 	for (i = 0; i < n && src[i] != '\0'; i++)
428 		dest[i] = src[i];
429 	for ( ; i < n; i++)
430 		dest[i] = 0;
431 
432 	return dest;
433 } /* zcharstrncpy */
434 
435 
436 /* zcharstrncmp
437  *
438  * A version of strncmp() for dealing with ZSCII strings
439  */
zcharstrncmp(zchar * s1,zchar * s2,size_t n)440 int zcharstrncmp(zchar *s1, zchar *s2, size_t n)
441 {
442 	zchar u1, u2;
443 	while (n-- > 0) {
444 		u1 = *s1++;
445 		u2 = *s2++;
446 		if (u1 != u2)
447 			return u1 - u2;
448 		if (u1 == 0)
449 			return 0;
450 	}
451 	return 0;
452 } /* zcharstrncmp */
453 
454 
455 /*
456  * unix_add_to_history
457  *
458  * Add the given string to the next available history buffer slot.
459  *
460  */
unix_add_to_history(zchar * str)461 static void unix_add_to_history(zchar *str)
462 {
463 
464 	if (*history_next != NULL)
465 		free( *history_next);
466 	*history_next = (zchar *)malloc((zcharstrlen(str) + 1) * sizeof(zchar));
467 	zcharstrncpy( *history_next, str, zcharstrlen(str) + 1);
468 	RING_INC( history_next, history_buffer, history_end);
469 	history_view = history_next; /* Reset user frame after each line */
470 
471 	return;
472 } /* unix_add_to_history */
473 
474 
475 /*
476  * unix_history_back
477  *
478  * Copy last available string to str, if possible.  Return 1 if successful.
479  * Only lines of at most maxlen characters will be considered.  In addition
480  * the first searchlen characters of the history entry must match those of str.
481  */
unix_history_back(zchar * str,int searchlen,int maxlen)482 static int unix_history_back(zchar *str, int searchlen, int maxlen)
483 {
484 	zchar **prev = history_view;
485 
486 	do {
487 		RING_DEC( history_view, history_buffer, history_end);
488 		if ((history_view == history_next) || (*history_view == NULL)) {
489 			os_beep(BEEP_HIGH);
490 			history_view = prev;
491 			return 0;
492 		}
493 	} while (zcharstrlen( *history_view) > (size_t) maxlen
494 	    || (searchlen != 0 && zcharstrncmp(str, *history_view, searchlen)));
495 	zcharstrncpy(str + searchlen, *history_view + searchlen,
496 		(size_t) maxlen - (zcharstrlen(str) + searchlen));
497 	return 1;
498 } /* unix_history_back */
499 
500 
501 /*
502  * unix_history_forward
503  *
504  * Opposite of unix_history_back, and works in the same way.
505  */
unix_history_forward(zchar * str,int searchlen,int maxlen)506 static int unix_history_forward(zchar *str, int searchlen, int maxlen)
507 {
508 	zchar **prev = history_view;
509 
510 	do {
511 		RING_INC( history_view, history_buffer, history_end);
512 		if ((history_view == history_next) || (*history_view == NULL)) {
513 			os_beep(BEEP_HIGH);
514 			history_view = prev;
515 			return 0;
516 		}
517 	} while (zcharstrlen( *history_view) > (size_t) maxlen
518 	    || (searchlen != 0 && zcharstrncmp(str, *history_view, searchlen)));
519 	zcharstrncpy(str + searchlen, *history_view + searchlen,
520 		(size_t) maxlen - (zcharstrlen(str) + searchlen));
521 	return 1;
522 } /* unix_history_forward */
523 
524 
525 /*
526  * scrnmove
527  *
528  * In the row of the cursor, move n characters starting at src to dest.
529  *
530  */
scrnmove(int dest,int src,int n)531 static void scrnmove(int dest, int src, int n)
532 {
533 	int col, x, y;
534 
535 	getyx(stdscr, y, x);
536 	if (src > dest) {
537 		for (col = src; col < src + n; col++) {
538 			chtype ch = mvinch(y, col);
539 			mvaddch(y, col - src + dest, ch);
540 		}
541 	} else if (src < dest) {
542 		for (col = src + n - 1; col >= src; col--) {
543 			chtype ch = mvinch(y, col);
544 			mvaddch(y, col - src + dest, ch);
545 		}
546 	}
547 	move(y, x);
548 	return;
549 } /* scrnmove */
550 
551 
552 /*
553  * scrnset
554  *
555  * In the row of the cursor, set n characters starting at start to c.
556  *
557  */
scrnset(int start,int c,int n)558 static void scrnset(int start, int c, int n)
559 {
560 	int y, x;
561 	getyx(stdscr, y, x);
562 	while (n--)
563 		mvaddch(y, start + n, c);
564 	move(y, x);
565 	return;
566 } /* scrnset */
567 
568 
569 #ifdef USE_UTF8
570 /*
571  * utf8_mvaddnstr
572  *
573  * Like mvaddnstr except modified to deal with UTF-8 and ZSCII
574  *
575  */
utf8_mvaddnstr(int y,int x,zchar * buf,int n)576 static void utf8_mvaddnstr(int y, int x, zchar * buf, int n)
577 {
578 	zchar *bp = buf;
579 
580 	move(y,x);
581 	while(*bp && (n > 0)) {
582 		if(*bp < ZC_LATIN1_MIN) {
583 			addch(*bp);
584 		} else {
585 			if(*bp > 0x7ff) {
586 				addch(0xe0 | ((*bp >> 12) & 0xf));
587 				addch(0x80 | ((*bp >> 6) & 0x3f));
588 				addch(0x80 | (*bp & 0x3f));
589 			} else {
590 				addch(0xc0 | ((*bp >> 6) & 0x1f));
591 				addch(0x80 | (*bp & 0x3f));
592 			}
593 		}
594 		bp++;
595 		n--;
596 	}
597 } /* utf8_mvaddnstr */
598 
599 
600 /*
601  * utf8_mvaddstr
602  *
603  * Like mvaddstr except modified to deal with UTF-8 and ZSCII
604  *
605  */
utf8_mvaddstr(int y,int x,zchar * buf)606 static void utf8_mvaddstr(int y, int x, zchar * buf)
607 {
608 	utf8_mvaddnstr(y,x,buf,zcharstrlen(buf));
609 } /* utf8_mvaddstr */
610 #endif /* USE_UTF8 */
611 
612 
613 /*
614  * os_read_line
615  *
616  * Read a line of input from the keyboard into a buffer. The buffer
617  * may already be primed with some text. In this case, the "initial"
618  * text is already displayed on the screen. After the input action
619  * is complete, the function returns with the terminating key value.
620  * The length of the input should not exceed "max" characters plus
621  * an extra 0 terminator.
622  *
623  * Terminating keys are the return key (13) and all function keys
624  * (see the Specification of the Z-machine) which are accepted by
625  * the is_terminator function. Mouse clicks behave like function
626  * keys except that the mouse position is stored in global variables
627  * "mouse_x" and "mouse_y" (top left coordinates are (1,1)).
628  *
629  * Furthermore, Frotz introduces some special terminating keys:
630  *
631  *     ZC_HKEY_KEY_PLAYBACK (Alt-P)
632  *     ZC_HKEY_RECORD (Alt-R)
633  *     ZC_HKEY_SEED (Alt-S)
634  *     ZC_HKEY_UNDO (Alt-U)
635  *     ZC_HKEY_RESTART (Alt-N, "new game")
636  *     ZC_HKEY_QUIT (Alt-X, "exit game")
637  *     ZC_HKEY_DEBUGGING (Alt-D)
638  *     ZC_HKEY_HELP (Alt-H)
639  *
640  * If the timeout argument is not zero, the input gets interrupted
641  * after timeout/10 seconds (and the return value is ZC_TIME_OUT).
642  *
643  * The complete input line including the cursor must fit in "width"
644  * screen units.  If the screen width changes during input, width
645  * is adjusted by the same amount.  bufmax is not adjusted: buf must
646  * contain space for at least bufmax + 1 characters (including final NUL).
647  *
648  * The function may be called once again to continue after timeouts,
649  * misplaced mouse clicks or hot keys. In this case the "continued"
650  * flag will be set. This information can be useful if the interface
651  * implements input line history.
652  *
653  * The screen is not scrolled after the return key was pressed. The
654  * cursor is at the end of the input line when the function returns.
655  *
656  * Since Inform 2.2 the helper function "completion" can be called
657  * to implement word completion (similar to tcsh under Unix).
658  *
659  */
os_read_line(int bufmax,zchar * buf,int timeout,int width,int continued)660 zchar os_read_line (int bufmax, zchar *buf, int timeout, int width,
661                     int continued)
662 {
663 	int ch, y, x, len = zcharstrlen(buf);
664 	int res;
665 	const int margin = MAX(z_header.screen_width - width, 0);
666 
667 	/* These are static to allow input continuation to work smoothly. */
668 	static int scrpos = 0, searchpos = -1, insert_flag = 1;
669 
670 	/* Set x and y to be at the start of the input area.  */
671 	getyx(stdscr, y, x);
672 	x -= len;
673 
674 	/* Better be careful here or it might segv.  I wonder if we should just
675 	 * ignore 'continued' and check for len > 0 instead?  Might work better
676 	 * with Beyond Zork.
677 	 */
678 	if (!continued || scrpos > len || searchpos > len) {
679 		scrpos = len;
680 		history_view = history_next; /* Reset user's history view. */
681 		searchpos = -1;		/* -1 means initialize from len. */
682 		insert_flag = 1;	/* Insert mode is now default. */
683 	}
684 
685 	unix_set_global_timeout(timeout);
686 	for (;;) {
687 		int x2, max;
688 		if (scrpos >= width)
689 			scrpos = width - 1;
690 			move(y, x + scrpos);
691 			/* Maybe there's a cleaner way to do this,
692 			 * but refresh() is  still needed here to
693 			 * print spaces.  --DG
694 			 */
695 			refresh();
696 			ch = unix_read_char(1);
697 		getyx(stdscr, y, x2);
698 		x2++;
699 		width = z_header.screen_width - margin;
700 		max = MIN(width, bufmax);
701 		/* The screen has shrunk and input no longer fits.  Chop. */
702 		if (len > max) {
703 			len = max;
704 			if (scrpos > len)
705 				scrpos = len;
706 			if (searchpos > len)
707 				searchpos = len;
708 		}
709 		switch (ch) {
710 		case ZC_BACKSPACE:	/* Delete preceding character */
711 			if (scrpos != 0) {
712 				len--;
713 				scrpos--;
714 				searchpos = -1;
715 				scrnmove(x + scrpos,
716 					x + scrpos + 1, len - scrpos);
717 				mvaddch(y, x + len, ' ');
718 				memmove(buf + scrpos, buf + scrpos + 1,
719 					(len - scrpos)*sizeof(zchar));
720 			}
721 			break;
722 		case ZC_DEL_WORD:
723 			if (scrpos != 0) {
724 				int newoffset =
725 					start_of_prev_word(scrpos, buf);
726 				searchpos = -1;
727 				int delta = scrpos - newoffset;
728 				int oldlen = len;
729 				int oldscrpos = scrpos;
730 				len -= delta;
731 				scrpos -= delta;
732 				scrnmove(x + scrpos, x + oldscrpos,
733 					len - scrpos);
734 				memmove(buf + scrpos, buf + oldscrpos,
735 					(len - scrpos)*sizeof(zchar));
736 				int i = newoffset;
737 				for (i = len; i <= oldlen ; i++)
738 					mvaddch(y, x + i, ' ');
739 			}
740 			break;
741 		case ZC_DEL_TO_BOL:
742 			if (scrpos != 0) {
743 				searchpos = -1;
744 				len -= scrpos;
745 				scrnmove(x, x + scrpos, len);
746 				memmove(buf, buf + scrpos,
747 					len*sizeof(zchar));
748 				for (int i = len; i <= len + scrpos; i++)
749 					mvaddch(y, x + i, ' ');
750 				scrpos = 0;
751 			}
752 			break;
753 		case CHR_DEL:
754 		case KEY_DC:		/* Delete following character */
755 			if (scrpos < len) {
756 				len--; searchpos = -1;
757 				scrnmove(x + scrpos, x + scrpos + 1,
758 					len - scrpos);
759 				mvaddch(y, x + len, ' ');
760 				memmove(buf + scrpos, buf + scrpos + 1,
761 				(len - scrpos)*sizeof(zchar));
762 			}
763 			continue; /* Don't feed is_terminator bad zchars. */
764 
765 		case KEY_EOL:	/* Delete from cursor to end of line.  */
766 			scrnset(x + scrpos, ' ', len - scrpos);
767 			len = scrpos;
768 			continue;
769 		case ZC_ESCAPE:		/* Delete whole line */
770 			scrnset(x, ' ', len);
771 			len = scrpos = 0;
772 			searchpos = -1;
773 			history_view = history_next;
774 			continue;
775 
776 		/* Cursor motion */
777 		case ZC_ARROW_LEFT:
778 			if (scrpos)
779 				scrpos--;
780 			continue;
781 		case ZC_ARROW_RIGHT:
782 			if (scrpos < len)
783 				scrpos++;
784 			continue;
785 		case KEY_HOME:
786 			scrpos = 0;
787 			continue;
788 		case KEY_END:
789 			scrpos = len;
790 			continue;
791 		case ZC_WORD_RIGHT:
792 			if (scrpos < len)
793 				scrpos = end_of_next_word(scrpos, buf, len);
794 			continue;
795 		case ZC_WORD_LEFT:
796 			if (scrpos > 0)
797 				scrpos = start_of_prev_word(scrpos, buf);
798 			continue;
799 		case KEY_IC:	/* Insert Character */
800 			insert_flag = !insert_flag;
801 			continue;
802 		case ZC_ARROW_UP:
803 		case ZC_ARROW_DOWN:
804 			if (searchpos < 0)
805 				searchpos = len;
806 			if (ch == ZC_ARROW_UP)
807 				res = unix_history_back(buf, searchpos, max);
808 			else
809 				res = unix_history_forward(buf, searchpos, max);
810 
811 			if (res) {
812 				scrnset(x, ' ', len);
813 #ifdef USE_UTF8
814 				utf8_mvaddstr(y, x, buf);
815 #else
816 				mvaddstr(y, x, (char *) buf);
817 #endif
818 				scrpos = len = zcharstrlen(buf);
819 			}
820 			continue;
821 		/* Passthrough as up/down arrows for Beyond Zork. */
822 		case KEY_PPAGE: ch = ZC_ARROW_UP; break;
823 		case KEY_NPAGE: ch = ZC_ARROW_DOWN; break;
824 		case '\t':
825 	    /* This really should be fixed to work also in the middle of a
826 	       sentence. */
827 			{
828 			int status;
829 			zchar extension[10], saved_char;
830 
831 			saved_char = buf[scrpos];
832 			buf[scrpos] = '\0';
833 			status = completion( buf, extension);
834 			buf[scrpos] = saved_char;
835 
836 			if (status != 2) {
837 				int ext_len = zcharstrlen(extension);
838 				if (ext_len > max - len) {
839 					ext_len = max - len;
840 					status = 1;
841 				}
842 				memmove(buf + scrpos + ext_len, buf + scrpos,
843 					(len - scrpos)*sizeof(zchar));
844 				memmove(buf + scrpos, extension,
845 					ext_len*sizeof(zchar));
846 				scrnmove(x + scrpos + ext_len, x + scrpos, len - scrpos);
847 #ifdef USE_UTF8
848 				utf8_mvaddnstr(y, x + scrpos, extension,
849 					ext_len);
850 #else
851 				mvaddnstr(y, x + scrpos, (char *)extension,
852 					ext_len);
853 #endif
854 				scrpos += ext_len;
855 				len += ext_len;
856 				searchpos = -1;
857 			}
858 			if (status) os_beep(BEEP_HIGH);
859 			}
860 			continue; /* TAB is invalid as an input character. */
861 		default:
862 			/* ASCII or ISO-Latin-1 */
863 			if ((ch >= ZC_ASCII_MIN && ch <= ZC_ASCII_MAX)
864 			    || (!u_setup.plain_ascii
865 			    && ch >= ZC_LATIN1_MIN /*&& ch <= ZC_LATIN1_MAX*/)) {
866 				searchpos = -1;
867 				if ((scrpos == max) || (insert_flag && (len == max))) {
868 					os_beep(BEEP_HIGH);
869 					continue;
870 				}
871 				if (insert_flag && (scrpos < len)) {
872 					/* move what's there to the right */
873 					scrnmove(x + scrpos + 1, x + scrpos,
874 						len - scrpos);
875 					memmove(buf + scrpos + 1, buf + scrpos,
876 						(len - scrpos)*sizeof(zchar));
877 				}
878 				if (insert_flag || scrpos == len)
879 					len++;
880 #ifdef USE_UTF8
881 				if (ch < ZC_LATIN1_MIN) {
882 					mvaddch(y, x + scrpos, ch);
883 				} else if(ch > 0x7ff) {
884 					mvaddch(y, x + scrpos, 0xe0 |
885 						((ch >> 12) & 0xf));
886 					addch(0x80 | ((ch >> 6) & 0x3f));
887 					addch(0x80 | (ch & 0x3f));
888 				} else {
889 					mvaddch(y, x + scrpos, 0xc0 |
890 						((ch >> 6) & 0x1f));
891 					addch(0x80 | (ch & 0x3f));
892 				}
893 #else
894 				mvaddch(y, x + scrpos, ch);
895 #endif
896 				buf[scrpos++] = ch;
897 				continue;
898 			}
899 		}
900 		if (is_terminator(ch)) {
901 			buf[len] = '\0';
902 			if (ch == ZC_RETURN)
903 				unix_add_to_history(buf);
904 			/* Games don't know about line editing and might
905 			 * get confused if the cursor is not at the end
906 			 * of the input line. */
907 			move(y, x + len);
908 			return ch;
909 		}
910 	}
911 } /* os_read_line */
912 
913 
914 /*
915  * os_read_key
916  *
917  * Read a single character from the keyboard (or a mouse click) and
918  * return it. Input aborts after timeout/10 seconds.
919  *
920  */
os_read_key(int timeout,int cursor)921 zchar os_read_key (int timeout, int cursor)
922 {
923 	zchar c;
924 
925 	refresh();
926 	if (!cursor)
927 		curs_set(0);
928 
929 	unix_set_global_timeout(timeout);
930 	c = (zchar) unix_read_char(0);
931 
932 	if (!cursor)
933 		curs_set(1);
934 	return c;
935 } /* os_read_key */
936 
937 
938 /*
939  * os_read_file_name
940  *
941  * Return the name of a file.  Flag can be one of:
942  *
943  *    FILE_SAVE     - Save game file
944  *    FILE_RESTORE  - Restore game file
945  *    FILE_SCRIPT   - Transcript file
946  *    FILE_RECORD   - Command file for recording
947  *    FILE_PLAYBACK - Command file for playback
948  *    FILE_SAVE_AUX - Save auxilary ("preferred settings") file
949  *    FILE_LOAD_AUX - Load auxilary ("preferred settings") file
950  *
951  * The length of the file name is limited by MAX_FILE_NAME. Ideally
952  * an interpreter should open a file requester to ask for the file
953  * name. If it is unable to do that then this function should call
954  * print_string and read_string to ask for a file name.
955  *
956  * Return value is NULL if there was a problem.
957  */
os_read_file_name(const char * default_name,int flag)958 char *os_read_file_name (const char *default_name, int flag)
959 {
960 	FILE *fp;
961 	int saved_replay = istream_replay;
962 	int saved_record = ostream_record;
963  	int i;
964 	char *tempname;
965 	zchar answer[4];
966 	char path_separator[2];
967 	char file_name[FILENAME_MAX + 1];
968 
969 	path_separator[0] = PATH_SEPARATOR;
970 	path_separator[1] = 0;
971 
972 	/* Turn off playback and recording temporarily */
973 	istream_replay = 0;
974 	ostream_record = 0;
975 
976 	/* If we're restoring a game before the interpreter starts,
977 	 * our filename is already provided.  Just go ahead silently.
978 	 */
979 	if (f_setup.restore_mode) {
980 		file_name[0]=0;
981 	} else {
982 		print_string ("Enter a file name.\nDefault is \"");
983 
984 		/* After successfully reading or writing a file, the default
985 		 * name gets saved and used again the next time something is
986 		 * to be read or written.  In restricted mode, we don't want
987 		 * to show any path prepended to the actual file name.  Here,
988 		 * we strip out that path and display only the filename.
989 		 */
990 		if (f_setup.restricted_path != NULL) {
991 			tempname = basename((char *)default_name);
992 			print_string(tempname);
993 		} else
994 #ifdef USE_UTF8
995 		{
996 			zchar z;
997 			i = 0;
998 			while (default_name[i]) {
999 				if((default_name[i] & 0x80) == 0) {
1000 					print_char(default_name[i++]);
1001 				} else if((default_name[i] & 0xe0) == 0xc0 ) {
1002 					z = default_name[i++] & 0x1f;
1003 					z = (z << 6) | (default_name[i++] & 0x3f);
1004 					print_char(z);
1005 				} else if((default_name[i] & 0xf0) == 0xe0 ) {
1006 					z = default_name[i++] & 0xf;
1007 					z = (z << 6) | (default_name[i++] & 0x3f);
1008 					z = (z << 6) | (default_name[i++] & 0x3f);
1009 					print_char(z);
1010 				} else {
1011 					i+=4;
1012 					print_char('?');
1013 				}
1014 			}
1015 		}
1016 #else
1017 		print_string (default_name);
1018 #endif
1019 		print_string ("\": ");
1020 #ifdef USE_UTF8
1021 		{
1022 			zchar z_name[FILENAME_MAX + 1];
1023 			zchar *zp;
1024 			read_string (FILENAME_MAX, z_name);
1025 			i = 0;
1026 			zp = z_name;
1027 			while (*zp) {
1028 				if(*zp <= 0x7f) {
1029 					if (i > FILENAME_MAX)
1030 						break;
1031 					file_name[i++] = *zp;
1032 				} else if(*zp > 0x7ff) {
1033 					if (i > FILENAME_MAX - 2)
1034 						break;
1035 					file_name[i++] = 0xe0 | ((*zp >> 12) & 0xf);
1036 					file_name[i++] = 0x80 | ((*zp >> 6) & 0x3f);
1037 					file_name[i++] = 0x80 | (*zp & 0x3f);
1038 				} else {
1039 					if (i > FILENAME_MAX - 1)
1040 						break;
1041 					file_name[i++] = 0xc0 | ((*zp >> 6) & 0x1f);
1042 					file_name[i++] = 0x80 | (*zp & 0x3f);
1043 				}
1044 				zp++;
1045 			}
1046 			file_name[i] = 0;
1047 		}
1048 #else
1049 		read_string (FILENAME_MAX, (zchar *)file_name);
1050 #endif
1051 	}
1052 
1053 	/* Return failure if path provided when in restricted mode.
1054 	 * I think this is better than accepting a path/filename
1055 	 * and stripping off the path.
1056 	 */
1057 	if (f_setup.restricted_path) {
1058 		tempname = dirname(file_name);
1059 		if (strlen(tempname) > 1)
1060 			return NULL;
1061 	}
1062 
1063 	/* Use the default name if nothing was typed */
1064 	if (file_name[0] == 0)
1065 		strncpy (file_name, default_name, FILENAME_MAX);
1066 
1067 	/* If we're restricted to one directory, strip any leading path left
1068 	 * over from a previous call to os_read_file_name(), then prepend
1069 	 * the prescribed path to the filename.  Hostile leading paths won't
1070 	 * get this far.  Instead we return failure a few lines above here if
1071 	 * someone tries it.
1072 	 */
1073 	if (f_setup.restricted_path != NULL) {
1074 		for (i = strlen(file_name); i > 0; i--) {
1075 			if (file_name[i] == PATH_SEPARATOR) {
1076 				i++;
1077 				break;
1078 			}
1079 		}
1080 		tempname = strdup(file_name + i);
1081 		strncpy(file_name, f_setup.restricted_path, FILENAME_MAX);
1082 
1083 		/* Make sure the final character is the path separator. */
1084 		if (file_name[strlen(file_name)-1] != PATH_SEPARATOR) {
1085 			strncat(file_name, path_separator, FILENAME_MAX - strlen(file_name) - 2);
1086 		}
1087 		strncat(file_name, tempname, strlen(file_name) - strlen(tempname) - 1);
1088 	}
1089 
1090 	/* Warn if overwriting a file. */
1091 	if ((flag == FILE_SAVE || flag == FILE_SAVE_AUX ||
1092 	    flag == FILE_RECORD || flag == FILE_SCRIPT)
1093 	    && ((fp = fopen(file_name, "rb")) != NULL)) {
1094 		fclose (fp);
1095 		print_string("Overwrite existing file? ");
1096 		read_string(4, answer);
1097 		if (tolower(answer[0] != 'y'))
1098 			return NULL;
1099 	}
1100 
1101 	/* Restore state of playback and recording */
1102 	istream_replay = saved_replay;
1103 	ostream_record = saved_record;
1104 
1105 	return strdup(file_name);
1106 } /* os_read_file_name */
1107 
1108 
1109 /*
1110  * os_read_mouse
1111  *
1112  * Store the mouse position in the global variables "mouse_x" and
1113  * "mouse_y" and return the mouse buttons currently pressed.
1114  *
1115  */
os_read_mouse(void)1116 zword os_read_mouse (void)
1117 {
1118 	/* INCOMPLETE */
1119 	return 0;
1120 } /* os_read_mouse */
1121 
1122 
1123 /*
1124  * Search for start of preceding word
1125  * param currpos marker position
1126  * param buf input buffer
1127  * returns new position
1128  */
start_of_prev_word(int currpos,const zchar * buf)1129 static int start_of_prev_word(int currpos, const zchar* buf) {
1130 	int i, j;
1131 	for (i = currpos - 1; i > 0 && buf[i] == ' '; i--) {}
1132 	j = i;
1133 	for (; i > 0 && buf[i] != ' '; i--) {}
1134 	if (i < j && i != 0)
1135 		i += 1;
1136 	return i;
1137 } /* start_of_prev_word */
1138 
1139 
1140 /*
1141  * Search for end of next word
1142  * param currpos marker position
1143  * param buf input buffer
1144  * param len length of buf
1145  * returns new position
1146  */
end_of_next_word(int currpos,const zchar * buf,int len)1147 static int end_of_next_word(int currpos, const zchar* buf, int len) {
1148 	int i;
1149 	for (i = currpos; i < len && buf[i] == ' '; i++) {}
1150 	for (; i < len && buf[i] != ' '; i++) {}
1151 	return i;
1152 } /* end_of_next_word */
1153