1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-11-02 08:57:19 -04:00
elinks/src/terminal/kbd.c

1326 lines
37 KiB
C
Raw Normal View History

2007-07-27 09:50:37 -04:00
/** Support for keyboard interface
* @file
*
2007-07-31 06:38:00 -04:00
* @todo TODO: move stuff from here to itrm.{c,h} and mouse.{c,h}
2007-07-27 09:50:37 -04:00
*/
#ifdef HAVE_CONFIG_H
#include "config.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
2007-07-27 09:50:37 -04:00
/** 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);
}
2007-07-27 09:50:37 -04:00
/** A select_handler_T write_func for itrm_out.sock. This is called
2007-07-31 06:38:00 -04:00
* 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),
2022-02-21 13:03:54 -05:00
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;
2022-01-16 13:38:30 -05:00
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)
2007-07-27 09:50:37 -04:00
#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) {
2020-06-24 13:18:18 -04:00
send_mouse_init_sequence(h);
}
#endif
#ifndef CONFIG_OS_DOS
write_sequence(h, INIT_BRACKETED_PASTE_SEQ);
#endif
done_draw();
}
2007-07-27 09:50:37 -04:00
#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;
2010-03-21 12:18:32 -04:00
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;
2010-03-21 12:18:32 -04:00
#elif defined(HAVE_FPATHCONF)
vdisable = fpathconf(itrm->in.ctl, _PC_VDISABLE);
#endif
2010-03-21 12:18:32 -04:00
#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];
2010-03-21 12:18:32 -04:00
#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;
}
2007-07-27 14:02:25 -04:00
/** 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.
*
2007-07-27 09:50:37 -04:00
* @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.
*
2007-07-27 09:50:37 -04:00
* @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,
2007-07-27 14:02:25 -04:00
* 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;
}
2022-01-16 15:08:50 -05:00
itrm = (struct itrm *)mem_calloc(1, sizeof(*itrm));
if (!itrm) return;
2022-01-16 15:08:50 -05:00
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;
Bug 885: Proper charset support in xterm window title When ELinks runs in an X11 terminal emulator (e.g. xterm), or in GNU Screen, it tries to update the title of the window to match the title of the current document. To do this, ELinks sends an "OSC 1 ; Pt BEL" sequence to the terminal. Unfortunately, xterm expects the Pt string to be in the ISO-8859-1 charset, making it impossible to display e.g. Cyrillic characters. In xterm patch #210 (2006-03-12) however, there is a menu item and a resource that can make xterm take the Pt string in UTF-8 instead, allowing characters from all around the world. The downside is that ELinks apparently cannot ask xterm whether the setting is on or off; so add a terminal._template_.latin1_title option to ELinks and let the user edit that instead. Complete list of changes: - Add the terminal._template_.latin1_title option. But do not add that to the terminal options window because it's already rather crowded there. - In set_window_title(), take a new codepage argument. Use it to decode the title into Unicode characters, and remove only actual control characters. For example, CP437 has graphical characters in the 0x80...0x9F range, so don't remove those, even though ISO-8859-1 has control characters in the same range. Likewise, don't misinterpret single bytes of UTF-8 characters as control characters. - In set_window_title(), do not truncate the title to the width of the window. The font is likely to be different and proportional anyway. But do truncate before 1024 bytes, an xterm limit. - In struct itrm, add a title_codepage member to remember which charset the master said it was going to use in the terminal window title. Initialize title_codepage in handle_trm(), update it in dispatch_special() if the master sends the new request TERM_FN_TITLE_CODEPAGE, and use it in most set_window_title() calls; but not in the one that sets $TERM as the title, because that string was not received from the master and should consist of ASCII characters only. - In set_terminal_title(), convert the caller-provided title to ISO-8859-1 or UTF-8 if appropriate, and report the codepage to the slave with the new TERM_FN_TITLE_CODEPAGE request. The conversion can run out of memory, so return a success/error flag, rather than void. In display_window_title(), check this result and don't update caches on error. - Add a NEWS entry for all of this.
2008-12-28 20:09:53 -05:00
/* 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);
}
2007-07-27 09:50:37 -04:00
/** A select_handler_T read_func and error_func for the pipe (long) @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) {
Bug 885: Proper charset support in xterm window title When ELinks runs in an X11 terminal emulator (e.g. xterm), or in GNU Screen, it tries to update the title of the window to match the title of the current document. To do this, ELinks sends an "OSC 1 ; Pt BEL" sequence to the terminal. Unfortunately, xterm expects the Pt string to be in the ISO-8859-1 charset, making it impossible to display e.g. Cyrillic characters. In xterm patch #210 (2006-03-12) however, there is a menu item and a resource that can make xterm take the Pt string in UTF-8 instead, allowing characters from all around the world. The downside is that ELinks apparently cannot ask xterm whether the setting is on or off; so add a terminal._template_.latin1_title option to ELinks and let the user edit that instead. Complete list of changes: - Add the terminal._template_.latin1_title option. But do not add that to the terminal options window because it's already rather crowded there. - In set_window_title(), take a new codepage argument. Use it to decode the title into Unicode characters, and remove only actual control characters. For example, CP437 has graphical characters in the 0x80...0x9F range, so don't remove those, even though ISO-8859-1 has control characters in the same range. Likewise, don't misinterpret single bytes of UTF-8 characters as control characters. - In set_window_title(), do not truncate the title to the width of the window. The font is likely to be different and proportional anyway. But do truncate before 1024 bytes, an xterm limit. - In struct itrm, add a title_codepage member to remember which charset the master said it was going to use in the terminal window title. Initialize title_codepage in handle_trm(), update it in dispatch_special() if the master sends the new request TERM_FN_TITLE_CODEPAGE, and use it in most set_window_title() calls; but not in the one that sets $TERM as the title, because that string was not received from the master and should consist of ASCII characters only. - In set_terminal_title(), convert the caller-provided title to ISO-8859-1 or UTF-8 if appropriate, and report the codepage to the slave with the new TERM_FN_TITLE_CODEPAGE request. The conversion can run out of memory, so return a success/error flag, rather than void. In display_window_title(), check this result and don't update caches on error. - Add a NEWS entry for all of this.
2008-12-28 20:09:53 -05:00
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)
Bug 885: Proper charset support in xterm window title When ELinks runs in an X11 terminal emulator (e.g. xterm), or in GNU Screen, it tries to update the title of the window to match the title of the current document. To do this, ELinks sends an "OSC 1 ; Pt BEL" sequence to the terminal. Unfortunately, xterm expects the Pt string to be in the ISO-8859-1 charset, making it impossible to display e.g. Cyrillic characters. In xterm patch #210 (2006-03-12) however, there is a menu item and a resource that can make xterm take the Pt string in UTF-8 instead, allowing characters from all around the world. The downside is that ELinks apparently cannot ask xterm whether the setting is on or off; so add a terminal._template_.latin1_title option to ELinks and let the user edit that instead. Complete list of changes: - Add the terminal._template_.latin1_title option. But do not add that to the terminal options window because it's already rather crowded there. - In set_window_title(), take a new codepage argument. Use it to decode the title into Unicode characters, and remove only actual control characters. For example, CP437 has graphical characters in the 0x80...0x9F range, so don't remove those, even though ISO-8859-1 has control characters in the same range. Likewise, don't misinterpret single bytes of UTF-8 characters as control characters. - In set_window_title(), do not truncate the title to the width of the window. The font is likely to be different and proportional anyway. But do truncate before 1024 bytes, an xterm limit. - In struct itrm, add a title_codepage member to remember which charset the master said it was going to use in the terminal window title. Initialize title_codepage in handle_trm(), update it in dispatch_special() if the master sends the new request TERM_FN_TITLE_CODEPAGE, and use it in most set_window_title() calls; but not in the one that sets $TERM as the title, because that string was not received from the master and should consist of ASCII characters only. - In set_terminal_title(), convert the caller-provided title to ISO-8859-1 or UTF-8 if appropriate, and report the codepage to the slave with the new TERM_FN_TITLE_CODEPAGE request. The conversion can run out of memory, so return a success/error flag, rather than void. In display_window_title(), check this result and don't update caches on error. - Add a NEWS entry for all of this.
2008-12-28 20:09:53 -05:00
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;
}
2007-07-27 09:50:37 -04:00
/** 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;
}
Bug 885: Proper charset support in xterm window title When ELinks runs in an X11 terminal emulator (e.g. xterm), or in GNU Screen, it tries to update the title of the window to match the title of the current document. To do this, ELinks sends an "OSC 1 ; Pt BEL" sequence to the terminal. Unfortunately, xterm expects the Pt string to be in the ISO-8859-1 charset, making it impossible to display e.g. Cyrillic characters. In xterm patch #210 (2006-03-12) however, there is a menu item and a resource that can make xterm take the Pt string in UTF-8 instead, allowing characters from all around the world. The downside is that ELinks apparently cannot ask xterm whether the setting is on or off; so add a terminal._template_.latin1_title option to ELinks and let the user edit that instead. Complete list of changes: - Add the terminal._template_.latin1_title option. But do not add that to the terminal options window because it's already rather crowded there. - In set_window_title(), take a new codepage argument. Use it to decode the title into Unicode characters, and remove only actual control characters. For example, CP437 has graphical characters in the 0x80...0x9F range, so don't remove those, even though ISO-8859-1 has control characters in the same range. Likewise, don't misinterpret single bytes of UTF-8 characters as control characters. - In set_window_title(), do not truncate the title to the width of the window. The font is likely to be different and proportional anyway. But do truncate before 1024 bytes, an xterm limit. - In struct itrm, add a title_codepage member to remember which charset the master said it was going to use in the terminal window title. Initialize title_codepage in handle_trm(), update it in dispatch_special() if the master sends the new request TERM_FN_TITLE_CODEPAGE, and use it in most set_window_title() calls; but not in the one that sets $TERM as the title, because that string was not received from the master and should consist of ASCII characters only. - In set_terminal_title(), convert the caller-provided title to ISO-8859-1 or UTF-8 if appropriate, and report the codepage to the slave with the new TERM_FN_TITLE_CODEPAGE request. The conversion can run out of memory, so return a success/error flag, rather than void. In display_window_title(), check this result and don't update caches on error. - Add a NEWS entry for all of this.
2008-12-28 20:09:53 -05:00
/* 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;
Bug 885: Proper charset support in xterm window title When ELinks runs in an X11 terminal emulator (e.g. xterm), or in GNU Screen, it tries to update the title of the window to match the title of the current document. To do this, ELinks sends an "OSC 1 ; Pt BEL" sequence to the terminal. Unfortunately, xterm expects the Pt string to be in the ISO-8859-1 charset, making it impossible to display e.g. Cyrillic characters. In xterm patch #210 (2006-03-12) however, there is a menu item and a resource that can make xterm take the Pt string in UTF-8 instead, allowing characters from all around the world. The downside is that ELinks apparently cannot ask xterm whether the setting is on or off; so add a terminal._template_.latin1_title option to ELinks and let the user edit that instead. Complete list of changes: - Add the terminal._template_.latin1_title option. But do not add that to the terminal options window because it's already rather crowded there. - In set_window_title(), take a new codepage argument. Use it to decode the title into Unicode characters, and remove only actual control characters. For example, CP437 has graphical characters in the 0x80...0x9F range, so don't remove those, even though ISO-8859-1 has control characters in the same range. Likewise, don't misinterpret single bytes of UTF-8 characters as control characters. - In set_window_title(), do not truncate the title to the width of the window. The font is likely to be different and proportional anyway. But do truncate before 1024 bytes, an xterm limit. - In struct itrm, add a title_codepage member to remember which charset the master said it was going to use in the terminal window title. Initialize title_codepage in handle_trm(), update it in dispatch_special() if the master sends the new request TERM_FN_TITLE_CODEPAGE, and use it in most set_window_title() calls; but not in the one that sets $TERM as the title, because that string was not received from the master and should consist of ASCII characters only. - In set_terminal_title(), convert the caller-provided title to ISO-8859-1 or UTF-8 if appropriate, and report the codepage to the slave with the new TERM_FN_TITLE_CODEPAGE request. The conversion can run out of memory, so return a success/error flag, rather than void. In display_window_title(), check this result and don't update caches on error. - Add a NEWS entry for all of this.
2008-12-28 20:09:53 -05:00
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
2022-02-16 14:49:21 -05:00
safe_hard_write(int fd, const char *buf, int len)
{
if (is_blocked()) return;
want_draw();
hard_write(fd, buf, len);
done_draw();
}
2007-07-27 09:50:37 -04:00
/** 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;
2007-07-14 05:26:45 -04:00
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 */
2007-07-14 05:26:45 -04:00
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;
2022-01-16 13:09:27 -05:00
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);
2007-07-14 05:26:45 -04:00
if (fg == TERM_EXEC_FG) block_itrm();
2022-04-22 15:47:52 -04:00
#ifndef WIN32
blockh = start_thread((void (*)(void *, int)) exec_thread,
param, param_len);
2022-04-22 15:47:52 -04:00
#else
exec_thread(param, param_len);
#endif
mem_free(param);
if (blockh == -1) {
2007-07-14 05:26:45 -04:00
if (fg == TERM_EXEC_FG)
unblock_itrm();
goto nasty_thing;
}
2007-07-14 05:26:45 -04:00
if (fg == TERM_EXEC_FG) {
set_handlers(blockh, (select_handler_T) unblock_itrm_x,
NULL, (select_handler_T) unblock_itrm_x,
(void *) (long) blockh);
} else {
set_handlers(blockh, close_handle, NULL, close_handle,
(void *) (long) 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);
}
2007-07-27 09:50:37 -04:00
/** 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.)
*
2007-07-27 09:50:37 -04:00
* @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
2021-12-20 06:15:11 -05:00
int ui_double_esc;
static inline int
get_ui_double_esc(void)
{
return ui_double_esc;
}
2007-07-27 09:50:37 -04:00
/** 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.)
*
2007-07-27 09:50:37 -04:00
* @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;
2021-12-20 06:15:11 -05:00
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;
}
2007-07-27 09:50:37 -04:00
/** Decode an escape sequence that begins with SS3 (SINGLE SHIFT 3).
* These are used for application cursor keys and the application keypad.
2007-07-27 09:50:37 -04:00
* @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 */
}
2007-07-27 09:50:37 -04:00
/** 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 */
2006-02-15 17:25:54 -05:00
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);
}
2007-07-27 09:50:37 -04:00
/** 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));
}
2007-07-27 09:50:37 -04:00
/** 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;
2006-07-28 08:57:50 -04:00
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 */
2006-07-28 08:57:50 -04:00
/* 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) {
2006-07-28 08:57:50 -04:00
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);
2006-07-28 08:57:50 -04:00
} 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) {
2021-12-20 06:15:11 -05:00
if (get_ui_double_esc()) {
el = decode_terminal_escape_sequence(itrm, &ev);
} else {
/* Need more data to figure it out. */
el = -1;
}
2006-07-28 08:57:50 -04:00
} 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],
2006-07-28 08:57:50 -04:00
KBD_MOD_NONE);
} else {
2006-07-28 08:57:50 -04:00
/* 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],
2006-07-28 08:57:50 -04:00
KBD_MOD_ALT);
}
}
if (el == 0) { /* Begins with ESC, but none of the above */
2006-07-28 08:57:50 -04:00
el = 2;
set_kbd_event(itrm, &ev, itrm->in.queue.data[1],
2006-07-28 08:57:50 -04:00
KBD_MOD_ALT);
}
} else if (itrm->in.queue.data[0] == 0) {
static const struct term_event_keyboard os2xtd[256] = {
#include "terminal/key.inc"
};
2006-07-28 08:57:50 -04:00
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);
2006-07-28 08:57:50 -04:00
}
}
2006-07-28 08:57:50 -04:00
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));
}
2006-08-13 14:57:35 -04:00
return_without_event:
2006-07-28 08:57:50 -04:00
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; }
2006-07-28 08:57:50 -04:00
itrm->in.queue.len -= el;
if (itrm->in.queue.len)
memmove(itrm->in.queue.data, itrm->in.queue.data + el, itrm->in.queue.len);
2006-07-28 08:57:50 -04:00
if (itrm->in.queue.len < ITRM_IN_QUEUE_SIZE)
handle_itrm_stdin(itrm);
2006-07-28 08:57:50 -04:00
return el;
}
}
2007-07-27 09:50:37 -04:00
/** 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));
}
2007-07-27 09:50:37 -04:00
/** 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);
}
2007-07-27 09:50:37 -04:00
/** 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);
}