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