Remember fragment of the splitted char and decode it next time. Idea by Jonas.
[elinks.git] / src / terminal / event.c
blobcf8a25f527485b5e030680114f3fbfd153a9f106
1 /* Event system support routines. */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <errno.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #ifdef HAVE_UNISTD_H
11 #include <unistd.h>
12 #endif
14 #include "elinks.h"
16 #include "intl/gettext/libintl.h"
17 #include "main/main.h" /* terminate */
18 #include "main/object.h"
19 #include "session/session.h"
20 #include "terminal/draw.h"
21 #include "terminal/event.h"
22 #include "terminal/kbd.h"
23 #include "terminal/mouse.h"
24 #include "terminal/tab.h"
25 #include "terminal/terminal.h"
26 #include "terminal/screen.h"
27 #include "terminal/window.h"
28 #include "util/conv.h"
29 #include "util/error.h"
30 #include "util/memory.h"
31 #include "util/snprintf.h"
32 #include "util/string.h"
33 #include "viewer/timer.h"
36 /* Information used for communication between ELinks instances */
37 struct terminal_interlink {
38 /* How big the input queue is and how much is free */
39 int qlen;
40 int qfreespace;
42 /* UTF8 input key value decoding data. */
43 struct {
44 unicode_val_T ucs;
45 int len;
46 int min;
47 } utf_8;
49 /* This is the queue of events as coming from the other ELinks instance
50 * owning the hosting terminal. */
51 unsigned char input_queue[1];
55 void
56 term_send_event(struct terminal *term, struct term_event *ev)
58 struct window *win;
60 assert(ev && term);
61 if_assert_failed return;
63 switch (ev->ev) {
64 case EVENT_INIT:
65 case EVENT_RESIZE:
67 int width = ev->info.size.width;
68 int height = ev->info.size.height;
70 if (width < 0 || height < 0) {
71 ERROR(gettext("Bad terminal size: %d, %d"),
72 width, height);
73 break;
76 resize_screen(term, width, height);
77 erase_screen(term);
78 /* Fall through */
80 case EVENT_REDRAW:
81 /* Nasty hack to avoid assertion failures when doing -remote
82 * stuff and the client exits right away */
83 if (!term->screen->image) break;
85 clear_terminal(term);
86 term->redrawing = TREDRAW_DELAYED;
87 /* Note that you do NOT want to ever go and create new
88 * window inside EVENT_INIT handler (it'll get second
89 * EVENT_INIT here). Perhaps the best thing you could do
90 * is registering a bottom-half handler which will open
91 * additional windows.
92 * --pasky */
93 if (ev->ev == EVENT_RESIZE) {
94 /* We want to propagate EVENT_RESIZE even to inactive
95 * tabs! Nothing wrong will get drawn (in the final
96 * result) as the active tab is always the first one,
97 * thus will be drawn last here. Thanks, Witek!
98 * --pasky */
99 foreachback (win, term->windows)
100 win->handler(win, ev);
102 } else {
103 foreachback (win, term->windows)
104 if (!inactive_tab(win))
105 win->handler(win, ev);
107 term->redrawing = TREDRAW_READY;
108 break;
110 case EVENT_MOUSE:
111 case EVENT_KBD:
112 case EVENT_ABORT:
113 assert(!list_empty(term->windows));
114 if_assert_failed break;
116 /* We need to send event to correct tab, not to the first one. --karpov */
117 /* ...if we want to send it to a tab at all. --pasky */
118 win = term->windows.next;
119 if (win->type == WINDOW_TAB) {
120 win = get_current_tab(term);
121 assertm(win, "No tab to send the event to!");
122 if_assert_failed return;
125 win->handler(win, ev);
129 static void
130 term_send_ucs(struct terminal *term, struct term_event *ev, unicode_val_T u)
132 unsigned char *recoded;
134 recoded = u2cp_no_nbsp(u, get_opt_codepage_tree(term->spec, "charset"));
135 if (!recoded) recoded = "*";
136 while (*recoded) {
137 ev->info.keyboard.key = *recoded;
138 term_send_event(term, ev);
139 recoded++;
143 static void
144 check_terminal_name(struct terminal *term, struct terminal_info *info)
146 unsigned char name[MAX_TERM_LEN + 10];
147 int i;
149 /* We check TERM env. var for sanity, and fallback to _template_ if
150 * needed. This way we prevent elinks.conf potential corruption. */
151 for (i = 0; info->name[i]; i++) {
152 if (isident(info->name[i])) continue;
154 usrerror(_("Warning: terminal name contains illicit chars.", term));
155 return;
158 snprintf(name, sizeof(name), "terminal.%s", info->name);
160 /* Unlock the default _template_ option tree that was asigned by
161 * init_term() and get the correct one. */
162 object_unlock(term->spec);
163 term->spec = get_opt_rec(config_options, name);
164 object_lock(term->spec);
165 #ifdef CONFIG_UTF_8
166 /* Probably not best place for set this. But now we finally have
167 * term->spec and term->utf8 should be set before decode session info.
168 * --Scrool */
169 term->utf8 = get_opt_bool_tree(term->spec, "utf_8_io");
170 #endif /* CONFIG_UTF_8 */
173 #ifdef CONFIG_MOUSE
174 static int
175 ignore_mouse_event(struct terminal *term, struct term_event *ev)
177 struct term_event_mouse *prev = &term->prev_mouse_event;
178 struct term_event_mouse *current = &ev->info.mouse;
180 if (check_mouse_action(ev, B_UP)
181 && current->y == prev->y
182 && (current->button & ~BM_ACT) == (prev->button & ~BM_ACT)) {
183 do_not_ignore_next_mouse_event(term);
185 return 1;
188 copy_struct(prev, current);
190 return 0;
192 #endif
194 static int
195 handle_interlink_event(struct terminal *term, struct term_event *ev)
197 struct terminal_info *info = NULL;
198 struct terminal_interlink *interlink = term->interlink;
200 switch (ev->ev) {
201 case EVENT_INIT:
202 if (interlink->qlen < TERMINAL_INFO_SIZE)
203 return 0;
205 info = (struct terminal_info *) ev;
207 if (interlink->qlen < TERMINAL_INFO_SIZE + info->length)
208 return 0;
210 info->name[MAX_TERM_LEN - 1] = 0;
211 check_terminal_name(term, info);
213 memcpy(term->cwd, info->cwd, MAX_CWD_LEN);
214 term->cwd[MAX_CWD_LEN - 1] = 0;
216 term->environment = info->system_env;
218 /* We need to make sure that it is possible to draw on the
219 * terminal screen before decoding the session info so that
220 * handling of bad URL syntax by openning msg_box() will be
221 * possible. */
222 term_send_event(term, ev);
224 /* Either the initialization of the first session failed or we
225 * are doing a remote session so quit.*/
226 if (!decode_session_info(term, info)) {
227 destroy_terminal(term);
228 /* Make sure the user is notified if the initialization
229 * of the first session fails. */
230 if (program.terminate) {
231 usrerror(_("Failed to create session.", term));
232 program.retval = RET_FATAL;
234 return 0;
237 ev->ev = EVENT_REDRAW;
238 /* Fall through */
239 case EVENT_REDRAW:
240 case EVENT_RESIZE:
241 term_send_event(term, ev);
242 break;
244 case EVENT_MOUSE:
245 #ifdef CONFIG_MOUSE
246 reset_timer();
247 if (!ignore_mouse_event(term, ev))
248 term_send_event(term, ev);
249 #endif
250 break;
252 case EVENT_KBD:
254 #ifndef CONFIG_UTF_8
255 int utf8_io = -1;
256 #endif /* CONFIG_UTF_8 */
257 int key = get_kbd_key(ev);
259 reset_timer();
261 if (check_kbd_modifier(ev, KBD_MOD_CTRL) && toupper(key) == 'L') {
262 redraw_terminal_cls(term);
263 break;
265 } else if (key == KBD_CTRL_C) {
266 destroy_terminal(term);
267 return 0;
270 if (interlink->utf_8.len) {
271 #ifdef CONFIG_UTF_8
272 if ((key & 0xC0) == 0x80 && term->utf8)
273 #else
274 utf8_io = get_opt_bool_tree(term->spec, "utf_8_io");
275 if ((key & 0xC0) == 0x80 && utf8_io)
276 #endif /* CONFIG_UTF_8 */
278 interlink->utf_8.ucs <<= 6;
279 interlink->utf_8.ucs |= key & 0x3F;
280 if (! --interlink->utf_8.len) {
281 unicode_val_T u = interlink->utf_8.ucs;
283 if (u < interlink->utf_8.min)
284 u = UCS_NO_CHAR;
285 term_send_ucs(term, ev, u);
287 break;
289 } else {
290 interlink->utf_8.len = 0;
291 term_send_ucs(term, ev, UCS_NO_CHAR);
295 #ifdef CONFIG_UTF_8
296 if (key < 0x80 || key > 0xFF || !term->utf8)
297 #else
298 if (key < 0x80 || key > 0xFF
299 || (utf8_io == -1
300 ? !get_opt_bool_tree(term->spec, "utf_8_io")
301 : !utf8_io))
302 #endif /* CONFIG_UTF_8 */
304 term_send_event(term, ev);
305 break;
307 } else if ((key & 0xC0) == 0xC0 && (key & 0xFE) != 0xFE) {
308 unsigned int mask, cov = 0x80;
309 int len = 0;
311 for (mask = 0x80; key & mask; mask >>= 1) {
312 len++;
313 interlink->utf_8.min = cov;
314 cov = 1 << (1 + 5 * len);
317 interlink->utf_8.len = len - 1;
318 interlink->utf_8.ucs = key & (mask - 1);
319 break;
322 term_send_ucs(term, ev, UCS_NO_CHAR);
323 break;
326 case EVENT_ABORT:
327 destroy_terminal(term);
328 return 0;
330 default:
331 ERROR(gettext("Bad event %d"), ev->ev);
334 /* For EVENT_INIT we read a liitle more */
335 if (info) return TERMINAL_INFO_SIZE + info->length;
336 return sizeof(*ev);
339 void
340 in_term(struct terminal *term)
342 struct terminal_interlink *interlink = term->interlink;
343 ssize_t r;
344 unsigned char *iq;
346 if (!interlink
347 || !interlink->qfreespace
348 || interlink->qfreespace - interlink->qlen > ALLOC_GR) {
349 int qlen = interlink ? interlink->qlen : 0;
350 int queuesize = ((qlen + ALLOC_GR) & ~(ALLOC_GR - 1));
351 int newsize = sizeof(*interlink) + queuesize;
353 interlink = mem_realloc(interlink, newsize);
354 if (!interlink) {
355 destroy_terminal(term);
356 return;
359 /* Blank the members for the first allocation */
360 if (!term->interlink)
361 memset(interlink, 0, sizeof(*interlink));
363 term->interlink = interlink;
364 interlink->qfreespace = queuesize - interlink->qlen;
367 iq = interlink->input_queue;
368 r = safe_read(term->fdin, iq + interlink->qlen, interlink->qfreespace);
369 if (r <= 0) {
370 if (r == -1 && errno != ECONNRESET)
371 ERROR(gettext("Could not read event: %d (%s)"),
372 errno, (unsigned char *) strerror(errno));
374 destroy_terminal(term);
375 return;
378 interlink->qlen += r;
379 interlink->qfreespace -= r;
381 while (interlink->qlen >= sizeof(struct term_event)) {
382 struct term_event *ev = (struct term_event *) iq;
383 int event_size = handle_interlink_event(term, ev);
385 /* If the event was not handled save the bytes in the queue for
386 * later in case more stuff is read later. */
387 if (!event_size) break;
389 /* Acount for the handled bytes */
390 interlink->qlen -= event_size;
391 interlink->qfreespace += event_size;
393 /* If there are no more bytes to handle stop else move next
394 * event bytes to the front of the queue. */
395 if (!interlink->qlen) break;
396 memmove(iq, iq + event_size, interlink->qlen);