1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-11-02 08:57:19 -04:00
elinks/src/terminal/event.c
Kalle Olavi Niemitalo 84e19d0e4e bug 1054: Don't abort downloads when closing a terminal.
Except if they have external handlers.

When ELinks receives an event from a terminal, move that terminal to
the beginning of the global "terminals" list, so that the terminals
are always sorted according to the time of the most recent use.  Note,
this affects the numbering of bookmark folders in session snapshots.

Add get_default_terminal(), which returns the most recently used
terminal that is still open.  Use that in various places that
previously used terminals.prev or terminals.next.  Four functions
fetch the size of the terminal for User-Agent headers, and
get_default_terminal() is not really right, but neither was the
original code; add TODO comments in those functions.

When the user chooses "Background and Notify", associate the download
with the terminal where the dialog box is.  So any later messages will
then appear in that terminal, if it is still open.  However, don't
change the terminal if the download has an external handler.

When a download gets some data, don't immediately check the associated
terminal.  Instead, wait for the download to end.  Then, if the
terminal of the download has been closed, use get_default_terminal()
instead.  If there is no default terminal either, just skip any
message boxes.
2008-10-15 23:29:25 +03:00

527 lines
14 KiB
C

/** Event system support routines.
* @file */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "elinks.h"
#include "intl/gettext/libintl.h"
#include "main/main.h" /* terminate */
#include "main/object.h"
#include "session/session.h"
#include "terminal/draw.h"
#include "terminal/event.h"
#include "terminal/kbd.h"
#include "terminal/mouse.h"
#include "terminal/tab.h"
#include "terminal/terminal.h"
#include "terminal/screen.h"
#include "terminal/window.h"
#include "util/conv.h"
#include "util/error.h"
#include "util/memory.h"
#include "util/snprintf.h"
#include "util/string.h"
#include "viewer/text/textarea.h"
#include "viewer/timer.h"
/** Information used for communication between ELinks instances */
struct terminal_interlink {
/** How big the input queue is */
int qlen;
/** How much is free */
int qfreespace;
/** UTF-8 input key value decoding data. */
struct {
unicode_val_T ucs;
int len;
unicode_val_T min;
/** Modifier keys from the key event that carried the
* first byte of the character. We need this because
* ELinks sees e.g. ESC U+00F6 as 0x1B 0xC3 0xB6 and
* converts it to Alt-0xC3 0xB6, attaching the
* modifier to the first byte only. */
term_event_modifier_T modifier;
} utf8;
/** This is the queue of events as coming from the other
* ELinks instance owning the hosting terminal. */
unsigned char input_queue[1];
};
void
term_send_event(struct terminal *term, struct term_event *ev)
{
struct window *win;
assert(ev && term);
if_assert_failed return;
switch (ev->ev) {
case EVENT_INIT:
case EVENT_RESIZE:
{
int width = ev->info.size.width;
int height = ev->info.size.height;
if (width < 0 || height < 0) {
ERROR(gettext("Bad terminal size: %d, %d"),
width, height);
break;
}
resize_screen(term, width, height);
erase_screen(term);
/* Fall through */
}
case EVENT_REDRAW:
/* Nasty hack to avoid assertion failures when doing -remote
* stuff and the client exits right away */
if (!term->screen->image) break;
clear_terminal(term);
term->redrawing = TREDRAW_DELAYED;
/* Note that you do NOT want to ever go and create new
* window inside EVENT_INIT handler (it'll get second
* EVENT_INIT here). Perhaps the best thing you could do
* is registering a bottom-half handler which will open
* additional windows.
* --pasky */
if (ev->ev == EVENT_RESIZE) {
/* We want to propagate EVENT_RESIZE even to inactive
* tabs! Nothing wrong will get drawn (in the final
* result) as the active tab is always the first one,
* thus will be drawn last here. Thanks, Witek!
* --pasky */
foreachback (win, term->windows)
win->handler(win, ev);
} else {
foreachback (win, term->windows)
if (!inactive_tab(win))
win->handler(win, ev);
}
term->redrawing = TREDRAW_READY;
break;
case EVENT_MOUSE:
case EVENT_KBD:
case EVENT_ABORT:
assert(!list_empty(term->windows));
if_assert_failed break;
/* We need to send event to correct tab, not to the first one. --karpov */
/* ...if we want to send it to a tab at all. --pasky */
win = term->windows.next;
if (win->type == WINDOW_TAB) {
win = get_current_tab(term);
assertm(win != NULL, "No tab to send the event to!");
if_assert_failed return;
}
win->handler(win, ev);
}
}
static void
term_send_ucs(struct terminal *term, unicode_val_T u,
term_event_modifier_T modifier)
{
#ifdef CONFIG_UTF8
struct term_event ev;
set_kbd_term_event(&ev, u, modifier);
term_send_event(term, &ev);
#else /* !CONFIG_UTF8 */
struct term_event ev;
const unsigned char *recoded;
set_kbd_term_event(&ev, KBD_UNDEF, modifier);
recoded = u2cp_no_nbsp(u, get_opt_codepage_tree(term->spec, "charset",
NULL));
if (!recoded) recoded = "*";
while (*recoded) {
ev.info.keyboard.key = *recoded;
term_send_event(term, &ev);
recoded++;
}
#endif /* !CONFIG_UTF8 */
}
static void
check_terminal_name(struct terminal *term, struct terminal_info *info)
{
unsigned char name[MAX_TERM_LEN + 10];
int i;
/* We check TERM env. var for sanity, and fallback to _template_ if
* needed. This way we prevent elinks.conf potential corruption. */
for (i = 0; info->name[i]; i++) {
if (isident(info->name[i])) continue;
usrerror(_("Warning: terminal name contains illicit chars.", term));
return;
}
snprintf(name, sizeof(name), "terminal.%s", info->name);
/* Unlock the default _template_ option tree that was asigned by
* init_term() and get the correct one. */
object_unlock(term->spec);
term->spec = get_opt_rec(config_options, name);
object_lock(term->spec);
#ifdef CONFIG_UTF8
/* Probably not best place for set this. But now we finally have
* term->spec and term->utf8 should be set before decode session info.
* --Scrool */
term->utf8_cp = is_cp_utf8(get_opt_codepage_tree(term->spec,
"charset", NULL));
/* Force UTF-8 I/O if the UTF-8 charset is selected. Various
* places assume that the terminal's charset is unibyte if
* UTF-8 I/O is disabled. (bug 827) */
term->utf8_io = term->utf8_cp
|| get_opt_bool_tree(term->spec, "utf_8_io", NULL);
#endif /* CONFIG_UTF8 */
}
#ifdef CONFIG_MOUSE
static int
ignore_mouse_event(struct terminal *term, struct term_event *ev)
{
struct term_event_mouse *prev = &term->prev_mouse_event;
struct term_event_mouse *current = &ev->info.mouse;
if (check_mouse_action(ev, B_UP)
&& current->y == prev->y
&& (current->button & ~BM_ACT) == (prev->button & ~BM_ACT)) {
do_not_ignore_next_mouse_event(term);
return 1;
}
copy_struct(prev, current);
return 0;
}
#endif
static int
handle_interlink_event(struct terminal *term, struct interlink_event *ilev)
{
struct terminal_info *info = NULL;
struct terminal_interlink *interlink = term->interlink;
struct term_event tev;
switch (ilev->ev) {
case EVENT_INIT:
if (interlink->qlen < TERMINAL_INFO_SIZE)
return 0;
info = (struct terminal_info *) ilev;
if (interlink->qlen < TERMINAL_INFO_SIZE + info->length)
return 0;
info->name[MAX_TERM_LEN - 1] = 0;
check_terminal_name(term, info);
memcpy(term->cwd, info->cwd, MAX_CWD_LEN);
term->cwd[MAX_CWD_LEN - 1] = 0;
term->environment = info->system_env;
/* We need to make sure that it is possible to draw on the
* terminal screen before decoding the session info so that
* handling of bad URL syntax by openning msg_box() will be
* possible. */
set_init_term_event(&tev,
ilev->info.size.width,
ilev->info.size.height);
term_send_event(term, &tev);
/* Either the initialization of the first session failed or we
* are doing a remote session so quit.*/
if (!decode_session_info(term, info)) {
destroy_terminal(term);
/* Make sure the user is notified if the initialization
* of the first session fails. */
if (program.terminate) {
usrerror(_("Failed to create session.", term));
program.retval = RET_FATAL;
}
return 0;
}
ilev->ev = EVENT_REDRAW;
/* Fall through */
case EVENT_REDRAW:
case EVENT_RESIZE:
set_wh_term_event(&tev, ilev->ev,
ilev->info.size.width,
ilev->info.size.height);
term_send_event(term, &tev);
/* If textarea_data is set and the terminal is not blocked,
* then this resize event must be the result of exiting the
* external editor. */
if (term->textarea_data && term->blocked == -1)
textarea_edit(1, term, NULL, NULL, NULL);
break;
case EVENT_MOUSE:
#ifdef CONFIG_MOUSE
reset_timer();
tev.ev = ilev->ev;
copy_struct(&tev.info.mouse, &ilev->info.mouse);
if (!ignore_mouse_event(term, &tev))
term_send_event(term, &tev);
#endif
break;
case EVENT_KBD:
{
int utf8_io = -1;
int key = ilev->info.keyboard.key;
term_event_modifier_T modifier = ilev->info.keyboard.modifier;
if (key >= 0x100)
key = -key;
reset_timer();
if (modifier == KBD_MOD_CTRL && (key == 'l' || key == 'L')) {
redraw_terminal_cls(term);
break;
} else if (key == KBD_CTRL_C) {
destroy_terminal(term);
return 0;
}
/* Character Conversions. */
#ifdef CONFIG_UTF8
/* struct term_event_keyboard carries UCS-4.
* - If the "utf_8_io" option is true or the "charset"
* option refers to UTF-8, then term->utf8_io is true,
* and handle_interlink_event() converts from UTF-8
* to UCS-4.
* - Otherwise, handle_interlink_event() converts from
* the codepage specified with the "charset" option
* to UCS-4. */
utf8_io = term->utf8_io;
#else
/* struct term_event_keyboard carries bytes in the
* charset of the terminal.
* - If the "utf_8_io" option is true, then
* handle_interlink_event() converts from UTF-8 to
* UCS-4, and term_send_ucs() converts from UCS-4 to
* the codepage specified with the "charset" option;
* this codepage cannot be UTF-8.
* - Otherwise, handle_interlink_event() passes the
* bytes straight through. */
utf8_io = get_opt_bool_tree(term->spec, "utf_8_io", NULL);
#endif /* CONFIG_UTF8 */
/* In UTF-8 byte sequences that have more than one byte, the
* first byte is between 0xC0 and 0xFF and the remaining bytes
* are between 0x80 and 0xBF. If there is just one byte, then
* it is between 0x00 and 0x7F and it is straight ASCII.
* (All 'betweens' are inclusive.) */
if (interlink->utf8.len) {
/* A previous call to handle_interlink_event
* got a UTF-8 start byte. */
if (key >= 0x80 && key <= 0xBF && utf8_io) {
/* This is a UTF-8 continuation byte. */
interlink->utf8.ucs <<= 6;
interlink->utf8.ucs |= key & 0x3F;
if (! --interlink->utf8.len) {
unicode_val_T u = interlink->utf8.ucs;
/* UTF-8 allows neither overlong
* sequences nor surrogates. */
if (u < interlink->utf8.min
|| is_utf16_surrogate(u))
u = UCS_REPLACEMENT_CHARACTER;
term_send_ucs(term, u,
term->interlink->utf8.modifier);
}
break;
} else {
/* The byte sequence for this character
* is ending prematurely. Send
* UCS_REPLACEMENT_CHARACTER for the
* terminated character, but don't break;
* let this byte be handled below. */
interlink->utf8.len = 0;
term_send_ucs(term, UCS_REPLACEMENT_CHARACTER,
term->interlink->utf8.modifier);
}
}
/* Note: We know that key <= 0xFF. */
if (key < 0x80 || !utf8_io) {
/* This byte is not part of a multibyte character
* encoding: either it is outside of the ranges for
* UTF-8 start and continuation bytes or UTF-8 I/O mode
* is disabled. */
#ifdef CONFIG_UTF8
if (key >= 0 && !utf8_io) {
/* Not special and UTF-8 mode is disabled:
* recode from the terminal charset to UCS-4. */
key = cp2u(get_opt_codepage_tree(term->spec,
"charset",
NULL),
key);
term_send_ucs(term, key, modifier);
break;
}
#endif /* !CONFIG_UTF8 */
/* It must be special (e.g., F1 or Enter)
* or a single-byte UTF-8 character. */
set_kbd_term_event(&tev, key, modifier);
term_send_event(term, &tev);
break;
} else if ((key & 0xC0) == 0xC0 && (key & 0xFE) != 0xFE) {
/* This is a UTF-8 start byte. */
/* Minimum character values for UTF-8 octet
* sequences of each length, from RFC 2279.
* According to the RFC, UTF-8 decoders should
* reject characters that are encoded with
* more octets than necessary. (RFC 3629,
* which ELinks does not yet otherwise follow,
* tightened the "should" to "MUST".) */
static const unicode_val_T min[] = {
0x00000080, /* ... 0x000007FF with 2 octets */
0x00000800, /* ... 0x0000FFFF with 3 octets */
0x00010000, /* ... 0x001FFFFF with 4 octets */
0x00200000, /* ... 0x03FFFFFF with 5 octets */
0x04000000 /* ... 0x7FFFFFFF with 6 octets */
};
unsigned int mask;
int len = 0;
/* Set @len = the number of contiguous 1's
* in the most significant bits of the first
* octet, i.e. @key. It is also the number
* of octets in the character. Leave @mask
* pointing to the first 0 bit found. */
for (mask = 0x80; key & mask; mask >>= 1)
len++;
/* This will hold because @key was checked above. */
assert(len >= 2 && len <= 6);
if_assert_failed goto invalid_utf8_start_byte;
interlink->utf8.min = min[len - 2];
interlink->utf8.len = len - 1;
interlink->utf8.ucs = key & (mask - 1);
interlink->utf8.modifier = modifier;
break;
}
invalid_utf8_start_byte:
term_send_ucs(term, UCS_REPLACEMENT_CHARACTER, modifier);
break;
}
case EVENT_ABORT:
destroy_terminal(term);
return 0;
default:
ERROR(gettext("Bad event %d"), ilev->ev);
}
/* For EVENT_INIT we read a liitle more */
if (info) return TERMINAL_INFO_SIZE + info->length;
return sizeof(*ilev);
}
void
in_term(struct terminal *term)
{
struct terminal_interlink *interlink = term->interlink;
ssize_t r;
unsigned char *iq;
/* Mark this as the most recently used terminal. */
move_to_top_of_list(terminals, term);
if (!interlink
|| !interlink->qfreespace
|| interlink->qfreespace - interlink->qlen > ALLOC_GR) {
int qlen = interlink ? interlink->qlen : 0;
int queuesize = ((qlen + ALLOC_GR) & ~(ALLOC_GR - 1));
int newsize = sizeof(*interlink) + queuesize;
interlink = mem_realloc(interlink, newsize);
if (!interlink) {
destroy_terminal(term);
return;
}
/* Blank the members for the first allocation */
if (!term->interlink)
memset(interlink, 0, sizeof(*interlink));
term->interlink = interlink;
interlink->qfreespace = queuesize - interlink->qlen;
}
iq = interlink->input_queue;
r = safe_read(term->fdin, iq + interlink->qlen, interlink->qfreespace);
if (r <= 0) {
if (r == -1 && errno != ECONNRESET)
ERROR(gettext("Could not read event: %d (%s)"),
errno, (unsigned char *) strerror(errno));
destroy_terminal(term);
return;
}
interlink->qlen += r;
interlink->qfreespace -= r;
while (interlink->qlen >= sizeof(struct interlink_event)) {
struct interlink_event *ev = (struct interlink_event *) iq;
int event_size = handle_interlink_event(term, ev);
/* If the event was not handled save the bytes in the queue for
* later in case more stuff is read later. */
if (!event_size) break;
/* Acount for the handled bytes */
interlink->qlen -= event_size;
interlink->qfreespace += event_size;
/* If there are no more bytes to handle stop else move next
* event bytes to the front of the queue. */
if (!interlink->qlen) break;
memmove(iq, iq + event_size, interlink->qlen);
}
}