mirror of
https://github.com/rkd77/elinks.git
synced 2024-12-04 14:46:47 -05:00
2bec2984c4
WATT-32 and openssl you must compile yourself. You must tweak configure options, something like: --disable-utf-8, etc. Only checked on dosemu with LFN. Networking lags is a bit.
522 lines
14 KiB
C
522 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
|
|
|
|
#ifdef HAVE_SYS_SOCKET_H
|
|
#include <sys/socket.h>
|
|
#endif
|
|
|
|
#include "elinks.h"
|
|
|
|
#include "intl/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. */
|
|
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 = (struct window *)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 char *recoded;
|
|
|
|
set_kbd_term_event(&ev, KBD_UNDEF, modifier);
|
|
recoded = u2cp_no_nbsp(u, get_terminal_codepage(term));
|
|
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)
|
|
{
|
|
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_terminal_codepage(term));
|
|
/* 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 (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_terminal_codepage(term), 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;
|
|
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 = (struct terminal_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, (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);
|
|
}
|
|
}
|