mirror of
https://github.com/rkd77/elinks.git
synced 2024-12-04 14:46:47 -05:00
361b8f661d
AFAIK on Windows long is sizeof 4, while void * is sizeof 8. intptr_t is the same sizeof as void *.
1329 lines
37 KiB
C
1329 lines
37 KiB
C
/** Support for keyboard interface
|
|
* @file
|
|
*
|
|
* @todo TODO: move stuff from here to itrm.{c,h} and mouse.{c,h}
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_STDINT_H
|
|
#include <stdint.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#ifdef HAVE_TERMIOS_H
|
|
#include <termios.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef __hpux__
|
|
#include <limits.h>
|
|
#define HPUX_PIPE (len > PIPE_BUF || errno != EAGAIN)
|
|
#else
|
|
#define HPUX_PIPE 1
|
|
#endif
|
|
|
|
#include "elinks.h"
|
|
|
|
#include "config/options.h"
|
|
#include "intl/libintl.h"
|
|
#include "main/main.h"
|
|
#include "main/select.h"
|
|
#include "main/timer.h"
|
|
#include "osdep/ascii.h"
|
|
#include "osdep/osdep.h"
|
|
#include "terminal/hardio.h"
|
|
#include "terminal/itrm.h"
|
|
#include "terminal/kbd.h"
|
|
#include "terminal/mouse.h"
|
|
#include "terminal/terminal.h"
|
|
#include "util/error.h"
|
|
#include "util/memory.h"
|
|
#include "util/string.h"
|
|
#include "util/time.h"
|
|
|
|
struct itrm *ditrm = NULL;
|
|
|
|
static void free_itrm(struct itrm *);
|
|
static void in_kbd(struct itrm *);
|
|
static void in_sock(struct itrm *);
|
|
static int process_queue(struct itrm *);
|
|
static void handle_itrm_stdin(struct itrm *);
|
|
static void unhandle_itrm_stdin(struct itrm *);
|
|
|
|
#ifdef CONFIG_DEBUG
|
|
/** This hack makes GCC put enum term_event_special_key in the debug
|
|
* information even though it is not otherwise used. The const
|
|
* prevents an unused-variable warning. */
|
|
static const enum term_event_special_key dummy_term_event_special_key;
|
|
#endif
|
|
|
|
|
|
int
|
|
is_blocked(void)
|
|
{
|
|
return ditrm && ditrm->blocked;
|
|
}
|
|
|
|
|
|
void
|
|
free_all_itrms(void)
|
|
{
|
|
if (ditrm) free_itrm(ditrm);
|
|
}
|
|
|
|
|
|
/** A select_handler_T write_func for itrm_out.sock. This is called
|
|
* when there is data in @c itrm->out.queue and it is possible to write
|
|
* it to @c itrm->out.sock. When @c itrm->out.queue becomes empty, this
|
|
* handler is temporarily removed. */
|
|
static void
|
|
itrm_queue_write(struct itrm *itrm)
|
|
{
|
|
int written;
|
|
int qlen = int_min(itrm->out.queue.len, 128);
|
|
|
|
assertm(qlen, "event queue empty");
|
|
if_assert_failed return;
|
|
|
|
written = safe_write(itrm->out.sock, itrm->out.queue.data, qlen);
|
|
if (written <= 0) {
|
|
if (written < 0) free_itrm(itrm); /* write error */
|
|
return;
|
|
}
|
|
|
|
itrm->out.queue.len -= written;
|
|
|
|
if (itrm->out.queue.len == 0) {
|
|
set_handlers(itrm->out.sock,
|
|
get_handler(itrm->out.sock, SELECT_HANDLER_READ),
|
|
NULL,
|
|
get_handler(itrm->out.sock, SELECT_HANDLER_ERROR),
|
|
get_handler_data(itrm->out.sock));
|
|
} else {
|
|
assert(itrm->out.queue.len > 0);
|
|
memmove(itrm->out.queue.data, itrm->out.queue.data + written, itrm->out.queue.len);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
itrm_queue_event(struct itrm *itrm, char *data, int len)
|
|
{
|
|
int w = 0;
|
|
|
|
if (!len) return;
|
|
|
|
if (!itrm->out.queue.len && can_write(itrm->out.sock)) {
|
|
w = safe_write(itrm->out.sock, data, len);
|
|
if (w <= 0 && HPUX_PIPE) {
|
|
register_bottom_half(free_itrm, itrm);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (w < len) {
|
|
int left = len - w;
|
|
unsigned char *c = (unsigned char *)mem_realloc(itrm->out.queue.data,
|
|
itrm->out.queue.len + left);
|
|
|
|
if (!c) {
|
|
free_itrm(itrm);
|
|
return;
|
|
}
|
|
|
|
itrm->out.queue.data = c;
|
|
memcpy(itrm->out.queue.data + itrm->out.queue.len, data + w, left);
|
|
itrm->out.queue.len += left;
|
|
set_handlers(itrm->out.sock,
|
|
get_handler(itrm->out.sock, SELECT_HANDLER_READ),
|
|
(select_handler_T) itrm_queue_write,
|
|
(select_handler_T) free_itrm, itrm);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
kbd_ctrl_c(void)
|
|
{
|
|
struct interlink_event ev;
|
|
|
|
if (!ditrm) return;
|
|
/* This is called because of a signal, and there may be input
|
|
* pending from the terminal, so do not reset
|
|
* ditrm->bracketed_pasting. */
|
|
set_kbd_interlink_event(&ev, KBD_CTRL_C, KBD_MOD_NONE);
|
|
itrm_queue_event(ditrm, (char *) &ev, sizeof(ev));
|
|
}
|
|
|
|
#define write_sequence(fd, seq) \
|
|
hard_write(fd, seq, sizeof(seq) - 1)
|
|
|
|
|
|
#define INIT_TERMINAL_SEQ "\033)0\0337" /**< Special Character and Line Drawing Set, Save Cursor */
|
|
#define INIT_ALT_SCREEN_SEQ "\033[?47h" /**< Use Alternate Screen Buffer */
|
|
#define INIT_BRACKETED_PASTE_SEQ "\033[?2004h" /**< Enable XTerm bracketed paste mode */
|
|
|
|
static void
|
|
send_init_sequence(int h, int altscreen)
|
|
{
|
|
#ifdef CONFIG_OS_DOS
|
|
save_terminal();
|
|
#endif
|
|
want_draw();
|
|
write_sequence(h, INIT_TERMINAL_SEQ);
|
|
|
|
/* If alternate screen is supported switch to it. */
|
|
if (altscreen) {
|
|
write_sequence(h, INIT_ALT_SCREEN_SEQ);
|
|
}
|
|
#ifdef CONFIG_MOUSE
|
|
if (mouse_enabled) {
|
|
send_mouse_init_sequence(h);
|
|
}
|
|
#endif
|
|
#ifndef CONFIG_OS_DOS
|
|
write_sequence(h, INIT_BRACKETED_PASTE_SEQ);
|
|
#endif
|
|
done_draw();
|
|
}
|
|
|
|
#define DONE_CLS_SEQ "\033[2J" /**< Erase in Display, Clear All */
|
|
#define DONE_TERMINAL_SEQ "\0338\r \b" /**< Restore Cursor (DECRC) + ??? */
|
|
#define DONE_ALT_SCREEN_SEQ "\033[?47l" /**< Use Normal Screen Buffer */
|
|
#define DONE_BRACKETED_PASTE_SEQ "\033[?2004l" /**< Disable XTerm bracketed paste mode */
|
|
|
|
static void
|
|
send_done_sequence(int h, int altscreen)
|
|
{
|
|
want_draw();
|
|
#ifndef CONFIG_OS_DOS
|
|
write_sequence(h, DONE_BRACKETED_PASTE_SEQ);
|
|
#endif
|
|
write_sequence(h, DONE_CLS_SEQ);
|
|
|
|
#ifdef CONFIG_MOUSE
|
|
send_mouse_done_sequence(h);
|
|
#endif
|
|
|
|
/* Switch from alternate screen. */
|
|
if (altscreen) {
|
|
write_sequence(h, DONE_ALT_SCREEN_SEQ);
|
|
}
|
|
|
|
write_sequence(h, DONE_TERMINAL_SEQ);
|
|
done_draw();
|
|
#ifdef CONFIG_OS_DOS
|
|
restore_terminal();
|
|
#endif
|
|
}
|
|
|
|
|
|
#undef write_sequence
|
|
|
|
void
|
|
resize_terminal(void)
|
|
{
|
|
struct interlink_event ev;
|
|
int width, height;
|
|
|
|
get_terminal_size(ditrm->out.std, &width, &height);
|
|
set_resize_interlink_event(&ev, width, height);
|
|
itrm_queue_event(ditrm, (char *) &ev, sizeof(ev));
|
|
}
|
|
|
|
void
|
|
get_terminal_name(char name[MAX_TERM_LEN])
|
|
{
|
|
char *term = getenv("TERM");
|
|
int i;
|
|
|
|
memset(name, 0, MAX_TERM_LEN);
|
|
|
|
if (!term) return;
|
|
|
|
for (i = 0; term[i] != 0 && i < MAX_TERM_LEN - 1; i++)
|
|
name[i] = isident(term[i]) ? term[i] : '-';
|
|
}
|
|
|
|
|
|
static int
|
|
setraw(struct itrm *itrm, int save_orig)
|
|
{
|
|
struct termios t;
|
|
long vdisable = -1;
|
|
|
|
memset(&t, 0, sizeof(t));
|
|
if (tcgetattr(itrm->in.ctl, &t)) return -1;
|
|
|
|
if (save_orig) copy_struct(&itrm->t, &t);
|
|
|
|
#ifdef _POSIX_VDISABLE
|
|
vdisable = _POSIX_VDISABLE;
|
|
#elif defined(HAVE_FPATHCONF)
|
|
vdisable = fpathconf(itrm->in.ctl, _PC_VDISABLE);
|
|
#endif
|
|
|
|
#ifdef VERASE
|
|
/* Is VERASE defined on Windows? */
|
|
if (vdisable != -1 && t.c_cc[VERASE] == vdisable)
|
|
itrm->verase = -1;
|
|
else
|
|
itrm->verase = (unsigned char) t.c_cc[VERASE];
|
|
#else
|
|
itrm->verase = -1;
|
|
#endif
|
|
|
|
elinks_cfmakeraw(&t);
|
|
t.c_lflag |= ISIG;
|
|
#ifdef TOSTOP
|
|
t.c_lflag |= get_opt_bool("ui.tostop", NULL) ? TOSTOP : 0;
|
|
#endif
|
|
t.c_oflag |= OPOST;
|
|
if (tcsetattr(itrm->in.ctl, TCSANOW, &t)) return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Construct the struct itrm of this process, make ::ditrm point to it,
|
|
* set up select() handlers, and send the initial interlink packet.
|
|
*
|
|
* The first five parameters are file descriptors that this function
|
|
* saves in submembers of struct itrm, and for which this function may
|
|
* set select() handlers. Please see the definitions of struct
|
|
* itrm_in and struct itrm_out for further explanations.
|
|
*
|
|
* @param std_in itrm_in.std: read tty device (or pipe)
|
|
* @param std_out itrm_out.std: write tty device (or pipe)
|
|
* @param sock_in itrm_in.sock
|
|
* - If master: == @a std_out (masterhood flag)
|
|
* - If slave: read socket from master
|
|
* @param sock_out itrm_out.sock
|
|
* - If master: write pipe to same process
|
|
* - If slave: write socket to master
|
|
* @param ctl_in itrm_in.ctl: control tty device
|
|
*
|
|
* The remaining three parameters control the initial interlink packet.
|
|
*
|
|
* @param init_string A string to be passed to the master process. Need
|
|
* not be null-terminated. If @a remote == 0, this is
|
|
* a URI. Otherwise, this is a remote command.
|
|
* @param init_len The length of init_string, in bytes.
|
|
* @param remote = 0 if asking the master to start a new session
|
|
* and display it via this process. Otherwise,
|
|
* enum ::remote_session_flags. */
|
|
void
|
|
handle_trm(int std_in, int std_out, int sock_in, int sock_out, int ctl_in,
|
|
void *init_string, int init_len, int remote)
|
|
{
|
|
struct itrm *itrm;
|
|
struct terminal_info info;
|
|
struct interlink_event_size *size = &info.event.info.size;
|
|
char *ts;
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
|
|
#ifdef CONFIG_OS_DOS
|
|
dos_setraw(ctl_in, 1);
|
|
#endif
|
|
|
|
get_terminal_size(ctl_in, &size->width, &size->height);
|
|
info.event.ev = EVENT_INIT;
|
|
info.system_env = get_system_env();
|
|
info.length = init_len;
|
|
|
|
if (remote) {
|
|
info.session_info = remote;
|
|
info.magic = INTERLINK_REMOTE_MAGIC;
|
|
} else {
|
|
info.session_info = get_cmd_opt_int("base-session");
|
|
info.magic = INTERLINK_NORMAL_MAGIC;
|
|
}
|
|
|
|
itrm = (struct itrm *)mem_calloc(1, sizeof(*itrm));
|
|
if (!itrm) return;
|
|
|
|
itrm->in.queue.data = (unsigned char *)mem_calloc(1, ITRM_IN_QUEUE_SIZE);
|
|
if (!itrm->in.queue.data) {
|
|
mem_free(itrm);
|
|
return;
|
|
}
|
|
|
|
ditrm = itrm;
|
|
itrm->in.std = std_in;
|
|
itrm->out.std = std_out;
|
|
itrm->in.sock = sock_in;
|
|
itrm->out.sock = sock_out;
|
|
itrm->in.ctl = ctl_in;
|
|
itrm->timer = TIMER_ID_UNDEF;
|
|
itrm->remote = !!remote;
|
|
|
|
/* If the master does not tell which charset it's using in
|
|
* this terminal, assume it's some ISO 8859. Because that's
|
|
* what older versions of ELinks did. */
|
|
itrm->title_codepage = get_cp_index("ISO-8859-1");
|
|
|
|
/* FIXME: Combination altscreen + xwin does not work as it should,
|
|
* mouse clicks are reportedly partially ignored. */
|
|
if (info.system_env & (ENV_SCREEN | ENV_XWIN))
|
|
itrm->altscreen = 1;
|
|
|
|
if (!remote) {
|
|
if (ctl_in >= 0) setraw(itrm, 1);
|
|
send_init_sequence(std_out, itrm->altscreen);
|
|
handle_terminal_resize(ctl_in, resize_terminal);
|
|
#ifdef CONFIG_MOUSE
|
|
enable_mouse();
|
|
#endif
|
|
handle_itrm_stdin(itrm);
|
|
} else {
|
|
/* elinks -remote may not have a valid stdin if not run from a tty (bug 938) */
|
|
if (std_in >= 0) handle_itrm_stdin(itrm);
|
|
}
|
|
|
|
if (sock_in != std_out)
|
|
set_handlers(sock_in, (select_handler_T) in_sock,
|
|
NULL, (select_handler_T) free_itrm, itrm);
|
|
|
|
get_terminal_name(info.name);
|
|
|
|
ts = get_cwd();
|
|
if (ts) {
|
|
memcpy(info.cwd, ts, int_min(strlen(ts), MAX_CWD_LEN));
|
|
mem_free(ts);
|
|
}
|
|
|
|
itrm_queue_event(itrm, (char *) &info, TERMINAL_INFO_SIZE);
|
|
itrm_queue_event(itrm, (char *) init_string, init_len);
|
|
}
|
|
|
|
|
|
/** A select_handler_T read_func and error_func for the pipe (intptr_t) @a h.
|
|
* This is called when the subprocess started on the terminal of this
|
|
* ELinks process exits. ELinks then resumes using the terminal. */
|
|
static void
|
|
unblock_itrm_x(void *h)
|
|
{
|
|
close_handle(h);
|
|
if (!ditrm) return;
|
|
unblock_itrm();
|
|
|
|
/* Note: External editor support depends on this resize event. */
|
|
resize_terminal();
|
|
}
|
|
|
|
|
|
int
|
|
unblock_itrm(void)
|
|
{
|
|
if (!ditrm) return -1;
|
|
|
|
if (ditrm->in.ctl >= 0 && setraw(ditrm, 0)) return -1;
|
|
ditrm->blocked = 0;
|
|
send_init_sequence(ditrm->out.std, ditrm->altscreen);
|
|
|
|
handle_itrm_stdin(ditrm);
|
|
resume_mouse(ditrm->mouse_h);
|
|
|
|
handle_terminal_resize(ditrm->in.ctl, resize_terminal);
|
|
unblock_stdin();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
block_itrm(void)
|
|
{
|
|
if (!ditrm) return;
|
|
|
|
ditrm->blocked = 1;
|
|
block_stdin();
|
|
kill_timer(&ditrm->timer);
|
|
ditrm->in.queue.len = 0;
|
|
unhandle_terminal_resize(ditrm->in.ctl);
|
|
send_done_sequence(ditrm->out.std, ditrm->altscreen);
|
|
tcsetattr(ditrm->in.ctl, TCSANOW, &ditrm->t);
|
|
unhandle_itrm_stdin(ditrm);
|
|
suspend_mouse(ditrm->mouse_h);
|
|
}
|
|
|
|
|
|
static void
|
|
free_itrm(struct itrm *itrm)
|
|
{
|
|
if (!itrm) return;
|
|
|
|
if (!itrm->remote) {
|
|
if (itrm->orig_title && *itrm->orig_title) {
|
|
set_window_title(itrm->orig_title, itrm->title_codepage);
|
|
|
|
} else if (itrm->touched_title) {
|
|
/* Set the window title to the value of $TERM if X11
|
|
* wasn't compiled in. Should hopefully make at least
|
|
* half the users happy. (debian bug #312955) */
|
|
char title[MAX_TERM_LEN];
|
|
|
|
get_terminal_name(title);
|
|
if (*title)
|
|
set_window_title(title,
|
|
get_cp_index("US-ASCII"));
|
|
}
|
|
|
|
|
|
unhandle_terminal_resize(itrm->in.ctl);
|
|
#ifdef CONFIG_MOUSE
|
|
disable_mouse();
|
|
#endif
|
|
send_done_sequence(itrm->out.std, itrm->altscreen);
|
|
tcsetattr(itrm->in.ctl, TCSANOW, &itrm->t);
|
|
}
|
|
|
|
mem_free_set(&itrm->orig_title, NULL);
|
|
|
|
/* elinks -remote may not have a valid stdin if not run from a tty (bug 938) */
|
|
if (!itrm->remote || itrm->in.std >= 0) clear_handlers(itrm->in.std);
|
|
clear_handlers(itrm->in.sock);
|
|
clear_handlers(itrm->out.std);
|
|
clear_handlers(itrm->out.sock);
|
|
|
|
kill_timer(&itrm->timer);
|
|
|
|
if (itrm == ditrm) ditrm = NULL;
|
|
mem_free_if(itrm->out.queue.data);
|
|
mem_free_if(itrm->in.queue.data);
|
|
mem_free(itrm);
|
|
|
|
program.terminate = 1;
|
|
}
|
|
|
|
/** Resize terminal to dimensions specified by @a text string.
|
|
* @a text should look like "width,height,old-width,old-height"
|
|
* where width and height are integers. */
|
|
static inline void
|
|
resize_terminal_from_str(const char *text_)
|
|
{
|
|
enum { NEW_WIDTH = 0, NEW_HEIGHT, OLD_WIDTH, OLD_HEIGHT, NUMBERS };
|
|
int numbers[NUMBERS];
|
|
int i;
|
|
char *text2, *text;
|
|
|
|
assert(text_ && *text_);
|
|
if_assert_failed return;
|
|
|
|
text2 = stracpy(text_);
|
|
if (!text2) {
|
|
return;
|
|
}
|
|
text = text2;
|
|
|
|
for (i = 0; i < NUMBERS; i++) {
|
|
char *p = strchr(text, ',');
|
|
|
|
if (p) {
|
|
*p++ = '\0';
|
|
|
|
} else if (i < OLD_HEIGHT) {
|
|
mem_free(text2);
|
|
return;
|
|
}
|
|
|
|
numbers[i] = atoi(text);
|
|
|
|
if (p) text = p;
|
|
}
|
|
|
|
resize_window(numbers[NEW_WIDTH], numbers[NEW_HEIGHT],
|
|
numbers[OLD_WIDTH], numbers[OLD_HEIGHT]);
|
|
resize_terminal();
|
|
mem_free(text2);
|
|
}
|
|
|
|
void
|
|
dispatch_special(const char *text)
|
|
{
|
|
switch (text[0]) {
|
|
case TERM_FN_TITLE:
|
|
if (ditrm) {
|
|
if (ditrm->remote)
|
|
break;
|
|
|
|
/* If ditrm->touched_title is 0, then
|
|
* ditrm->orig_title should be NULL,
|
|
* but check it to prevent any leak. */
|
|
if (!ditrm->orig_title && !ditrm->touched_title)
|
|
ditrm->orig_title = get_window_title(
|
|
ditrm->title_codepage);
|
|
ditrm->touched_title = 1;
|
|
}
|
|
/* TODO: Is it really possible to get here with
|
|
* ditrm == NULL, and which charset would then
|
|
* be most appropriate? */
|
|
set_window_title(text + 1,
|
|
ditrm ? ditrm->title_codepage
|
|
: get_cp_index("US-ASCII"));
|
|
break;
|
|
case TERM_FN_RESIZE:
|
|
if (ditrm && ditrm->remote)
|
|
break;
|
|
|
|
resize_terminal_from_str(text + 1);
|
|
break;
|
|
case TERM_FN_TITLE_CODEPAGE:
|
|
if (ditrm) {
|
|
int cp = get_cp_index(text + 1);
|
|
|
|
/* If the master sends the name of an
|
|
* unrecognized charset, assume only
|
|
* that it's ASCII compatible. */
|
|
if (cp == -1)
|
|
cp = get_cp_index("US-ASCII");
|
|
ditrm->title_codepage = cp;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void inline
|
|
safe_hard_write(int fd, const char *buf, int len)
|
|
{
|
|
if (is_blocked()) return;
|
|
|
|
want_draw();
|
|
hard_write(fd, buf, len);
|
|
done_draw();
|
|
}
|
|
|
|
/** A select_handler_T read_func for itrm_in.sock. A slave process
|
|
* calls this when the master sends it data to be displayed. The
|
|
* master process never calls this. */
|
|
static void
|
|
in_sock(struct itrm *itrm)
|
|
{
|
|
struct string path;
|
|
struct string delete_;
|
|
char ch;
|
|
int fg; /* enum term_exec */
|
|
ssize_t bytes_read, i, p;
|
|
char buf[ITRM_OUT_QUEUE_SIZE];
|
|
|
|
bytes_read = safe_read(itrm->in.sock, buf, ITRM_OUT_QUEUE_SIZE);
|
|
if (bytes_read <= 0) goto free_and_return;
|
|
|
|
qwerty:
|
|
for (i = 0; i < bytes_read; i++)
|
|
if (!buf[i])
|
|
goto has_nul_byte;
|
|
|
|
safe_hard_write(itrm->out.std, buf, bytes_read);
|
|
return;
|
|
|
|
has_nul_byte:
|
|
if (i) safe_hard_write(itrm->out.std, buf, i);
|
|
|
|
i++;
|
|
assert(ITRM_OUT_QUEUE_SIZE - i > 0);
|
|
memmove(buf, buf + i, ITRM_OUT_QUEUE_SIZE - i);
|
|
bytes_read -= i;
|
|
p = 0;
|
|
|
|
#define RD(xx) { \
|
|
char cc; \
|
|
\
|
|
if (p < bytes_read) \
|
|
cc = buf[p++]; \
|
|
else if ((hard_read(itrm->in.sock, &cc, 1)) <= 0) \
|
|
goto free_and_return; \
|
|
xx = cc; \
|
|
}
|
|
|
|
RD(fg);
|
|
|
|
if (!init_string(&path)) goto free_and_return;
|
|
|
|
while (1) {
|
|
RD(ch);
|
|
if (!ch) break;
|
|
add_char_to_string(&path, ch);
|
|
}
|
|
|
|
if (!init_string(&delete_)) {
|
|
done_string(&path);
|
|
goto free_and_return;
|
|
}
|
|
|
|
while (1) {
|
|
RD(ch);
|
|
if (!ch) break;
|
|
add_char_to_string(&delete_, ch);
|
|
}
|
|
|
|
#undef RD
|
|
|
|
if (!*path.source) {
|
|
dispatch_special(delete_.source);
|
|
|
|
} else {
|
|
int blockh;
|
|
char *param;
|
|
int path_len, del_len, param_len;
|
|
|
|
/* TODO: Should this be changed to allow TERM_EXEC_NEWWIN
|
|
* in a blocked terminal? There is similar code in
|
|
* exec_on_terminal(). --KON, 2007 */
|
|
if (is_blocked() && fg != TERM_EXEC_BG) {
|
|
if (*delete_.source) unlink(delete_.source);
|
|
goto nasty_thing;
|
|
}
|
|
|
|
path_len = path.length;
|
|
del_len = delete_.length;
|
|
param_len = path_len + del_len + 3;
|
|
|
|
param = (char *)mem_alloc(param_len);
|
|
if (!param) goto nasty_thing;
|
|
|
|
param[0] = fg;
|
|
memcpy(param + 1, path.source, path_len + 1);
|
|
memcpy(param + 1 + path_len + 1, delete_.source, del_len + 1);
|
|
|
|
if (fg == TERM_EXEC_FG) block_itrm();
|
|
|
|
#ifndef WIN32
|
|
blockh = start_thread((void (*)(void *, int)) exec_thread,
|
|
param, param_len);
|
|
#else
|
|
exec_thread(param, param_len);
|
|
|
|
#endif
|
|
mem_free(param);
|
|
|
|
if (blockh == -1) {
|
|
if (fg == TERM_EXEC_FG)
|
|
unblock_itrm();
|
|
|
|
goto nasty_thing;
|
|
}
|
|
|
|
if (fg == TERM_EXEC_FG) {
|
|
set_handlers(blockh, (select_handler_T) unblock_itrm_x,
|
|
NULL, (select_handler_T) unblock_itrm_x,
|
|
(void *) (intptr_t) blockh);
|
|
|
|
} else {
|
|
set_handlers(blockh, close_handle, NULL, close_handle,
|
|
(void *) (intptr_t) blockh);
|
|
}
|
|
}
|
|
|
|
nasty_thing:
|
|
done_string(&path);
|
|
done_string(&delete_);
|
|
assert(ITRM_OUT_QUEUE_SIZE - p > 0);
|
|
memmove(buf, buf + p, ITRM_OUT_QUEUE_SIZE - p);
|
|
bytes_read -= p;
|
|
|
|
goto qwerty;
|
|
|
|
free_and_return:
|
|
free_itrm(itrm);
|
|
}
|
|
|
|
|
|
/** Parse an ECMA-48 control sequence that was received from a
|
|
* terminal. Extract the Final Byte (if there are no Intermediate
|
|
* Bytes) and the value of the first parameter (if it is an integer).
|
|
*
|
|
* This function assumes the control sequence begins with a CSI -
|
|
* CONTROL SEQUENCE INTRODUCER encoded as ESC [. (ECMA-48 also allows
|
|
* 0x9B as a single-byte CSI, but we don't support that here.)
|
|
*
|
|
* @returns one of:
|
|
* - -1 if the control sequence is not yet complete; the caller sets a timer.
|
|
* - 0 if the control sequence does not comply with ECMA-48.
|
|
* - The length of the control sequence otherwise. */
|
|
static inline int
|
|
get_esc_code(unsigned char *str, int len, char *final_byte,
|
|
int *first_param_value)
|
|
{
|
|
const int parameter_pos = 2;
|
|
int intermediate_pos;
|
|
int final_pos;
|
|
int pos;
|
|
|
|
*final_byte = '\0';
|
|
*first_param_value = 0;
|
|
|
|
/* Parameter Bytes */
|
|
pos = parameter_pos;
|
|
while (pos < len && str[pos] >= 0x30 && str[pos] <= 0x3F)
|
|
++pos;
|
|
|
|
/* Intermediate Bytes */
|
|
intermediate_pos = pos;
|
|
while (pos < len && str[pos] >= 0x20 && str[pos] <= 0x2F)
|
|
++pos;
|
|
|
|
/* Final Byte */
|
|
final_pos = pos;
|
|
if (pos >= len)
|
|
return -1;
|
|
if (!(str[pos] >= 0x40 && str[pos] <= 0x7E))
|
|
return 0;
|
|
|
|
/* The control sequence seems OK. If the first Parameter
|
|
* Byte indicates that the parameter string is formatted
|
|
* as specified in clause 5.4.2 of ECMA-48, and the first
|
|
* parameter is an integer, then compute its value.
|
|
* (We need not check @len here because the loop cannot get
|
|
* past the Final Byte.) */
|
|
for (pos = parameter_pos; str[pos] >= 0x30 && str[pos] <= 0x39; ++pos)
|
|
*first_param_value = *first_param_value * 10 + str[pos] - 0x30;
|
|
/* If the first parameter contains an embedded separator, then
|
|
* the value is not an integer, so discard what we computed. */
|
|
if (str[pos] == 0x3A)
|
|
*first_param_value = 0;
|
|
|
|
/* The meaning of the Final Byte depends on the Intermediate
|
|
* Bytes. Because we don't currently need to recognize any
|
|
* control sequences that use Intermediate Bytes, we just
|
|
* discard the Final Byte if there are any Intermediate
|
|
* Bytes. */
|
|
if (intermediate_pos == final_pos)
|
|
*final_byte = str[final_pos];
|
|
|
|
return final_pos + 1;
|
|
}
|
|
|
|
/* Define it to dump queue content in a readable form,
|
|
* it may help to determine terminal sequences, and see what goes on. --Zas */
|
|
/* #define DEBUG_ITRM_QUEUE */
|
|
|
|
#ifdef DEBUG_ITRM_QUEUE
|
|
#include <stdio.h>
|
|
#include <ctype.h> /* isprint() isspace((unsigned char)) */
|
|
#endif
|
|
|
|
int ui_double_esc;
|
|
|
|
static inline int
|
|
get_ui_double_esc(void)
|
|
{
|
|
return ui_double_esc;
|
|
}
|
|
|
|
/** Decode a control sequence that begins with CSI (CONTROL SEQUENCE
|
|
* INTRODUCER) encoded as ESC [, and set @a *ev accordingly.
|
|
* (ECMA-48 also allows 0x9B as a single-byte CSI, but we don't
|
|
* support that here.)
|
|
*
|
|
* @returns one of:
|
|
* - -1 if the control sequence is not yet complete; the caller sets a timer.
|
|
* - 0 if the control sequence should be parsed by some other function.
|
|
* - The length of the control sequence otherwise.
|
|
* Returning >0 does not imply this function has altered @a *ev. */
|
|
static int
|
|
decode_terminal_escape_sequence(struct itrm *itrm, struct interlink_event *ev)
|
|
{
|
|
struct term_event_keyboard kbd = { KBD_UNDEF, KBD_MOD_NONE };
|
|
char c;
|
|
int v;
|
|
int el;
|
|
|
|
if (itrm->in.queue.len == 2 && itrm->in.queue.data[1] == ASCII_ESC && get_ui_double_esc()) {
|
|
kbd.key = KBD_ESC;
|
|
set_kbd_interlink_event(ev, kbd.key, kbd.modifier);
|
|
return 2;
|
|
}
|
|
if (itrm->in.queue.len < 3) return -1;
|
|
|
|
if (itrm->in.queue.data[2] == '[') {
|
|
/* The terminfo entry for linux has "kf1=\E[[A", etc.
|
|
* These are not control sequences compliant with
|
|
* clause 5.4 of ECMA-48. (According to ECMA-48,
|
|
* "\E[[" is SRS - START REVERSED STRING.) */
|
|
if (itrm->in.queue.len >= 4
|
|
&& itrm->in.queue.data[3] >= 'A'
|
|
&& itrm->in.queue.data[3] <= 'L') {
|
|
kbd.key = number_to_kbd_fkey(itrm->in.queue.data[3] - 'A' + 1);
|
|
set_kbd_interlink_event(ev, kbd.key, kbd.modifier);
|
|
return 4;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
el = get_esc_code(itrm->in.queue.data, itrm->in.queue.len, &c, &v);
|
|
if (el == -1) {
|
|
/* If the control sequence is incomplete but itrm->in.queue
|
|
* is already full, then we must not wait for more input:
|
|
* kbd_timeout might call in_kbd and thus process_input
|
|
* and come right back here. Better just reject the whole
|
|
* thing and let the initial CSI be handled as Alt-[. */
|
|
if (itrm->in.queue.len == ITRM_IN_QUEUE_SIZE)
|
|
return 0;
|
|
else
|
|
return -1;
|
|
}
|
|
#ifdef DEBUG_ITRM_QUEUE
|
|
fprintf(stderr, "esc code: %c v=%d c=%c el=%d\n", itrm->in.queue.data[1], v, c, el);
|
|
fflush(stderr);
|
|
#endif
|
|
|
|
/* The following information should be listed for each escape
|
|
* sequence recognized here:
|
|
*
|
|
* 1. Which control function ECMA-48 assigns to the sequence.
|
|
* Put parentheses around this if the control function
|
|
* seems unrelated to how ELinks actually treats the
|
|
* sequence. Write "private" if it is a control sequence
|
|
* reserved for private or experimental use in ECMA-48.
|
|
* (Those have a Final Byte in the range 0x70 to 0x7F,
|
|
* optionally preceded by a single Intermediate Byte 0x20.)
|
|
*
|
|
* 2. The capname used by Terminfo, if any. These should help
|
|
* when ELinks is eventually changed to read escape
|
|
* sequences from Terminfo (bug 96).
|
|
*
|
|
* 3. The $TERM identifier of some terminal that generates
|
|
* this escape sequence with the meaning expected by
|
|
* ELinks. Escape sequences with no known terminal may end
|
|
* up being removed from ELinks when bug 96 is fixed.
|
|
*/ /* ECMA-48 Terminfo $TERM */
|
|
switch (c) { /* ------- -------- ----- */
|
|
case 'A': kbd.key = KBD_UP; break; /* CUU kcuu1 vt200 */
|
|
case 'B': kbd.key = KBD_DOWN; break; /* CUD kcud1 vt200 */
|
|
case 'C': kbd.key = KBD_RIGHT; break; /* CUF kcuf1 vt200 */
|
|
case 'D': kbd.key = KBD_LEFT; break; /* CUB kcub1 vt200 */
|
|
case 'F': /* (CPL) kend cons25 */
|
|
case 'e': kbd.key = KBD_END; break; /* (VPR) kend */
|
|
case 'H': kbd.key = KBD_HOME; break; /* CUP khome cons25 */
|
|
case 'I': kbd.key = KBD_PAGE_UP; break; /* (CHT) kpp cons25 */
|
|
case 'G': kbd.key = KBD_PAGE_DOWN; break; /* (CHA) knp cons25 */
|
|
case 'L': kbd.key = KBD_INS; break; /* (IL) kich1 cons25 */
|
|
/* Free BSD (TERM=cons25 etc.) */
|
|
/* case 'M': kbd.key = KBD_F1; break;*/ /* (DL) kf1 cons25 */
|
|
case 'N': kbd.key = KBD_F2; break; /* (EF) kf2 cons25 */
|
|
case 'O': kbd.key = KBD_F3; break; /* (EA) kf3 cons25 */
|
|
case 'P': kbd.key = KBD_F4; break; /* (DCH) kf4 cons25 */
|
|
case 'Q': kbd.key = KBD_F5; break; /* (SEE) kf5 cons25 */
|
|
/* case 'R': kbd.key = KBD_F6; break;*/ /* (CPR) kf6 cons25 */
|
|
case 'S': kbd.key = KBD_F7; break; /* (SU) kf7 cons25 */
|
|
case 'T': kbd.key = KBD_F8; break; /* (SD) kf8 cons25 */
|
|
case 'U': kbd.key = KBD_F9; break; /* (NP) kf9 cons25 */
|
|
case 'V': kbd.key = KBD_F10; break; /* (PP) kf10 cons25 */
|
|
case 'W': kbd.key = KBD_F11; break; /* (CTC) kf11 cons25 */
|
|
case 'X': kbd.key = KBD_F12; break; /* (ECH) kf12 cons25 */
|
|
|
|
case 'Z': /* CBT kcbt cons25 */
|
|
kbd.key = KBD_TAB; kbd.modifier = KBD_MOD_SHIFT; break;
|
|
|
|
case 'z': switch (v) { /* private */
|
|
case 247: kbd.key = KBD_INS; break; /* kich1 */
|
|
case 214: kbd.key = KBD_HOME; break; /* khome sun */
|
|
case 220: kbd.key = KBD_END; break; /* kend sun */
|
|
case 216: kbd.key = KBD_PAGE_UP; break; /* kpp sun */
|
|
case 222: kbd.key = KBD_PAGE_DOWN; break; /* knp sun */
|
|
case 249: kbd.key = KBD_DEL; break; /* kdch1 */
|
|
} break;
|
|
|
|
case '~': switch (v) { /* private */
|
|
case 1: kbd.key = KBD_HOME; break; /* khome linux */
|
|
case 2: kbd.key = KBD_INS; break; /* kich1 linux */
|
|
case 3: kbd.key = KBD_DEL; break; /* kdch1 linux */
|
|
case 4: kbd.key = KBD_END; break; /* kend linux */
|
|
case 5: kbd.key = KBD_PAGE_UP; break; /* kpp linux */
|
|
case 6: kbd.key = KBD_PAGE_DOWN; break; /* knp linux */
|
|
case 7: kbd.key = KBD_HOME; break; /* khome rxvt */
|
|
case 8: kbd.key = KBD_END; break; /* kend rxvt */
|
|
|
|
case 11: kbd.key = KBD_F1; break; /* kf1 rxvt */
|
|
case 12: kbd.key = KBD_F2; break; /* kf2 rxvt */
|
|
case 13: kbd.key = KBD_F3; break; /* kf3 rxvt */
|
|
case 14: kbd.key = KBD_F4; break; /* kf4 rxvt */
|
|
case 15: kbd.key = KBD_F5; break; /* kf5 rxvt */
|
|
|
|
case 17: kbd.key = KBD_F6; break; /* kf6 vt200 */
|
|
case 18: kbd.key = KBD_F7; break; /* kf7 vt200 */
|
|
case 19: kbd.key = KBD_F8; break; /* kf8 vt200 */
|
|
case 20: kbd.key = KBD_F9; break; /* kf9 vt200 */
|
|
case 21: kbd.key = KBD_F10; break; /* kf10 vt200 */
|
|
|
|
case 23: kbd.key = KBD_F11; break; /* kf11 vt200 */
|
|
case 24: kbd.key = KBD_F12; break; /* kf12 vt200 */
|
|
|
|
/* Give preference to F11 and F12 over shifted F1 and F2. */
|
|
/*
|
|
case 23: kbd.key = KBD_F1; kbd.modifier = KBD_MOD_SHIFT; break;
|
|
case 24: kbd.key = KBD_F2; kbd.modifier = KBD_MOD_SHIFT; break;
|
|
*/
|
|
|
|
case 25: kbd.key = KBD_F3; kbd.modifier = KBD_MOD_SHIFT; break;
|
|
case 26: kbd.key = KBD_F4; kbd.modifier = KBD_MOD_SHIFT; break;
|
|
|
|
case 28: kbd.key = KBD_F5; kbd.modifier = KBD_MOD_SHIFT; break;
|
|
case 29: kbd.key = KBD_F6; kbd.modifier = KBD_MOD_SHIFT; break;
|
|
|
|
case 31: kbd.key = KBD_F7; kbd.modifier = KBD_MOD_SHIFT; break;
|
|
case 32: kbd.key = KBD_F8; kbd.modifier = KBD_MOD_SHIFT; break;
|
|
case 33: kbd.key = KBD_F9; kbd.modifier = KBD_MOD_SHIFT; break;
|
|
case 34: kbd.key = KBD_F10; kbd.modifier = KBD_MOD_SHIFT; break;
|
|
|
|
case 200: itrm->bracketed_pasting = 1; break; /* xterm */
|
|
case 201: itrm->bracketed_pasting = 0; break; /* xterm */
|
|
|
|
} break;
|
|
|
|
case 'R': resize_terminal(); break; /* CPR u6 */
|
|
case 'M': /* (DL) kmous xterm */
|
|
#ifdef CONFIG_MOUSE
|
|
el = decode_terminal_mouse_escape_sequence(itrm, ev, el, v);
|
|
#endif /* CONFIG_MOUSE */
|
|
break;
|
|
}
|
|
|
|
/* KBD_UNDEF here means it was unrecognized or a mouse event. */
|
|
if (kbd.key != KBD_UNDEF)
|
|
set_kbd_interlink_event(ev, kbd.key, kbd.modifier);
|
|
|
|
return el;
|
|
}
|
|
|
|
/** Decode an escape sequence that begins with SS3 (SINGLE SHIFT 3).
|
|
* These are used for application cursor keys and the application keypad.
|
|
* @returns one of:
|
|
* - -1 if the escape sequence is not yet complete; the caller sets a timer.
|
|
* - 0 if the escape sequence should be parsed by some other function.
|
|
* - The length of the escape sequence otherwise.
|
|
* Returning >0 does not imply this function has altered @a *ev. */
|
|
static int
|
|
decode_terminal_application_key(struct itrm *itrm, struct interlink_event *ev)
|
|
{
|
|
unsigned char c;
|
|
struct interlink_event_keyboard kbd = { KBD_UNDEF, KBD_MOD_NONE };
|
|
|
|
assert(itrm->in.queue.len >= 2);
|
|
assert(itrm->in.queue.data[0] == ASCII_ESC);
|
|
assert(itrm->in.queue.data[1] == 0x4F); /* == 'O', incidentally */
|
|
if_assert_failed return 0;
|
|
|
|
if (itrm->in.queue.len < 3) return -1;
|
|
/* According to ECMA-35 section 8.4, a single (possibly multibyte)
|
|
* character follows the SS3. We now assume the code identifies
|
|
* GL as the single-shift area and the designated set has 94
|
|
* characters. */
|
|
c = itrm->in.queue.data[2];
|
|
if (c < 0x21 || c > 0x7E) return 0;
|
|
|
|
switch (c) { /* Terminfo $TERM */
|
|
case ' ': kbd.key = ' '; break; /* xterm */
|
|
case 'A': kbd.key = KBD_UP; break; /* kcuu1 vt100 */
|
|
case 'B': kbd.key = KBD_DOWN; break; /* kcud1 vt100 */
|
|
case 'C': kbd.key = KBD_RIGHT; break; /* kcuf1 vt100 */
|
|
case 'D': kbd.key = KBD_LEFT; break; /* kcub1 vt100 */
|
|
case 'F': kbd.key = KBD_END; break; /* kend xterm */
|
|
case 'H': kbd.key = KBD_HOME; break; /* khome xterm */
|
|
case 'I': kbd.key = KBD_TAB; break; /* xterm */
|
|
case 'M': kbd.key = KBD_ENTER; break; /* kent vt100 */
|
|
/* FIXME: xterm generates ESC O 2 P for Shift-PF1 */
|
|
case 'P': kbd.key = KBD_F1; break; /* kf1 vt100 */
|
|
case 'Q': kbd.key = KBD_F2; break; /* kf2 vt100 */
|
|
case 'R': kbd.key = KBD_F3; break; /* kf3 vt100 */
|
|
case 'S': kbd.key = KBD_F4; break; /* kf4 vt100 */
|
|
case 'X': kbd.key = '='; break; /* xterm */
|
|
|
|
case 'j': case 'k': case 'l': case 'm': /* *+,- xterm */
|
|
case 'n': case 'o': case 'p': case 'q': /* ./01 xterm */
|
|
case 'r': case 's': case 't': case 'u': /* 2345 xterm */
|
|
case 'v': case 'w': case 'x': case 'y': /* 6789 xterm */
|
|
kbd.key = c - 'p' + '0'; break;
|
|
}
|
|
if (kbd.key != KBD_UNDEF)
|
|
copy_struct(&ev->info.keyboard, &kbd);
|
|
|
|
return 3; /* even if we didn't recognize it */
|
|
}
|
|
|
|
|
|
/** Initialize @a *ev to match the byte @a key received from the terminal.
|
|
* @a key must not be a value from enum term_event_special_key. */
|
|
static void
|
|
set_kbd_event(const struct itrm *itrm, struct interlink_event *ev,
|
|
int key, term_event_modifier_T modifier)
|
|
{
|
|
if (key == itrm->verase)
|
|
key = KBD_BS;
|
|
else switch (key) {
|
|
case ASCII_TAB:
|
|
key = KBD_TAB;
|
|
break;
|
|
case ASCII_DEL: /* often overridden by itrm->verase above */
|
|
key = KBD_DEL;
|
|
break;
|
|
case ASCII_LF:
|
|
case ASCII_CR:
|
|
key = KBD_ENTER;
|
|
break;
|
|
|
|
case ASCII_ESC:
|
|
key = KBD_ESC;
|
|
break;
|
|
|
|
case ASCII_BS: /* often overridden by itrm->verase above */
|
|
default:
|
|
if (key < ' ') {
|
|
key += 'A' - 1;
|
|
modifier |= KBD_MOD_CTRL;
|
|
}
|
|
}
|
|
|
|
set_kbd_interlink_event(ev, key, modifier);
|
|
}
|
|
|
|
/** Timer callback for itrm.timer. As explained in install_timer(),
|
|
* this function must erase the expired timer ID from all variables. */
|
|
static void
|
|
kbd_timeout(struct itrm *itrm)
|
|
{
|
|
struct interlink_event ev;
|
|
int el;
|
|
|
|
itrm->timer = TIMER_ID_UNDEF;
|
|
/* The expired timer ID has now been erased. */
|
|
|
|
assertm(itrm->in.queue.len, "timeout on empty queue");
|
|
assert(!itrm->blocked); /* block_itrm should have killed itrm->timer */
|
|
if_assert_failed return;
|
|
|
|
if (can_read(itrm->in.std)) {
|
|
in_kbd(itrm);
|
|
return;
|
|
}
|
|
|
|
if (itrm->in.queue.len >= 2 && itrm->in.queue.data[0] == ASCII_ESC) {
|
|
/* This is used for ESC [ and ESC O. */
|
|
set_kbd_event(itrm, &ev, itrm->in.queue.data[1], KBD_MOD_ALT);
|
|
el = 2;
|
|
} else {
|
|
set_kbd_event(itrm, &ev, itrm->in.queue.data[0], KBD_MOD_NONE);
|
|
el = 1;
|
|
}
|
|
itrm->bracketed_pasting = 0;
|
|
itrm_queue_event(itrm, (char *) &ev, sizeof(ev));
|
|
|
|
itrm->in.queue.len -= el;
|
|
if (itrm->in.queue.len)
|
|
memmove(itrm->in.queue.data, itrm->in.queue.data + el, itrm->in.queue.len);
|
|
|
|
while (process_queue(itrm));
|
|
}
|
|
|
|
/** Parse one event from itrm_in.queue and append to itrm_out.queue.
|
|
* @pre On entry, @a *itrm must not be blocked.
|
|
* @returns the number of bytes removed from itrm->in.queue; at least 0.
|
|
* @post If this function leaves the queue not full, it also reenables
|
|
* reading from itrm->in.std. (Because it does not add to the queue,
|
|
* it never need disable reading.) */
|
|
static int
|
|
process_queue(struct itrm *itrm)
|
|
{
|
|
struct interlink_event ev;
|
|
int el = 0;
|
|
|
|
if (!itrm->in.queue.len) goto return_without_event;
|
|
assert(!itrm->blocked);
|
|
if_assert_failed return 0; /* unlike goto, don't enable reading */
|
|
|
|
set_kbd_interlink_event(&ev, KBD_UNDEF, KBD_MOD_NONE);
|
|
|
|
#ifdef DEBUG_ITRM_QUEUE
|
|
{
|
|
int i;
|
|
|
|
/* Dump current queue in a readable form to stderr. */
|
|
for (i = 0; i < itrm->in.queue.len; i++)
|
|
if (itrm->in.queue.data[i] == ASCII_ESC)
|
|
fprintf(stderr, "ESC ");
|
|
else if (isprint(itrm->in.queue.data[i]) && !isspace((unsigned char)itrm->in.queue.data[i]))
|
|
fprintf(stderr, "%c ", itrm->in.queue.data[i]);
|
|
else
|
|
fprintf(stderr, "0x%02x ", itrm->in.queue.data[i]);
|
|
|
|
fprintf(stderr, "\n");
|
|
fflush(stderr);
|
|
}
|
|
#endif /* DEBUG_ITRM_QUEUE */
|
|
|
|
/* el == -1 means itrm->in.queue appears to be the beginning of an
|
|
* escape sequence but it is not yet complete. Set a timer;
|
|
* if it times out, then assume it wasn't an escape sequence
|
|
* after all.
|
|
* el == 0 means this function has not yet figured out what the data
|
|
* in itrm->in.queue is, but some possibilities remain.
|
|
* One of them will be chosen before returning.
|
|
* el > 0 means some bytes were successfully parsed from the beginning
|
|
* of itrm->in.queue and should now be removed from there.
|
|
* However, this does not always imply an event will be queued.
|
|
*/
|
|
|
|
/* ELinks should also recognize U+009B CONTROL SEQUENCE INTRODUCER
|
|
* as meaning the same as ESC 0x5B, and U+008F SINGLE SHIFT THREE as
|
|
* meaning the same as ESC 0x4F, but those cannot yet be implemented
|
|
* because of bug 777: the UTF-8 decoder is run too late. */
|
|
if (itrm->in.queue.data[0] == ASCII_ESC) {
|
|
if (itrm->in.queue.len < 2) {
|
|
el = -1;
|
|
} else if (itrm->in.queue.data[1] == 0x5B /* CSI */) {
|
|
el = decode_terminal_escape_sequence(itrm, &ev);
|
|
} else if (itrm->in.queue.data[1] == 0x4F /* SS3 */) {
|
|
el = decode_terminal_application_key(itrm, &ev);
|
|
} else if (itrm->in.queue.data[1] == ASCII_ESC) {
|
|
/* ESC ESC can be either Alt-Esc or the
|
|
* beginning of e.g. ESC ESC 0x5B 0x41,
|
|
* which we should parse as Esc Up. */
|
|
if (itrm->in.queue.len < 3) {
|
|
if (get_ui_double_esc()) {
|
|
el = decode_terminal_escape_sequence(itrm, &ev);
|
|
} else {
|
|
/* Need more data to figure it out. */
|
|
el = -1;
|
|
}
|
|
} else if (itrm->in.queue.data[2] == 0x5B
|
|
|| itrm->in.queue.data[2] == 0x4F) {
|
|
/* The first ESC appears to be followed
|
|
* by an escape sequence. Treat it as
|
|
* a standalone Esc. */
|
|
el = 1;
|
|
set_kbd_event(itrm, &ev,
|
|
itrm->in.queue.data[0],
|
|
KBD_MOD_NONE);
|
|
} else {
|
|
/* The second ESC of ESC ESC is not the
|
|
* beginning of any known escape sequence.
|
|
* This must be Alt-Esc, then. */
|
|
el = 2;
|
|
set_kbd_event(itrm, &ev,
|
|
itrm->in.queue.data[1],
|
|
KBD_MOD_ALT);
|
|
}
|
|
}
|
|
if (el == 0) { /* Begins with ESC, but none of the above */
|
|
el = 2;
|
|
set_kbd_event(itrm, &ev, itrm->in.queue.data[1],
|
|
KBD_MOD_ALT);
|
|
}
|
|
|
|
} else if (itrm->in.queue.data[0] == 0) {
|
|
static const struct term_event_keyboard os2xtd[256] = {
|
|
#include "terminal/key.inc"
|
|
};
|
|
|
|
if (itrm->in.queue.len < 2)
|
|
el = -1;
|
|
else {
|
|
el = 2;
|
|
set_kbd_interlink_event(&ev,
|
|
os2xtd[(unsigned char)itrm->in.queue.data[1]].key,
|
|
os2xtd[(unsigned char)itrm->in.queue.data[1]].modifier);
|
|
}
|
|
}
|
|
|
|
if (el == 0) {
|
|
el = 1;
|
|
set_kbd_event(itrm, &ev, itrm->in.queue.data[0],
|
|
itrm->bracketed_pasting ? KBD_MOD_PASTE : KBD_MOD_NONE);
|
|
}
|
|
|
|
/* The call to decode_terminal_escape_sequence() might have changed the
|
|
* keyboard event to a mouse event. */
|
|
if (ev.ev == EVENT_MOUSE || ev.info.keyboard.key != KBD_UNDEF) {
|
|
itrm_queue_event(itrm, (char *) &ev, sizeof(ev));
|
|
itrm->bracketed_pasting =
|
|
(ev.ev == EVENT_KBD
|
|
&& (ev.info.keyboard.modifier & KBD_MOD_PASTE));
|
|
}
|
|
|
|
return_without_event:
|
|
if (el == -1) {
|
|
install_timer(&itrm->timer, ESC_TIMEOUT, (void (*)(void *)) kbd_timeout,
|
|
itrm);
|
|
return 0;
|
|
} else {
|
|
assertm(itrm->in.queue.len >= el, "event queue underflow");
|
|
if_assert_failed { itrm->in.queue.len = el; }
|
|
|
|
itrm->in.queue.len -= el;
|
|
if (itrm->in.queue.len)
|
|
memmove(itrm->in.queue.data, itrm->in.queue.data + el, itrm->in.queue.len);
|
|
|
|
if (itrm->in.queue.len < ITRM_IN_QUEUE_SIZE)
|
|
handle_itrm_stdin(itrm);
|
|
|
|
return el;
|
|
}
|
|
}
|
|
|
|
|
|
/** A select_handler_T read_func for itrm_in.std. This is called when
|
|
* characters typed by the user arrive from the terminal. */
|
|
static void
|
|
in_kbd(struct itrm *itrm)
|
|
{
|
|
int r;
|
|
|
|
if (!can_read(itrm->in.std)) return;
|
|
|
|
kill_timer(&itrm->timer);
|
|
|
|
if (itrm->in.queue.len >= ITRM_IN_QUEUE_SIZE) {
|
|
unhandle_itrm_stdin(itrm);
|
|
while (process_queue(itrm));
|
|
return;
|
|
}
|
|
|
|
r = safe_read(itrm->in.std, itrm->in.queue.data + itrm->in.queue.len,
|
|
ITRM_IN_QUEUE_SIZE - itrm->in.queue.len);
|
|
if (r <= 0) {
|
|
free_itrm(itrm);
|
|
return;
|
|
}
|
|
|
|
itrm->in.queue.len += r;
|
|
if (itrm->in.queue.len > ITRM_IN_QUEUE_SIZE) {
|
|
ERROR(gettext("Too many bytes read from the itrm!"));
|
|
itrm->in.queue.len = ITRM_IN_QUEUE_SIZE;
|
|
}
|
|
|
|
while (process_queue(itrm));
|
|
}
|
|
|
|
/** Enable reading from itrm_in.std. ELinks will read any available
|
|
* bytes from the tty into itrm->in.queue and then parse them.
|
|
* Reading should be enabled whenever itrm->in.queue is not full and
|
|
* itrm->blocked is 0. */
|
|
static void
|
|
handle_itrm_stdin(struct itrm *itrm)
|
|
{
|
|
assert(itrm->in.std >= 0);
|
|
if_assert_failed return;
|
|
|
|
set_handlers(itrm->in.std, (select_handler_T) in_kbd, NULL,
|
|
(select_handler_T) free_itrm, itrm);
|
|
}
|
|
|
|
/** Disable reading from itrm_in.std. Reading should be disabled
|
|
* whenever itrm->in.queue is full (there is no room for the data)
|
|
* or itrm->blocked is 1 (other processes may read the data). */
|
|
static void
|
|
unhandle_itrm_stdin(struct itrm *itrm)
|
|
{
|
|
assert(itrm->in.std >= 0);
|
|
if_assert_failed return;
|
|
|
|
set_handlers(itrm->in.std, (select_handler_T) NULL, NULL,
|
|
(select_handler_T) free_itrm, itrm);
|
|
}
|