Terminal emulator with all the bells and whistles https://www.enlightenment.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1806 lines
45 KiB

#include "private.h"
#include <Elementary.h>
#include <Ecore_Input.h>
#include <Ecore_IMF.h>
#include <Ecore_IMF_Evas.h>
#include "termpty.h"
#include "termptyesc.h"
#include "termptyops.h"
#include "backlog.h"
#include "keyin.h"
#if !defined(BINARY_TYFUZZ) && !defined(BINARY_TYTEST)
# include "win.h"
#endif
#include "termio.h"
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
#if defined (__sun) || defined (__sun__)
# include <stropts.h>
#endif
#include <assert.h>
/* specific log domain to help debug only terminal code parser */
int _termpty_log_dom = -1;
#undef CRITICAL
#undef ERR
#undef WRN
#undef INF
#undef DBG
#define CRITICAL(...) EINA_LOG_DOM_CRIT(_termpty_log_dom, __VA_ARGS__)
#define ERR(...) EINA_LOG_DOM_ERR(_termpty_log_dom, __VA_ARGS__)
#define WRN(...) EINA_LOG_DOM_WARN(_termpty_log_dom, __VA_ARGS__)
#define INF(...) EINA_LOG_DOM_INFO(_termpty_log_dom, __VA_ARGS__)
#define DBG(...) EINA_LOG_DOM_DBG(_termpty_log_dom, __VA_ARGS__)
void
termpty_init(void)
{
if (_termpty_log_dom >= 0) return;
_termpty_log_dom = eina_log_domain_register("termpty", NULL);
if (_termpty_log_dom < 0)
EINA_LOG_CRIT("Could not create logging domain '%s'", "termpty");
}
void
termpty_shutdown(void)
{
if (_termpty_log_dom < 0) return;
eina_log_domain_unregister(_termpty_log_dom);
_termpty_log_dom = -1;
}
Eina_Bool
termpty_can_handle_key(const Termpty *ty,
const Keys_Handler *khdl,
const Evas_Event_Key_Down *ev)
{
// if term app asked for kbd lock - don't handle here
if (ty->termstate.kbd_lock)
return EINA_FALSE;
// if app asked us to not do autorepeat - ignore press if is it is the same
// timestamp as last one
if ((ty->termstate.no_autorepeat) &&
(ev->timestamp == khdl->last_keyup))
return EINA_FALSE;
return EINA_TRUE;
}
void
termpty_handle_buf(Termpty *ty, const Eina_Unicode *codepoints, int len)
{
Eina_Unicode *c, *ce, *c2, *b, *d;
int n, bytes;
c = (Eina_Unicode *)codepoints;
ce = &(c[len]);
if (ty->buf)
{
bytes = (ty->buflen + len + 1) * sizeof(int);
b = realloc(ty->buf, bytes);
if (!b)
{
ERR(_("memerr: %s"), strerror(errno));
return;
}
DBG("realloc add %i + %i", (int)(ty->buflen * sizeof(int)), (int)(len * sizeof(int)));
if (!ty->buf_have_zero)
{
d = &(b[ty->buflen]);
ce = (Eina_Unicode *)codepoints + len;
for (c = (Eina_Unicode *)codepoints; c < ce; c++, d++)
{
*d = *c;
if (*c == 0x0)
{
ty->buf_have_zero = EINA_TRUE;
break;
}
}
for (; c < ce; c++, d++) *d = *c;
}
else
{
bytes = len * sizeof(Eina_Unicode);
memcpy(&(b[ty->buflen]), codepoints, bytes);
}
ty->buf = b;
ty->buflen += len;
ty->buf[ty->buflen] = 0;
c = ty->buf;
ce = c + ty->buflen;
while (c < ce)
{
n = termpty_handle_seq(ty, c, ce);
if (n == 0)
{
Eina_Unicode *tmp = ty->buf;
ty->buf = NULL;
ty->buflen = 0;
bytes = ((char *)ce - (char *)c) + sizeof(Eina_Unicode);
DBG("malloc till %i", (int)(bytes - sizeof(Eina_Unicode)));
ty->buf = malloc(bytes);
if (!ty->buf)
{
ERR(_("memerr: %s"), strerror(errno));
return;
}
ty->buf_have_zero = EINA_FALSE;
for (d = ty->buf, c2 = c; c2 < ce; c2++, d++)
{
*d = *c2;
if (*c2 == 0x0)
{
ty->buf_have_zero = EINA_TRUE;
break;
}
}
for (; c2 < ce; c2++, d++) *d = *c2;
bytes = (char *)ce - (char *)c;
ty->buflen = bytes / sizeof(Eina_Unicode);
ty->buf[ty->buflen] = 0;
free(tmp);
break;
}
c += n;
}
if (c == ce)
{
if (ty->buf)
{
ty->buf_have_zero = EINA_FALSE;
free(ty->buf);
ty->buf = NULL;
}
ty->buflen = 0;
}
}
else
{
ty->buf_have_zero = EINA_TRUE;
while (c < ce)
{
n = termpty_handle_seq(ty, c, ce);
if (n == 0)
{
bytes = ((char *)ce - (char *)c) + sizeof(Eina_Unicode);
ty->buf_have_zero = EINA_FALSE;
ty->buf = malloc(bytes);
DBG("malloc %i", (int)(bytes - sizeof(Eina_Unicode)));
if (!ty->buf)
{
ERR(_("memerr: %s"), strerror(errno));
}
else
{
ty->buf_have_zero = EINA_FALSE;
for (d = ty->buf, c2 = c; c2 < ce; c2++, d++)
{
*d = *c2;
if (*c2 == 0x0)
{
ty->buf_have_zero = EINA_TRUE;
break;
}
}
for (; c2 < ce; c2++, d++) *d = *c2;
bytes = (char *)ce - (char *)c;
ty->buflen = bytes / sizeof(Eina_Unicode);
ty->buf[ty->buflen] = 0;
}
break;
}
c += n;
}
}
}
static void
_pty_size(Termpty *ty)
{
struct winsize sz;
sz.ws_col = ty->w;
sz.ws_row = ty->h;
sz.ws_xpixel = 0;
sz.ws_ypixel = 0;
if (ty->fd >= 0)
if (ioctl(ty->fd, TIOCSWINSZ, &sz) < 0)
ERR(_("Size set ioctl failed: %s"), strerror(errno));
}
static Eina_Bool
_handle_read(Termpty *ty, Eina_Bool false_on_empty)
{
int len, reads;
// read up to 64 * 4096 bytes
for (reads = 0; reads < 64; reads++)
{
Eina_Unicode codepoint[4097];
char buf[4097];
char *rbuf = buf;
int i, j;
len = sizeof(buf) - 1;
for (i = 0; i < (int)sizeof(ty->oldbuf) && ty->oldbuf[i] & 0x80; i++)
{
*rbuf = ty->oldbuf[i];
rbuf++;
len--;
}
errno = 0;
len = read(ty->fd, rbuf, len);
if ((len < 0 && !(errno == EAGAIN || errno == EINTR)) ||
(len == 0 && errno != 0))
{
/* Do not print error if the child has exited */
if (ty->pid != -1)
{
ERR("error while reading from tty slave fd: %s", strerror(errno));
}
close(ty->fd);
ty->fd = -1;
if (ty->hand_fd)
ecore_main_fd_handler_del(ty->hand_fd);
ty->hand_fd = NULL;
return ECORE_CALLBACK_CANCEL;
}
if (len <= 0) break;
for (i = 0; i < (int)sizeof(ty->oldbuf); i++)
ty->oldbuf[i] = 0;
len += rbuf - buf;
/*
printf(" I: ");
int jj;
for (jj = 0; jj < len; jj++)
{
if ((buf[jj] < ' ') || (buf[jj] >= 0x7f))
printf("\033[33m%02x\033[0m", (unsigned char)buf[jj]);
else
printf("%c", buf[jj]);
}
printf("\n");
*/
buf[len] = 0;
// convert UTF8 to codepoint integers
j = 0;
for (i = 0; i < len;)
{
Eina_Unicode g = 0, prev_i = i;
if (buf[i])
{
g = eina_unicode_utf8_next_get(buf, &i);
if ((0xdc80 <= g) && (g <= 0xdcff) &&
(len - (int)prev_i) <= (int)sizeof(ty->oldbuf))
{
unsigned int k;
for (k = 0;
(k < (unsigned int)sizeof(ty->oldbuf)) &&
(k < (unsigned int)(len - prev_i));
k++)
{
ty->oldbuf[k] = buf[prev_i+k];
}
DBG("failure at %d/%d/%d", (int)prev_i, (int)i, len);
break;
}
}
else
{
g = 0;
i++;
}
codepoint[j] = g;
j++;
}
codepoint[j] = 0;
// DBG("---------------- handle buf %i", j);
termpty_handle_buf(ty, codepoint, j);
}
if (ty->cb.change.func)
ty->cb.change.func(ty->cb.change.data);
#if defined(BINARY_TYFUZZ) || defined(BINARY_TYTEST)
if (len <= 0)
{
ty->exit_code = 0;
ty->pid = -1;
if (ty->hand_exe_exit)
ecore_event_handler_del(ty->hand_exe_exit);
ty->hand_exe_exit = NULL;
if (ty->hand_fd)
ecore_main_fd_handler_del(ty->hand_fd);
ty->hand_fd = NULL;
ty->fd = -1;
ty->slavefd = -1;
if (ty->cb.exited.func)
ty->cb.exited.func(ty->cb.exited.data);
return ECORE_CALLBACK_CANCEL;
}
#endif
if ((false_on_empty) && (len <= 0))
return ECORE_CALLBACK_CANCEL;
return ECORE_CALLBACK_RENEW;
}
static Eina_Bool
_handle_write(Termpty *ty)
{
struct ty_sb *sb = &ty->write_buffer;
ssize_t len;
if (!sb->len)
return ECORE_CALLBACK_RENEW;
len = write(ty->fd, sb->buf, sb->len);
if (len < 0 && (errno != EINTR && errno != EAGAIN))
{
ERR(_("Could not write to file descriptor %d: %s"),
ty->fd, strerror(errno));
return ECORE_CALLBACK_CANCEL;
}
ty_sb_lskip(sb, len);
if (!sb->len && ty->hand_fd)
ecore_main_fd_handler_active_set(ty->hand_fd,
ECORE_FD_ERROR |
ECORE_FD_READ);
return ECORE_CALLBACK_RENEW;
}
static Eina_Bool
_fd_do(Termpty *ty, Ecore_Fd_Handler *fd_handler, Eina_Bool false_on_empty)
{
if (ecore_main_fd_handler_active_get(fd_handler, ECORE_FD_ERROR))
{
DBG("error while doing I/O on tty slave fd");
ty->hand_fd = NULL;
return ECORE_CALLBACK_CANCEL;
}
if (ty->fd == -1)
{
ty->hand_fd = NULL;
return ECORE_CALLBACK_CANCEL;
}
// it seems the BSDs can not read from this side of the pair if the other side
// is closed ... but no longer just bsd's - i have seen this now on linux
if (ty->pid == -1)
{
ty->hand_fd = NULL;
return ECORE_CALLBACK_CANCEL;
}
if (ecore_main_fd_handler_active_get(fd_handler, ECORE_FD_READ))
{
if (!_handle_read(ty, false_on_empty))
return ECORE_CALLBACK_CANCEL;
}
if (ecore_main_fd_handler_active_get(fd_handler, ECORE_FD_WRITE))
{
if (!_handle_write(ty))
return ECORE_CALLBACK_CANCEL;
}
return ECORE_CALLBACK_PASS_ON;
}
static Eina_Bool
_cb_fd(void *data, Ecore_Fd_Handler *fd_handler)
{
return _fd_do(data, fd_handler, EINA_FALSE);
}
static Eina_Bool
_cb_exe_exit(void *data,
int _type EINA_UNUSED,
void *event)
{
Ecore_Exe_Event_Del *ev = event;
Termpty *ty = data;
Eina_Bool res;
DBG("got exit (code:%d) on pid %d (ty->pid=%d)", ev->exit_code, ev->pid, ty->pid);
if (ev->pid != ty->pid)
return ECORE_CALLBACK_PASS_ON;
ty->exit_code = ev->exit_code;
ty->pid = -1;
if (ty->hand_exe_exit)
ecore_event_handler_del(ty->hand_exe_exit);
ty->hand_exe_exit = NULL;
/* Read everything till the end */
res = ECORE_CALLBACK_PASS_ON;
while (ty->hand_fd && res != ECORE_CALLBACK_CANCEL)
{
res = _fd_do(ty, ty->hand_fd, EINA_TRUE);
}
if (ty->hand_fd)
ecore_main_fd_handler_del(ty->hand_fd);
ty->hand_fd = NULL;
if (ty->fd >= 0)
close(ty->fd);
ty->fd = -1;
if (ty->slavefd >= 0)
close(ty->slavefd);
ty->slavefd = -1;
if (ty->cb.exited.func)
ty->cb.exited.func(ty->cb.exited.data);
return ECORE_CALLBACK_PASS_ON;
}
static void
_limit_coord(Termpty *ty)
{
TERMPTY_RESTRICT_FIELD(ty->termstate.had_cr_x, 0, ty->w);
TERMPTY_RESTRICT_FIELD(ty->termstate.had_cr_y, 0, ty->h);
TERMPTY_RESTRICT_FIELD(ty->cursor_state.cx, 0, ty->w);
TERMPTY_RESTRICT_FIELD(ty->cursor_state.cy, 0, ty->h);
TERMPTY_RESTRICT_FIELD(ty->cursor_save[0].cx, 0, ty->w);
TERMPTY_RESTRICT_FIELD(ty->cursor_save[0].cy, 0, ty->h);
TERMPTY_RESTRICT_FIELD(ty->cursor_save[1].cx, 0, ty->w);
TERMPTY_RESTRICT_FIELD(ty->cursor_save[1].cy, 0, ty->h);
}
void
termpty_resize_tabs(Termpty *ty, int old_w, int new_w)
{
unsigned int *new_tabs;
int i;
size_t nb_elems;
if ((new_w == old_w) && ty->tabs) return;
nb_elems = ROUND_UP(new_w, 8);
new_tabs = calloc(nb_elems, sizeof(unsigned int));
if (!new_tabs) return;
if (ty->tabs)
{
size_t n = old_w;
if (nb_elems < n) n = nb_elems;
if (n > 0) memcpy(new_tabs, ty->tabs, n * sizeof(unsigned int));
free(ty->tabs);
}
ty->tabs = new_tabs;
for (i = ROUND_UP(ty->w, TAB_WIDTH); i < new_w; i += TAB_WIDTH)
{
TAB_SET(ty, i);
}
}
static Eina_Bool
_is_shell_valid(const char *cmd)
{
struct stat st;
if (!cmd)
return EINA_FALSE;
if (cmd[0] == '\0')
return EINA_FALSE;
if (cmd[0] != '/')
{
ERR("shell command '%s' is not an absolute path", cmd);
return EINA_FALSE;
}
if (stat(cmd, &st) != 0)
{
ERR("shell command '%s' can not be stat(): %s", cmd, strerror(errno));
return EINA_FALSE;
}
if ((st.st_mode & S_IFMT) != S_IFREG)
{
ERR("shell command '%s' is not a regular file", cmd);
return EINA_FALSE;
}
if ((st.st_mode & S_IXOTH) == 0)
{
ERR("shell command '%s' is not executable", cmd);
return EINA_FALSE;
}
return EINA_TRUE;
}
Termpty *
termpty_new(const char *cmd, Eina_Bool login_shell, const char *cd,
int w, int h, Config *config, const char *title,
Ecore_Window window_id)
{
Termpty *ty;
const char *pty;
int mode;
struct termios t;
Eina_Bool needs_shell;
const char *shell = NULL;
const char *args[4] = {NULL, NULL, NULL, NULL};
const char *arg0;
ty = calloc(1, sizeof(Termpty));
if (!ty) return NULL;
ty->config = config;
ty->w = w;
ty->h = h;
ty->backsize = config->scrollback;
ty->screen = calloc(1, sizeof(Termcell) * ty->w * ty->h);
if (!ty->screen)
{
ERR("Allocation of term %s %ix%i failed: %s",
"screen", ty->w, ty->h, strerror(errno));
goto err;
}
ty->screen2 = calloc(1, sizeof(Termcell) * ty->w * ty->h);
if (!ty->screen2)
{
ERR("Allocation of term %s %ix%i failed: %s",
"screen2", ty->w, ty->h, strerror(errno));
goto err;
}
ty->hl.bitmap = calloc(1, HL_LINKS_MAX / 8); /* bit map for 1 << 16 elements */
if (!ty->hl.bitmap)
{
ERR("Allocation of %d bytes failed: %s",
HL_LINKS_MAX / 8, strerror(errno));
goto err;
}
/* Mark id 0 as set */
ty->hl.bitmap[0] = 1;
termpty_resize_tabs(ty, 0, w);
termpty_reset_state(ty);
ty->circular_offset = 0;
#if defined(BINARY_TYFUZZ) || defined(BINARY_TYTEST)
ty->fd = STDIN_FILENO;
ty->hand_fd = ecore_main_fd_handler_add(ty->fd,
ECORE_FD_READ | ECORE_FD_ERROR,
_cb_fd, ty,
NULL, NULL);
_pty_size(ty);
termpty_save_register(ty);
return ty;
#endif
needs_shell = ((!cmd) ||
(strpbrk(cmd, " |&;<>()$`\\\"'*?#") != NULL));
DBG("cmd='%s' needs_shell=%u", cmd ? cmd : "", needs_shell);
if (needs_shell)
{
shell = getenv("SHELL");
if (!_is_shell_valid(shell))
shell = NULL;
if (!shell)
{
uid_t uid = getuid();
struct passwd *pw = getpwuid(uid);
if (pw) shell = pw->pw_shell;
}
if (!shell)
{
WRN(_("Could not find shell, falling back to %s"), "/bin/sh");
shell = "/bin/sh";
}
}
if (!needs_shell)
args[0] = cmd;
else
{
args[0] = shell;
if (cmd)
{
args[1] = "-c";
args[2] = cmd;
}
}
arg0 = strrchr(args[0], '/');
if (!arg0) arg0 = args[0];
else arg0++;
if (title)
{
ty->prop.title = eina_stringshare_add(title);
ty->prop.user_title = eina_stringshare_add(title);
}
else
ty->prop.title = eina_stringshare_add(arg0);
ty->fd = posix_openpt(O_RDWR | O_NOCTTY);
if (ty->fd < 0)
{
ERR(_("Function %s failed: %s"), "posix_openpt()", strerror(errno));
goto err;
}
if (grantpt(ty->fd) != 0)
{
WRN(_("Function %s failed: %s"), "grantpt()", strerror(errno));
}
if (unlockpt(ty->fd) != 0)
{
ERR(_("Function %s failed: %s"), "unlockpt()", strerror(errno));
goto err;
}
pty = ptsname(ty->fd);
ty->slavefd = open(pty, O_RDWR | O_NOCTTY);
if (ty->slavefd < 0)
{
ERR(_("open() of pty '%s' failed: %s"), pty, strerror(errno));
goto err;
}
mode = fcntl(ty->fd, F_GETFL, 0);
if (mode < 0)
{
ERR(_("fcntl() on pty '%s' failed: %s"), pty, strerror(errno));
goto err;
}
if (!(mode & O_NDELAY))
if (fcntl(ty->fd, F_SETFL, mode | O_NDELAY))
{
ERR(_("fcntl() on pty '%s' failed: %s"), pty, strerror(errno));
goto err;
}
#if defined (__sun) || defined (__sun__)
if (ioctl(ty->slavefd, I_PUSH, "ptem") < 0
|| ioctl(ty->slavefd, I_PUSH, "ldterm") < 0
|| ioctl(ty->slavefd, I_PUSH, "ttcompat") < 0)
{
ERR(_("ioctl() on pty '%s' failed: %s"), pty, strerror(errno));
goto err;
}
# endif
if (tcgetattr(ty->slavefd, &t) < 0)
{
ERR("unable to tcgetattr: %s", strerror(errno));
goto err;
}
t.c_cc[VERASE] = (config->erase_is_del) ? 0x7f : 0x8;
#ifdef IUTF8
t.c_iflag |= IUTF8;
#endif
if (tcsetattr(ty->slavefd, TCSANOW, &t) < 0)
{
ERR("unable to tcsetattr: %s", strerror(errno));
goto err;
}
ty->hand_exe_exit = ecore_event_handler_add(ECORE_EXE_EVENT_DEL,
_cb_exe_exit, ty);
if (!ty->hand_exe_exit)
{
ERR("event handler add failed");
goto err;
}
ty->pid = fork();
8 years ago
if (ty->pid < 0)
{
ERR("unable to fork: %s", strerror(errno));