[wip] Initial stab on inline images support, with many problems
[elinks/images.git] / src / terminal / event.c
blob0a5aaa3767196d890d6cf672884ad751b91f494f
1 /** Event system support routines.
2 * @file */
4 #ifdef HAVE_CONFIG_H
5 #include "config.h"
6 #endif
8 #include <errno.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #ifdef HAVE_UNISTD_H
12 #include <unistd.h>
13 #endif
15 #include "elinks.h"
17 #include "intl/gettext/libintl.h"
18 #include "main/main.h" /* terminate */
19 #include "main/object.h"
20 #include "session/session.h"
21 #include "terminal/draw.h"
22 #include "terminal/event.h"
23 #include "terminal/kbd.h"
24 #include "terminal/mouse.h"
25 #include "terminal/tab.h"
26 #include "terminal/terminal.h"
27 #include "terminal/screen.h"
28 #include "terminal/window.h"
29 #include "util/conv.h"
30 #include "util/error.h"
31 #include "util/memory.h"
32 #include "util/snprintf.h"
33 #include "util/string.h"
34 #include "viewer/text/textarea.h"
35 #include "viewer/timer.h"
38 /** Information used for communication between ELinks instances */
39 struct terminal_interlink {
40 /** How big the input queue is */
41 int qlen;
42 /** How much is free */
43 int qfreespace;
45 /** UTF-8 input key value decoding data. */
46 struct {
47 unicode_val_T ucs;
48 int len;
49 unicode_val_T min;
50 /** Modifier keys from the key event that carried the
51 * first byte of the character. We need this because
52 * ELinks sees e.g. ESC U+00F6 as 0x1B 0xC3 0xB6 and
53 * converts it to Alt-0xC3 0xB6, attaching the
54 * modifier to the first byte only. */
55 term_event_modifier_T modifier;
56 } utf8;
58 /** This is the queue of events as coming from the other
59 * ELinks instance owning the hosting terminal. */
60 unsigned char input_queue[1];
64 void
65 term_send_event(struct terminal *term, struct term_event *ev)
67 struct window *win;
69 assert(ev && term);
70 if_assert_failed return;
72 switch (ev->ev) {
73 case EVENT_INIT:
74 case EVENT_RESIZE:
76 int width = ev->info.size.width;
77 int height = ev->info.size.height;
79 if (width < 0 || height < 0) {
80 ERROR(gettext("Bad terminal size: %d, %d"),
81 width, height);
82 break;
85 resize_screen(term, width, height, ev->info.size.xres, ev->info.size.yres);
86 erase_screen(term);
87 /* Fall through */
89 case EVENT_REDRAW:
90 /* Nasty hack to avoid assertion failures when doing -remote
91 * stuff and the client exits right away */
92 if (!term->screen->image) break;
94 clear_terminal(term);
95 term->redrawing = TREDRAW_DELAYED;
96 /* Note that you do NOT want to ever go and create new
97 * window inside EVENT_INIT handler (it'll get second
98 * EVENT_INIT here). Perhaps the best thing you could do
99 * is registering a bottom-half handler which will open
100 * additional windows.
101 * --pasky */
102 if (ev->ev == EVENT_RESIZE) {
103 /* We want to propagate EVENT_RESIZE even to inactive
104 * tabs! Nothing wrong will get drawn (in the final
105 * result) as the active tab is always the first one,
106 * thus will be drawn last here. Thanks, Witek!
107 * --pasky */
108 foreachback (win, term->windows)
109 win->handler(win, ev);
111 } else {
112 foreachback (win, term->windows)
113 if (!inactive_tab(win))
114 win->handler(win, ev);
116 term->redrawing = TREDRAW_READY;
117 break;
119 case EVENT_MOUSE:
120 case EVENT_KBD:
121 case EVENT_ABORT:
122 assert(!list_empty(term->windows));
123 if_assert_failed break;
125 /* We need to send event to correct tab, not to the first one. --karpov */
126 /* ...if we want to send it to a tab at all. --pasky */
127 win = term->windows.next;
128 if (win->type == WINDOW_TAB) {
129 win = get_current_tab(term);
130 assertm(win != NULL, "No tab to send the event to!");
131 if_assert_failed return;
134 win->handler(win, ev);
138 static void
139 term_send_ucs(struct terminal *term, unicode_val_T u,
140 term_event_modifier_T modifier)
142 #ifdef CONFIG_UTF8
143 struct term_event ev;
145 set_kbd_term_event(&ev, u, modifier);
146 term_send_event(term, &ev);
147 #else /* !CONFIG_UTF8 */
148 struct term_event ev;
149 const unsigned char *recoded;
151 set_kbd_term_event(&ev, KBD_UNDEF, modifier);
152 recoded = u2cp_no_nbsp(u, get_terminal_codepage(term));
153 if (!recoded) recoded = "*";
154 while (*recoded) {
155 ev.info.keyboard.key = *recoded;
156 term_send_event(term, &ev);
157 recoded++;
159 #endif /* !CONFIG_UTF8 */
162 static void
163 check_terminal_name(struct terminal *term, struct terminal_info *info)
165 unsigned char name[MAX_TERM_LEN + 10];
166 int i;
168 /* We check TERM env. var for sanity, and fallback to _template_ if
169 * needed. This way we prevent elinks.conf potential corruption. */
170 for (i = 0; info->name[i]; i++) {
171 if (isident(info->name[i])) continue;
173 usrerror(_("Warning: terminal name contains illicit chars.", term));
174 return;
177 snprintf(name, sizeof(name), "terminal.%s", info->name);
179 /* Unlock the default _template_ option tree that was asigned by
180 * init_term() and get the correct one. */
181 object_unlock(term->spec);
182 term->spec = get_opt_rec(config_options, name);
183 object_lock(term->spec);
184 #ifdef CONFIG_UTF8
185 /* Probably not best place for set this. But now we finally have
186 * term->spec and term->utf8 should be set before decode session info.
187 * --Scrool */
188 term->utf8_cp = is_cp_utf8(get_terminal_codepage(term));
189 /* Force UTF-8 I/O if the UTF-8 charset is selected. Various
190 * places assume that the terminal's charset is unibyte if
191 * UTF-8 I/O is disabled. (bug 827) */
192 term->utf8_io = term->utf8_cp
193 || get_opt_bool_tree(term->spec, "utf_8_io", NULL);
194 #endif /* CONFIG_UTF8 */
197 #ifdef CONFIG_MOUSE
198 static int
199 ignore_mouse_event(struct terminal *term, struct term_event *ev)
201 struct term_event_mouse *prev = &term->prev_mouse_event;
202 struct term_event_mouse *current = &ev->info.mouse;
204 if (check_mouse_action(ev, B_UP)
205 && current->y == prev->y
206 && (current->button & ~BM_ACT) == (prev->button & ~BM_ACT)) {
207 do_not_ignore_next_mouse_event(term);
209 return 1;
212 copy_struct(prev, current);
214 return 0;
216 #endif
218 static int
219 handle_interlink_event(struct terminal *term, struct interlink_event *ilev)
221 struct terminal_info *info = NULL;
222 struct terminal_interlink *interlink = term->interlink;
223 struct term_event tev;
225 switch (ilev->ev) {
226 case EVENT_INIT:
227 if (interlink->qlen < TERMINAL_INFO_SIZE)
228 return 0;
230 info = (struct terminal_info *) ilev;
232 if (interlink->qlen < TERMINAL_INFO_SIZE + info->length)
233 return 0;
235 info->name[MAX_TERM_LEN - 1] = 0;
236 check_terminal_name(term, info);
238 memcpy(term->cwd, info->cwd, MAX_CWD_LEN);
239 term->cwd[MAX_CWD_LEN - 1] = 0;
241 term->environment = info->system_env;
243 /* We need to make sure that it is possible to draw on the
244 * terminal screen before decoding the session info so that
245 * handling of bad URL syntax by openning msg_box() will be
246 * possible. */
247 set_init_term_event(&tev,
248 ilev->info.size.width,
249 ilev->info.size.height,
250 ilev->info.size.xres,
251 ilev->info.size.yres);
252 term_send_event(term, &tev);
254 /* Either the initialization of the first session failed or we
255 * are doing a remote session so quit.*/
256 if (!decode_session_info(term, info)) {
257 destroy_terminal(term);
258 /* Make sure the user is notified if the initialization
259 * of the first session fails. */
260 if (program.terminate) {
261 usrerror(_("Failed to create session.", term));
262 program.retval = RET_FATAL;
264 return 0;
267 ilev->ev = EVENT_REDRAW;
268 /* Fall through */
269 case EVENT_REDRAW:
270 case EVENT_RESIZE:
271 set_wh_term_event(&tev, ilev->ev,
272 ilev->info.size.width,
273 ilev->info.size.height,
274 ilev->info.size.xres,
275 ilev->info.size.yres);
276 term_send_event(term, &tev);
278 /* If textarea_data is set and the terminal is not blocked,
279 * then this resize event must be the result of exiting the
280 * external editor. */
281 if (term->textarea_data && term->blocked == -1)
282 textarea_edit(1, term, NULL, NULL, NULL);
284 break;
286 case EVENT_MOUSE:
287 #ifdef CONFIG_MOUSE
288 reset_timer();
289 tev.ev = ilev->ev;
290 copy_struct(&tev.info.mouse, &ilev->info.mouse);
291 if (!ignore_mouse_event(term, &tev))
292 term_send_event(term, &tev);
293 #endif
294 break;
296 case EVENT_KBD:
298 int utf8_io = -1;
299 int key = ilev->info.keyboard.key;
300 term_event_modifier_T modifier = ilev->info.keyboard.modifier;
302 if (key >= 0x100)
303 key = -key;
305 reset_timer();
307 if (key == KBD_CTRL_C) {
308 destroy_terminal(term);
309 return 0;
312 /* Character Conversions. */
313 #ifdef CONFIG_UTF8
314 /* struct term_event_keyboard carries UCS-4.
315 * - If the "utf_8_io" option is true or the "charset"
316 * option refers to UTF-8, then term->utf8_io is true,
317 * and handle_interlink_event() converts from UTF-8
318 * to UCS-4.
319 * - Otherwise, handle_interlink_event() converts from
320 * the codepage specified with the "charset" option
321 * to UCS-4. */
322 utf8_io = term->utf8_io;
323 #else
324 /* struct term_event_keyboard carries bytes in the
325 * charset of the terminal.
326 * - If the "utf_8_io" option is true, then
327 * handle_interlink_event() converts from UTF-8 to
328 * UCS-4, and term_send_ucs() converts from UCS-4 to
329 * the codepage specified with the "charset" option;
330 * this codepage cannot be UTF-8.
331 * - Otherwise, handle_interlink_event() passes the
332 * bytes straight through. */
333 utf8_io = get_opt_bool_tree(term->spec, "utf_8_io", NULL);
334 #endif /* CONFIG_UTF8 */
336 /* In UTF-8 byte sequences that have more than one byte, the
337 * first byte is between 0xC0 and 0xFF and the remaining bytes
338 * are between 0x80 and 0xBF. If there is just one byte, then
339 * it is between 0x00 and 0x7F and it is straight ASCII.
340 * (All 'betweens' are inclusive.) */
342 if (interlink->utf8.len) {
343 /* A previous call to handle_interlink_event
344 * got a UTF-8 start byte. */
346 if (key >= 0x80 && key <= 0xBF && utf8_io) {
347 /* This is a UTF-8 continuation byte. */
349 interlink->utf8.ucs <<= 6;
350 interlink->utf8.ucs |= key & 0x3F;
351 if (! --interlink->utf8.len) {
352 unicode_val_T u = interlink->utf8.ucs;
354 /* UTF-8 allows neither overlong
355 * sequences nor surrogates. */
356 if (u < interlink->utf8.min
357 || is_utf16_surrogate(u))
358 u = UCS_REPLACEMENT_CHARACTER;
359 term_send_ucs(term, u,
360 term->interlink->utf8.modifier);
362 break;
364 } else {
365 /* The byte sequence for this character
366 * is ending prematurely. Send
367 * UCS_REPLACEMENT_CHARACTER for the
368 * terminated character, but don't break;
369 * let this byte be handled below. */
371 interlink->utf8.len = 0;
372 term_send_ucs(term, UCS_REPLACEMENT_CHARACTER,
373 term->interlink->utf8.modifier);
377 /* Note: We know that key <= 0xFF. */
379 if (key < 0x80 || !utf8_io) {
380 /* This byte is not part of a multibyte character
381 * encoding: either it is outside of the ranges for
382 * UTF-8 start and continuation bytes or UTF-8 I/O mode
383 * is disabled. */
385 #ifdef CONFIG_UTF8
386 if (key >= 0 && !utf8_io) {
387 /* Not special and UTF-8 mode is disabled:
388 * recode from the terminal charset to UCS-4. */
390 key = cp2u(get_terminal_codepage(term), key);
391 term_send_ucs(term, key, modifier);
392 break;
394 #endif /* !CONFIG_UTF8 */
396 /* It must be special (e.g., F1 or Enter)
397 * or a single-byte UTF-8 character. */
398 set_kbd_term_event(&tev, key, modifier);
399 term_send_event(term, &tev);
400 break;
402 } else if ((key & 0xC0) == 0xC0 && (key & 0xFE) != 0xFE) {
403 /* This is a UTF-8 start byte. */
405 /* Minimum character values for UTF-8 octet
406 * sequences of each length, from RFC 2279.
407 * According to the RFC, UTF-8 decoders should
408 * reject characters that are encoded with
409 * more octets than necessary. (RFC 3629,
410 * which ELinks does not yet otherwise follow,
411 * tightened the "should" to "MUST".) */
412 static const unicode_val_T min[] = {
413 0x00000080, /* ... 0x000007FF with 2 octets */
414 0x00000800, /* ... 0x0000FFFF with 3 octets */
415 0x00010000, /* ... 0x001FFFFF with 4 octets */
416 0x00200000, /* ... 0x03FFFFFF with 5 octets */
417 0x04000000 /* ... 0x7FFFFFFF with 6 octets */
419 unsigned int mask;
420 int len = 0;
422 /* Set @len = the number of contiguous 1's
423 * in the most significant bits of the first
424 * octet, i.e. @key. It is also the number
425 * of octets in the character. Leave @mask
426 * pointing to the first 0 bit found. */
427 for (mask = 0x80; key & mask; mask >>= 1)
428 len++;
430 /* This will hold because @key was checked above. */
431 assert(len >= 2 && len <= 6);
432 if_assert_failed goto invalid_utf8_start_byte;
434 interlink->utf8.min = min[len - 2];
435 interlink->utf8.len = len - 1;
436 interlink->utf8.ucs = key & (mask - 1);
437 interlink->utf8.modifier = modifier;
438 break;
441 invalid_utf8_start_byte:
442 term_send_ucs(term, UCS_REPLACEMENT_CHARACTER, modifier);
443 break;
446 case EVENT_ABORT:
447 destroy_terminal(term);
448 return 0;
450 default:
451 ERROR(gettext("Bad event %d"), ilev->ev);
454 /* For EVENT_INIT we read a liitle more */
455 if (info) return TERMINAL_INFO_SIZE + info->length;
456 return sizeof(*ilev);
459 void
460 in_term(struct terminal *term)
462 struct terminal_interlink *interlink = term->interlink;
463 ssize_t r;
464 unsigned char *iq;
466 /* Mark this as the most recently used terminal. */
467 move_to_top_of_list(terminals, term);
469 if (!interlink
470 || !interlink->qfreespace
471 || interlink->qfreespace - interlink->qlen > ALLOC_GR) {
472 int qlen = interlink ? interlink->qlen : 0;
473 int queuesize = ((qlen + ALLOC_GR) & ~(ALLOC_GR - 1));
474 int newsize = sizeof(*interlink) + queuesize;
476 interlink = mem_realloc(interlink, newsize);
477 if (!interlink) {
478 destroy_terminal(term);
479 return;
482 /* Blank the members for the first allocation */
483 if (!term->interlink)
484 memset(interlink, 0, sizeof(*interlink));
486 term->interlink = interlink;
487 interlink->qfreespace = queuesize - interlink->qlen;
490 iq = interlink->input_queue;
491 r = safe_read(term->fdin, iq + interlink->qlen, interlink->qfreespace);
492 if (r <= 0) {
493 if (r == -1 && errno != ECONNRESET)
494 ERROR(gettext("Could not read event: %d (%s)"),
495 errno, (unsigned char *) strerror(errno));
497 destroy_terminal(term);
498 return;
501 interlink->qlen += r;
502 interlink->qfreespace -= r;
504 while (interlink->qlen >= sizeof(struct interlink_event)) {
505 struct interlink_event *ev = (struct interlink_event *) iq;
506 int event_size = handle_interlink_event(term, ev);
508 /* If the event was not handled save the bytes in the queue for
509 * later in case more stuff is read later. */
510 if (!event_size) break;
512 /* Acount for the handled bytes */
513 interlink->qlen -= event_size;
514 interlink->qfreespace += event_size;
516 /* If there are no more bytes to handle stop else move next
517 * event bytes to the front of the queue. */
518 if (!interlink->qlen) break;
519 memmove(iq, iq + event_size, interlink->qlen);