Comment the UTF-8 decoding in handle_interlink_event
[elinks.git] / src / terminal / kbd.c
blob229cf4de7e9287ab7c5ab580c52daf6b0e796c9e
1 /* Support for keyboard interface */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdlib.h>
8 #include <string.h>
9 #ifdef HAVE_TERMIOS_H
10 #include <termios.h>
11 #endif
12 #ifdef HAVE_UNISTD_H
13 #include <unistd.h>
14 #endif
15 #ifdef __hpux__
16 #include <limits.h>
17 #define HPUX_PIPE (len > PIPE_BUF || errno != EAGAIN)
18 #else
19 #define HPUX_PIPE 1
20 #endif
22 #include "elinks.h"
24 #include "config/options.h"
25 #include "intl/gettext/libintl.h"
26 #include "main/select.h"
27 #include "main/timer.h"
28 #include "osdep/ascii.h"
29 #include "osdep/osdep.h"
30 #include "terminal/hardio.h"
31 #include "terminal/itrm.h"
32 #include "terminal/kbd.h"
33 #include "terminal/mouse.h"
34 #include "terminal/terminal.h"
35 #include "util/error.h"
36 #include "util/memory.h"
37 #include "util/string.h"
38 #include "util/time.h"
40 /* TODO: move stuff from here to itrm.{c,h} and mouse.{c,h} */
42 struct itrm *ditrm = NULL;
44 static void free_itrm(struct itrm *);
45 static void in_kbd(struct itrm *);
46 static void in_sock(struct itrm *);
47 static int process_queue(struct itrm *);
48 static void handle_itrm_stdin(struct itrm *);
49 static void unhandle_itrm_stdin(struct itrm *);
51 int
52 is_blocked(void)
54 return ditrm && ditrm->blocked;
58 void
59 free_all_itrms(void)
61 if (ditrm) free_itrm(ditrm);
65 /* A select_handler_T write_func for itrm->out.sock. This is called
66 * when there is data in itrm->out.queue and it is possible to write
67 * it to itrm->out.sock. When itrm->out.queue becomes empty, this
68 * handler is temporarily removed. */
69 static void
70 itrm_queue_write(struct itrm *itrm)
72 int written;
73 int qlen = int_min(itrm->out.queue.len, 128);
75 assertm(qlen, "event queue empty");
76 if_assert_failed return;
78 written = safe_write(itrm->out.sock, itrm->out.queue.data, qlen);
79 if (written <= 0) {
80 if (written < 0) free_itrm(itrm); /* write error */
81 return;
84 itrm->out.queue.len -= written;
86 if (itrm->out.queue.len == 0) {
87 set_handlers(itrm->out.sock,
88 get_handler(itrm->out.sock, SELECT_HANDLER_READ),
89 NULL,
90 get_handler(itrm->out.sock, SELECT_HANDLER_ERROR),
91 get_handler(itrm->out.sock, SELECT_HANDLER_DATA));
92 } else {
93 assert(itrm->out.queue.len > 0);
94 memmove(itrm->out.queue.data, itrm->out.queue.data + written, itrm->out.queue.len);
99 void
100 itrm_queue_event(struct itrm *itrm, unsigned char *data, int len)
102 int w = 0;
104 if (!len) return;
106 if (!itrm->out.queue.len && can_write(itrm->out.sock)) {
107 w = safe_write(itrm->out.sock, data, len);
108 if (w <= 0 && HPUX_PIPE) {
109 register_bottom_half(free_itrm, itrm);
110 return;
114 if (w < len) {
115 int left = len - w;
116 unsigned char *c = mem_realloc(itrm->out.queue.data,
117 itrm->out.queue.len + left);
119 if (!c) {
120 free_itrm(itrm);
121 return;
124 itrm->out.queue.data = c;
125 memcpy(itrm->out.queue.data + itrm->out.queue.len, data + w, left);
126 itrm->out.queue.len += left;
127 set_handlers(itrm->out.sock,
128 get_handler(itrm->out.sock, SELECT_HANDLER_READ),
129 (select_handler_T) itrm_queue_write,
130 (select_handler_T) free_itrm, itrm);
135 void
136 kbd_ctrl_c(void)
138 struct interlink_event ev;
140 if (!ditrm) return;
141 set_kbd_interlink_event(&ev, KBD_CTRL_C, KBD_MOD_NONE);
142 itrm_queue_event(ditrm, (unsigned char *) &ev, sizeof(ev));
145 #define write_sequence(fd, seq) \
146 hard_write(fd, seq, sizeof(seq) - 1)
149 #define INIT_TERMINAL_SEQ "\033)0\0337" /* Special Character and Line Drawing Set, Save Cursor */
150 #define INIT_ALT_SCREEN_SEQ "\033[?47h" /* Use Alternate Screen Buffer */
152 static void
153 send_init_sequence(int h, int altscreen)
155 write_sequence(h, INIT_TERMINAL_SEQ);
157 /* If alternate screen is supported switch to it. */
158 if (altscreen) {
159 write_sequence(h, INIT_ALT_SCREEN_SEQ);
161 #ifdef CONFIG_MOUSE
162 send_mouse_init_sequence(h);
163 #endif
166 #define DONE_CLS_SEQ "\033[2J" /* Erase in Display, Clear All */
167 #define DONE_TERMINAL_SEQ "\0338\r \b" /* Restore Cursor (DECRC) + ??? */
168 #define DONE_ALT_SCREEN_SEQ "\033[?47l" /* Use Normal Screen Buffer */
170 static void
171 send_done_sequence(int h, int altscreen)
173 write_sequence(h, DONE_CLS_SEQ);
175 #ifdef CONFIG_MOUSE
176 send_mouse_done_sequence(h);
177 #endif
179 /* Switch from alternate screen. */
180 if (altscreen) {
181 write_sequence(h, DONE_ALT_SCREEN_SEQ);
184 write_sequence(h, DONE_TERMINAL_SEQ);
188 #undef write_sequence
190 void
191 resize_terminal(void)
193 struct interlink_event ev;
194 int width, height;
196 get_terminal_size(ditrm->out.std, &width, &height);
197 set_resize_interlink_event(&ev, width, height);
198 itrm_queue_event(ditrm, (char *) &ev, sizeof(ev));
201 static void
202 get_terminal_name(unsigned char name[MAX_TERM_LEN])
204 unsigned char *term = getenv("TERM");
205 int i;
207 memset(name, 0, MAX_TERM_LEN);
209 if (!term) return;
211 for (i = 0; term[i] != 0 && i < MAX_TERM_LEN - 1; i++)
212 name[i] = isident(term[i]) ? term[i] : '-';
216 static int
217 setraw(int fd, struct termios *p)
219 struct termios t;
221 memset(&t, 0, sizeof(t));
222 if (tcgetattr(fd, &t)) return -1;
224 if (p) copy_struct(p, &t);
226 elinks_cfmakeraw(&t);
227 t.c_lflag |= ISIG;
228 #ifdef TOSTOP
229 t.c_lflag |= TOSTOP;
230 #endif
231 t.c_oflag |= OPOST;
232 if (tcsetattr(fd, TCSANOW, &t)) return -1;
234 return 0;
237 /* Construct the struct itrm of this process, make ditrm point to it,
238 * set up select() handlers, and send the initial interlink packet.
240 * The first five parameters are file descriptors that this function
241 * saves in submembers of struct itrm, and for which this function may
242 * set select() handlers. Please see the definitions of struct
243 * itrm_in and struct itrm_out for further explanations.
245 * param member file if process is master file if process is slave
246 * ------ ------ ------------------------- ------------------------
247 * std_in in.std read tty device (or pipe) read tty device (or pipe)
248 * std_out out.std write tty device (or pipe) write tty device (or pipe)
249 * sock_in in.sock ==std_out (masterhood flag) read socket from master
250 * sock_out out.sock write pipe to same process write socket to master
251 * ctl_in in.ctl control tty device control tty device
253 * The remaining three parameters control the initial interlink packet.
255 * init_string = A string to be passed to the master process. Need
256 * not be null-terminated. If remote==0, this is a URI.
257 * Otherwise, this is a remote command.
258 * init_len = The length of init_string, in bytes.
259 * remote = 0 if asking the master to start a new session
260 * and display it via this process. Otherwise,
261 * enum remote_session_flags. */
262 void
263 handle_trm(int std_in, int std_out, int sock_in, int sock_out, int ctl_in,
264 void *init_string, int init_len, int remote)
266 struct itrm *itrm;
267 struct terminal_info info;
268 struct interlink_event_size *size = &info.event.info.size;
269 unsigned char *ts;
271 memset(&info, 0, sizeof(info));
273 get_terminal_size(ctl_in, &size->width, &size->height);
274 info.event.ev = EVENT_INIT;
275 info.system_env = get_system_env();
276 info.length = init_len;
278 if (remote) {
279 info.session_info = remote;
280 info.magic = INTERLINK_REMOTE_MAGIC;
281 } else {
282 info.session_info = get_cmd_opt_int("base-session");
283 info.magic = INTERLINK_NORMAL_MAGIC;
286 itrm = mem_calloc(1, sizeof(*itrm));
287 if (!itrm) return;
289 itrm->in.queue.data = mem_calloc(1, ITRM_IN_QUEUE_SIZE);
290 if (!itrm->in.queue.data) {
291 mem_free(itrm);
292 return;
295 ditrm = itrm;
296 itrm->in.std = std_in;
297 itrm->out.std = std_out;
298 itrm->in.sock = sock_in;
299 itrm->out.sock = sock_out;
300 itrm->in.ctl = ctl_in;
301 itrm->timer = TIMER_ID_UNDEF;
302 itrm->remote = !!remote;
304 /* FIXME: Combination altscreen + xwin does not work as it should,
305 * mouse clicks are reportedly partially ignored. */
306 if (info.system_env & (ENV_SCREEN | ENV_XWIN))
307 itrm->altscreen = 1;
309 if (!remote) {
310 if (ctl_in >= 0) setraw(ctl_in, &itrm->t);
311 send_init_sequence(std_out, itrm->altscreen);
312 handle_terminal_resize(ctl_in, resize_terminal);
313 #ifdef CONFIG_MOUSE
314 enable_mouse();
315 #endif
318 handle_itrm_stdin(itrm);
320 if (sock_in != std_out)
321 set_handlers(sock_in, (select_handler_T) in_sock,
322 NULL, (select_handler_T) free_itrm, itrm);
324 get_terminal_name(info.name);
326 ts = get_cwd();
327 if (ts) {
328 memcpy(info.cwd, ts, int_min(strlen(ts), MAX_CWD_LEN));
329 mem_free(ts);
332 itrm_queue_event(itrm, (char *) &info, TERMINAL_INFO_SIZE);
333 itrm_queue_event(itrm, (char *) init_string, init_len);
337 /* A select_handler_T read_func and error_func for the pipe (long) h.
338 * This is called when the subprocess started on the terminal of this
339 * ELinks process exits. ELinks then resumes using the terminal. */
340 static void
341 unblock_itrm_x(void *h)
343 close_handle(h);
344 if (!ditrm) return;
345 unblock_itrm(0);
346 resize_terminal();
351 unblock_itrm(int fd)
353 if (!ditrm) return -1;
355 if (ditrm->in.ctl >= 0 && setraw(ditrm->in.ctl, NULL)) return -1;
356 ditrm->blocked = 0;
357 send_init_sequence(ditrm->out.std, ditrm->altscreen);
359 handle_itrm_stdin(ditrm);
360 resume_mouse(ditrm->mouse_h);
362 handle_terminal_resize(ditrm->in.ctl, resize_terminal);
363 unblock_stdin();
365 return 0;
369 void
370 block_itrm(int fd)
372 if (!ditrm) return;
374 ditrm->blocked = 1;
375 block_stdin();
376 kill_timer(&ditrm->timer);
377 ditrm->in.queue.len = 0;
378 unhandle_terminal_resize(ditrm->in.ctl);
379 send_done_sequence(ditrm->out.std, ditrm->altscreen);
380 tcsetattr(ditrm->in.ctl, TCSANOW, &ditrm->t);
381 unhandle_itrm_stdin(ditrm);
382 suspend_mouse(ditrm->mouse_h);
386 static void
387 free_itrm(struct itrm *itrm)
389 if (!itrm) return;
391 if (!itrm->remote) {
392 if (itrm->orig_title && *itrm->orig_title) {
393 set_window_title(itrm->orig_title);
395 } else if (itrm->touched_title) {
396 /* Set the window title to the value of $TERM if X11
397 * wasn't compiled in. Should hopefully make at least
398 * half the users happy. (debian bug #312955) */
399 unsigned char title[MAX_TERM_LEN];
401 get_terminal_name(title);
402 if (*title)
403 set_window_title(title);
407 unhandle_terminal_resize(itrm->in.ctl);
408 #ifdef CONFIG_MOUSE
409 disable_mouse();
410 #endif
411 send_done_sequence(itrm->out.std, itrm->altscreen);
412 tcsetattr(itrm->in.ctl, TCSANOW, &itrm->t);
415 mem_free_set(&itrm->orig_title, NULL);
417 clear_handlers(itrm->in.std);
418 clear_handlers(itrm->in.sock);
419 clear_handlers(itrm->out.std);
420 clear_handlers(itrm->out.sock);
422 kill_timer(&itrm->timer);
424 if (itrm == ditrm) ditrm = NULL;
425 mem_free_if(itrm->out.queue.data);
426 mem_free_if(itrm->in.queue.data);
427 mem_free(itrm);
430 /* Resize terminal to dimensions specified by @text string.
431 * @text should look like "width,height,old-width,old-height" where width and
432 * height are integers. */
433 static inline void
434 resize_terminal_from_str(unsigned char *text)
436 enum { NEW_WIDTH = 0, NEW_HEIGHT, OLD_WIDTH, OLD_HEIGHT, NUMBERS } i;
437 int numbers[NUMBERS];
439 assert(text && *text);
440 if_assert_failed return;
442 for (i = 0; i < NUMBERS; i++) {
443 unsigned char *p = strchr(text, ',');
445 if (p) {
446 *p++ = '\0';
448 } else if (i < OLD_HEIGHT) {
449 return;
452 numbers[i] = atoi(text);
454 if (p) text = p;
457 resize_window(numbers[NEW_WIDTH], numbers[NEW_HEIGHT],
458 numbers[OLD_WIDTH], numbers[OLD_HEIGHT]);
459 resize_terminal();
462 void
463 dispatch_special(unsigned char *text)
465 switch (text[0]) {
466 case TERM_FN_TITLE:
467 if (ditrm) {
468 if (ditrm->remote)
469 break;
471 if (!ditrm->orig_title)
472 ditrm->orig_title = get_window_title();
473 ditrm->touched_title = 1;
475 set_window_title(text + 1);
476 break;
477 case TERM_FN_RESIZE:
478 if (ditrm && ditrm->remote)
479 break;
481 resize_terminal_from_str(text + 1);
482 break;
486 static void inline
487 safe_hard_write(int fd, unsigned char *buf, int len)
489 if (is_blocked()) return;
491 want_draw();
492 hard_write(fd, buf, len);
493 done_draw();
496 /* A select_handler_T read_func for itrm->in.sock. A slave process
497 * calls this when the master sends it data to be displayed. The
498 * master process never calls this. */
499 static void
500 in_sock(struct itrm *itrm)
502 struct string path;
503 struct string delete;
504 char ch;
505 int fg;
506 ssize_t bytes_read, i, p;
507 unsigned char buf[ITRM_OUT_QUEUE_SIZE];
509 bytes_read = safe_read(itrm->in.sock, buf, ITRM_OUT_QUEUE_SIZE);
510 if (bytes_read <= 0) goto free_and_return;
512 qwerty:
513 for (i = 0; i < bytes_read; i++)
514 if (!buf[i])
515 goto has_nul_byte;
517 safe_hard_write(itrm->out.std, buf, bytes_read);
518 return;
520 has_nul_byte:
521 if (i) safe_hard_write(itrm->out.std, buf, i);
523 i++;
524 assert(ITRM_OUT_QUEUE_SIZE - i > 0);
525 memmove(buf, buf + i, ITRM_OUT_QUEUE_SIZE - i);
526 bytes_read -= i;
527 p = 0;
529 #define RD(xx) { \
530 unsigned char cc; \
532 if (p < bytes_read) \
533 cc = buf[p++]; \
534 else if ((hard_read(itrm->in.sock, &cc, 1)) <= 0) \
535 goto free_and_return; \
536 xx = cc; \
539 RD(fg);
541 if (!init_string(&path)) goto free_and_return;
543 while (1) {
544 RD(ch);
545 if (!ch) break;
546 add_char_to_string(&path, ch);
549 if (!init_string(&delete)) {
550 done_string(&path);
551 goto free_and_return;
554 while (1) {
555 RD(ch);
556 if (!ch) break;
557 add_char_to_string(&delete, ch);
560 #undef RD
562 if (!*path.source) {
563 dispatch_special(delete.source);
565 } else {
566 int blockh;
567 unsigned char *param;
568 int path_len, del_len, param_len;
570 if (is_blocked() && fg) {
571 if (*delete.source) unlink(delete.source);
572 goto nasty_thing;
575 path_len = path.length;
576 del_len = delete.length;
577 param_len = path_len + del_len + 3;
579 param = mem_alloc(param_len);
580 if (!param) goto nasty_thing;
582 param[0] = fg;
583 memcpy(param + 1, path.source, path_len + 1);
584 memcpy(param + 1 + path_len + 1, delete.source, del_len + 1);
586 if (fg == 1) block_itrm(itrm->in.ctl);
588 blockh = start_thread((void (*)(void *, int)) exec_thread,
589 param, param_len);
590 mem_free(param);
592 if (blockh == -1) {
593 if (fg == 1)
594 unblock_itrm(itrm->in.ctl);
596 goto nasty_thing;
599 if (fg == 1) {
600 set_handlers(blockh, (select_handler_T) unblock_itrm_x,
601 NULL, (select_handler_T) unblock_itrm_x,
602 (void *) (long) blockh);
604 } else {
605 set_handlers(blockh, close_handle, NULL, close_handle,
606 (void *) (long) blockh);
610 nasty_thing:
611 done_string(&path);
612 done_string(&delete);
613 assert(ITRM_OUT_QUEUE_SIZE - p > 0);
614 memmove(buf, buf + p, ITRM_OUT_QUEUE_SIZE - p);
615 bytes_read -= p;
617 goto qwerty;
619 free_and_return:
620 free_itrm(itrm);
624 /* Returns the length of the escape sequence */
625 static inline int
626 get_esc_code(unsigned char *str, int len, unsigned char *code, int *num)
628 int pos;
630 *num = 0;
631 *code = '\0';
633 for (pos = 2; pos < len; pos++) {
634 if (!isdigit(str[pos]) || pos > 7) {
635 *code = str[pos];
637 return pos + 1;
639 *num = *num * 10 + str[pos] - '0';
642 return 0;
645 /* Define it to dump queue content in a readable form,
646 * it may help to determine terminal sequences, and see what goes on. --Zas */
647 /* #define DEBUG_ITRM_QUEUE */
649 #ifdef DEBUG_ITRM_QUEUE
650 #include <stdio.h>
651 #include <ctype.h> /* isprint() isspace() */
652 #endif
654 /* Returns length of the escape sequence or -1 if the caller needs to set up
655 * the ESC delay timer. */
656 static int
657 decode_terminal_escape_sequence(struct itrm *itrm, struct interlink_event *ev)
659 struct term_event_keyboard kbd = { KBD_UNDEF, KBD_MOD_NONE };
660 unsigned char c;
661 int v;
662 int el;
664 if (itrm->in.queue.len < 3) return -1;
666 if (itrm->in.queue.data[2] == '[') {
667 /* The terminfo entry for linux has "kf1=\E[[A", etc.
668 * These are not control sequences compliant with
669 * clause 5.4 of ECMA-48. */
670 if (itrm->in.queue.len >= 4
671 && itrm->in.queue.data[3] >= 'A'
672 && itrm->in.queue.data[3] <= 'L') {
673 kbd.key = number_to_kbd_fkey(itrm->in.queue.data[3] - 'A' + 1);
674 set_kbd_interlink_event(ev, kbd.key, kbd.modifier);
675 return 4;
678 return -1;
681 el = get_esc_code(itrm->in.queue.data, itrm->in.queue.len, &c, &v);
682 #ifdef DEBUG_ITRM_QUEUE
683 fprintf(stderr, "esc code: %c v=%d c=%c el=%d\n", itrm->in.queue.data[1], v, c, el);
684 fflush(stderr);
685 #endif
687 switch (c) { /* ECMA-48 Terminfo $TERM */
688 case 0: return -1; /* ------- -------- ----- */
689 case 'A': kbd.key = KBD_UP; break; /* CUU kcuu1 vt200 */
690 case 'B': kbd.key = KBD_DOWN; break; /* CUD kcud1 vt200 */
691 case 'C': kbd.key = KBD_RIGHT; break; /* CUF kcuf1 vt200 */
692 case 'D': kbd.key = KBD_LEFT; break; /* CUB kcub1 vt200 */
693 case 'F': /* (CPL) kend cons25 */
694 case 'e': kbd.key = KBD_END; break; /* (VPR) kend */
695 case 'H': kbd.key = KBD_HOME; break; /* CUP khome cons25 */
696 case 'I': kbd.key = KBD_PAGE_UP; break; /* (CHT) kpp cons25 */
697 case 'G': kbd.key = KBD_PAGE_DOWN; break; /* (CHA) knp cons25 */
698 /* Free BSD (TERM=cons25 etc.) */
699 /* case 'M': kbd.key = KBD_F1; break;*/ /* (DL) kf1 cons25 */
700 case 'N': kbd.key = KBD_F2; break; /* (EF) kf2 cons25 */
701 case 'O': kbd.key = KBD_F3; break; /* (EA) kf3 cons25 */
702 case 'P': kbd.key = KBD_F4; break; /* (DCH) kf4 cons25 */
703 case 'Q': kbd.key = KBD_F5; break; /* (SEE) kf5 cons25 */
704 /* case 'R': kbd.key = KBD_F6; break;*/ /* (CPR) kf6 cons25 */
705 case 'S': kbd.key = KBD_F7; break; /* (SU) kf7 cons25 */
706 case 'T': kbd.key = KBD_F8; break; /* (SD) kf8 cons25 */
707 case 'U': kbd.key = KBD_F9; break; /* (NP) kf9 cons25 */
708 case 'V': kbd.key = KBD_F10; break; /* (PP) kf10 cons25 */
709 case 'W': kbd.key = KBD_F11; break; /* (CTC) kf11 cons25 */
710 case 'X': kbd.key = KBD_F12; break; /* (ECH) kf12 cons25 */
712 case 'z': switch (v) { /* private */
713 case 247: kbd.key = KBD_INS; break; /* kich1 */
714 case 214: kbd.key = KBD_HOME; break; /* khome sun */
715 case 220: kbd.key = KBD_END; break; /* kend sun */
716 case 216: kbd.key = KBD_PAGE_UP; break; /* kpp sun */
717 case 222: kbd.key = KBD_PAGE_DOWN; break; /* knp sun */
718 case 249: kbd.key = KBD_DEL; break; /* kdch1 */
719 } break;
721 case '~': switch (v) { /* private */
722 case 1: kbd.key = KBD_HOME; break; /* khome linux */
723 case 2: kbd.key = KBD_INS; break; /* kich1 linux */
724 case 3: kbd.key = KBD_DEL; break; /* kdch1 linux */
725 case 4: kbd.key = KBD_END; break; /* kend linux */
726 case 5: kbd.key = KBD_PAGE_UP; break; /* kpp linux */
727 case 6: kbd.key = KBD_PAGE_DOWN; break; /* knp linux */
728 case 7: kbd.key = KBD_HOME; break; /* khome rxvt */
729 case 8: kbd.key = KBD_END; break; /* kend rxvt */
731 case 11: kbd.key = KBD_F1; break; /* kf1 rxvt */
732 case 12: kbd.key = KBD_F2; break; /* kf2 rxvt */
733 case 13: kbd.key = KBD_F3; break; /* kf3 rxvt */
734 case 14: kbd.key = KBD_F4; break; /* kf4 rxvt */
735 case 15: kbd.key = KBD_F5; break; /* kf5 rxvt */
737 case 17: kbd.key = KBD_F6; break; /* kf6 vt200 */
738 case 18: kbd.key = KBD_F7; break; /* kf7 vt200 */
739 case 19: kbd.key = KBD_F8; break; /* kf8 vt200 */
740 case 20: kbd.key = KBD_F9; break; /* kf9 vt200 */
741 case 21: kbd.key = KBD_F10; break; /* kf10 vt200 */
743 case 23: kbd.key = KBD_F11; break; /* kf11 vt200 */
744 case 24: kbd.key = KBD_F12; break; /* kf12 vt200 */
746 /* Give preference to F11 and F12 over shifted F1 and F2. */
748 case 23: kbd.key = KBD_F1; kbd.modifier = KBD_MOD_SHIFT; break;
749 case 24: kbd.key = KBD_F2; kbd.modifier = KBD_MOD_SHIFT; break;
752 case 25: kbd.key = KBD_F3; kbd.modifier = KBD_MOD_SHIFT; break;
753 case 26: kbd.key = KBD_F4; kbd.modifier = KBD_MOD_SHIFT; break;
755 case 28: kbd.key = KBD_F5; kbd.modifier = KBD_MOD_SHIFT; break;
756 case 29: kbd.key = KBD_F6; kbd.modifier = KBD_MOD_SHIFT; break;
758 case 31: kbd.key = KBD_F7; kbd.modifier = KBD_MOD_SHIFT; break;
759 case 32: kbd.key = KBD_F8; kbd.modifier = KBD_MOD_SHIFT; break;
760 case 33: kbd.key = KBD_F9; kbd.modifier = KBD_MOD_SHIFT; break;
761 case 34: kbd.key = KBD_F10; kbd.modifier = KBD_MOD_SHIFT; break;
763 } break;
765 case 'R': resize_terminal(); break; /* CPR u6 */
766 case 'M': /* (DL) kmous xterm */
767 #ifdef CONFIG_MOUSE
768 el = decode_terminal_mouse_escape_sequence(itrm, ev, el, v);
769 #endif /* CONFIG_MOUSE */
770 break;
773 /* KBD_UNDEF here means it was unrecognized or a mouse event. */
774 if (kbd.key != KBD_UNDEF)
775 set_kbd_interlink_event(ev, kbd.key, kbd.modifier);
777 return el;
780 /* Decode an escape sequence that begins with SS3 (SINGLE SHIFT 3).
781 * These are used for application cursor keys and the application keypad.
782 * Return one of:
783 * -1 if the escape sequence is not yet complete; the caller sets a timer.
784 * 0 if the escape sequence should be parsed by some other function.
785 * The length of the escape sequence otherwise.
786 * Returning >0 does not imply this function has altered *ev. */
787 static int
788 decode_terminal_application_key(struct itrm *itrm, struct interlink_event *ev)
790 unsigned char c;
791 struct interlink_event_keyboard kbd = { KBD_UNDEF, KBD_MOD_NONE };
793 assert(itrm->in.queue.len >= 2);
794 assert(itrm->in.queue.data[0] == ASCII_ESC);
795 assert(itrm->in.queue.data[1] == 0x4F); /* == 'O', incidentally */
796 if_assert_failed return 0;
798 if (itrm->in.queue.len < 3) return -1;
799 /* According to ECMA-35 section 8.4, a single (possibly multibyte)
800 * character follows the SS3. We now assume the code identifies
801 * GL as the single-shift area and the designated set has 94
802 * characters. */
803 c = itrm->in.queue.data[2];
804 if (c < 0x21 || c > 0x7E) return 0;
806 switch (c) { /* Terminfo $TERM */
807 case ' ': kbd.key = ' '; break; /* xterm */
808 case 'A': kbd.key = KBD_UP; break; /* kcuu1 vt100 */
809 case 'B': kbd.key = KBD_DOWN; break; /* kcud1 vt100 */
810 case 'C': kbd.key = KBD_RIGHT; break; /* kcuf1 vt100 */
811 case 'D': kbd.key = KBD_LEFT; break; /* kcub1 vt100 */
812 case 'F': kbd.key = KBD_END; break; /* kend xterm */
813 case 'H': kbd.key = KBD_HOME; break; /* khome xterm */
814 case 'I': kbd.key = KBD_TAB; break; /* xterm */
815 case 'M': kbd.key = KBD_ENTER; break; /* kent vt100 */
816 /* FIXME: xterm generates ESC O 2 P for Shift-PF1 */
817 case 'P': kbd.key = KBD_F1; break; /* kf1 vt100 */
818 case 'Q': kbd.key = KBD_F2; break; /* kf2 vt100 */
819 case 'R': kbd.key = KBD_F3; break; /* kf3 vt100 */
820 case 'S': kbd.key = KBD_F4; break; /* kf4 vt100 */
821 case 'X': kbd.key = '='; break; /* xterm */
823 case 'j': case 'k': case 'l': case 'm': /* *+,- xterm */
824 case 'n': case 'o': case 'p': case 'q': /* ./01 xterm */
825 case 'r': case 's': case 't': case 'u': /* 2345 xterm */
826 case 'v': case 'w': case 'x': case 'y': /* 6789 xterm */
827 kbd.key = c - 'p' + '0'; break;
829 if (kbd.key != KBD_UNDEF)
830 copy_struct(&ev->info.keyboard, &kbd);
832 return 3; /* even if we didn't recognize it */
836 static void
837 set_kbd_event(struct interlink_event *ev, int key, int modifier)
839 switch (key) {
840 case ASCII_TAB:
841 key = KBD_TAB;
842 break;
843 #if defined(HAVE_SYS_CONSIO_H) || defined(HAVE_MACHINE_CONSOLE_H) /* BSD */
844 case ASCII_BS:
845 key = KBD_BS;
846 break;
847 case ASCII_DEL:
848 key = KBD_DEL;
849 break;
850 #else
851 case ASCII_BS:
852 case ASCII_DEL:
853 key = KBD_BS;
854 break;
855 #endif
856 case ASCII_LF:
857 case ASCII_CR:
858 key = KBD_ENTER;
859 break;
861 case ASCII_ESC:
862 key = KBD_ESC;
863 break;
865 default:
866 if (key < ' ') {
867 key += 'A' - 1;
868 modifier = KBD_MOD_CTRL;
872 set_kbd_interlink_event(ev, key, modifier);
875 static void
876 kbd_timeout(struct itrm *itrm)
878 struct interlink_event ev;
879 int el;
881 itrm->timer = TIMER_ID_UNDEF;
883 assertm(itrm->in.queue.len, "timeout on empty queue");
884 assert(!itrm->blocked); /* block_itrm should have killed itrm->timer */
885 if_assert_failed return;
887 if (can_read(itrm->in.std)) {
888 in_kbd(itrm);
889 return;
892 if (itrm->in.queue.len >= 2 && itrm->in.queue.data[0] == ASCII_ESC) {
893 /* This is used for ESC [ and ESC O. */
894 set_kbd_event(&ev, itrm->in.queue.data[1], KBD_MOD_ALT);
895 el = 2;
896 } else {
897 set_kbd_event(&ev, itrm->in.queue.data[0], KBD_MOD_NONE);
898 el = 1;
900 itrm_queue_event(itrm, (char *) &ev, sizeof(ev));
902 itrm->in.queue.len -= el;
903 if (itrm->in.queue.len)
904 memmove(itrm->in.queue.data, itrm->in.queue.data + el, itrm->in.queue.len);
906 while (process_queue(itrm));
909 /* Parse one event from itrm->in.queue and append to itrm->out.queue.
910 * Return the number of bytes removed from itrm->in.queue; at least 0.
911 * If this function leaves the queue not full, it also reenables reading
912 * from itrm->in.std. (Because it does not add to the queue, it never
913 * need disable reading.) On entry, the itrm must not be blocked. */
914 static int
915 process_queue(struct itrm *itrm)
917 struct interlink_event ev;
918 int el = 0;
920 if (!itrm->in.queue.len) goto return_without_event;
921 assert(!itrm->blocked);
922 if_assert_failed return 0; /* unlike goto, don't enable reading */
924 set_kbd_interlink_event(&ev, KBD_UNDEF, KBD_MOD_NONE);
926 #ifdef DEBUG_ITRM_QUEUE
928 int i;
930 /* Dump current queue in a readable form to stderr. */
931 for (i = 0; i < itrm->in.queue.len; i++)
932 if (itrm->in.queue.data[i] == ASCII_ESC)
933 fprintf(stderr, "ESC ");
934 else if (isprint(itrm->in.queue.data[i]) && !isspace(itrm->in.queue.data[i]))
935 fprintf(stderr, "%c ", itrm->in.queue.data[i]);
936 else
937 fprintf(stderr, "0x%02x ", itrm->in.queue.data[i]);
939 fprintf(stderr, "\n");
940 fflush(stderr);
942 #endif /* DEBUG_ITRM_QUEUE */
944 /* el == -1 means itrm->in.queue appears to be the beginning of an
945 * escape sequence but it is not yet complete. Set a timer;
946 * if it times out, then assume it wasn't an escape sequence
947 * after all.
948 * el == 0 means this function has not yet figured out what the data
949 * in itrm->in.queue is, but some possibilities remain.
950 * One of them will be chosen before returning.
951 * el > 0 means some bytes were successfully parsed from the beginning
952 * of itrm->in.queue and should now be removed from there.
953 * However, this does not always imply an event will be queued.
956 /* ELinks should also recognize U+009B CONTROL SEQUENCE INTRODUCER
957 * as meaning the same as ESC 0x5B, and U+008F SINGLE SHIFT THREE as
958 * meaning the same as ESC 0x4F, but those cannot yet be implemented
959 * because of bug 777: the UTF-8 decoder is run too late. */
960 if (itrm->in.queue.data[0] == ASCII_ESC) {
961 if (itrm->in.queue.len < 2) {
962 el = -1;
963 } else if (itrm->in.queue.data[1] == 0x5B /* CSI */) {
964 el = decode_terminal_escape_sequence(itrm, &ev);
965 } else if (itrm->in.queue.data[1] == 0x4F /* SS3 */) {
966 el = decode_terminal_application_key(itrm, &ev);
967 } else if (itrm->in.queue.data[1] == ASCII_ESC) {
968 /* ESC ESC can be either Alt-Esc or the
969 * beginning of e.g. ESC ESC 0x5B 0x41,
970 * which we should parse as Esc Up. */
971 if (itrm->in.queue.len < 3) {
972 /* Need more data to figure it out. */
973 el = -1;
974 } else if (itrm->in.queue.data[2] == 0x5B
975 || itrm->in.queue.data[2] == 0x4F) {
976 /* The first ESC appears to be followed
977 * by an escape sequence. Treat it as
978 * a standalone Esc. */
979 el = 1;
980 set_kbd_event(&ev, itrm->in.queue.data[0],
981 KBD_MOD_NONE);
982 } else {
983 /* The second ESC of ESC ESC is not the
984 * beginning of any known escape sequence.
985 * This must be Alt-Esc, then. */
986 el = 2;
987 set_kbd_event(&ev, itrm->in.queue.data[1],
988 KBD_MOD_ALT);
990 } else { /* ESC followed by something else */
991 el = 2;
992 set_kbd_event(&ev, itrm->in.queue.data[1],
993 KBD_MOD_ALT);
996 } else if (itrm->in.queue.data[0] == 0) {
997 static const struct term_event_keyboard os2xtd[256] = {
998 #include "terminal/key.inc"
1001 if (itrm->in.queue.len < 2)
1002 el = -1;
1003 else {
1004 el = 2;
1005 set_kbd_interlink_event(&ev,
1006 os2xtd[itrm->in.queue.data[1]].key,
1007 os2xtd[itrm->in.queue.data[1]].modifier);
1011 if (el == 0) {
1012 el = 1;
1013 set_kbd_event(&ev, itrm->in.queue.data[0], KBD_MOD_NONE);
1016 /* The call to decode_terminal_escape_sequence() might have changed the
1017 * keyboard event to a mouse event. */
1018 if (ev.ev == EVENT_MOUSE || ev.info.keyboard.key != KBD_UNDEF)
1019 itrm_queue_event(itrm, (char *) &ev, sizeof(ev));
1021 return_without_event:
1022 if (el == -1) {
1023 install_timer(&itrm->timer, ESC_TIMEOUT, (void (*)(void *)) kbd_timeout,
1024 itrm);
1025 return 0;
1026 } else {
1027 assertm(itrm->in.queue.len >= el, "event queue underflow");
1028 if_assert_failed { itrm->in.queue.len = el; }
1030 itrm->in.queue.len -= el;
1031 if (itrm->in.queue.len)
1032 memmove(itrm->in.queue.data, itrm->in.queue.data + el, itrm->in.queue.len);
1034 if (itrm->in.queue.len < ITRM_IN_QUEUE_SIZE)
1035 handle_itrm_stdin(itrm);
1037 return el;
1042 /* A select_handler_T read_func for itrm->in.std. This is called when
1043 * characters typed by the user arrive from the terminal. */
1044 static void
1045 in_kbd(struct itrm *itrm)
1047 int r;
1049 if (!can_read(itrm->in.std)) return;
1051 kill_timer(&itrm->timer);
1053 if (itrm->in.queue.len >= ITRM_IN_QUEUE_SIZE) {
1054 unhandle_itrm_stdin(itrm);
1055 while (process_queue(itrm));
1056 return;
1059 r = safe_read(itrm->in.std, itrm->in.queue.data + itrm->in.queue.len,
1060 ITRM_IN_QUEUE_SIZE - itrm->in.queue.len);
1061 if (r <= 0) {
1062 free_itrm(itrm);
1063 return;
1066 itrm->in.queue.len += r;
1067 if (itrm->in.queue.len > ITRM_IN_QUEUE_SIZE) {
1068 ERROR(gettext("Too many bytes read from the itrm!"));
1069 itrm->in.queue.len = ITRM_IN_QUEUE_SIZE;
1072 while (process_queue(itrm));
1075 /* Enable reading from itrm->in.std. ELinks will read any available
1076 * bytes from the tty into itrm->in.queue and then parse them.
1077 * Reading should be enabled whenever itrm->in.queue is not full and
1078 * itrm->blocked is 0. */
1079 static void
1080 handle_itrm_stdin(struct itrm *itrm)
1082 set_handlers(itrm->in.std, (select_handler_T) in_kbd, NULL,
1083 (select_handler_T) free_itrm, itrm);
1086 /* Disable reading from itrm->in.std. Reading should be disabled
1087 * whenever itrm->in.queue is full (there is no room for the data)
1088 * or itrm->blocked is 1 (other processes may read the data). */
1089 static void
1090 unhandle_itrm_stdin(struct itrm *itrm)
1092 set_handlers(itrm->in.std, (select_handler_T) NULL, NULL,
1093 (select_handler_T) free_itrm, itrm);