#include <ctype.h> #include <errno.h> #include <fcntl.h> #include <langinfo.h> #include <linux/input.h> #include <locale.h> #include <signal.h> #include <stdarg.h> #include <stddef.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/epoll.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/uio.h> #include <sys/wait.h> #include <termios.h> #include <unistd.h> #include <fira_mono_tables.h> #include <keymap.h> #include <shell.h> static void term_init(int argc, const char *argv[]); static void term_interact(void); int main(int argc, const char *argv[]) { term_init(argc, argv); for (;;) term_interact(); } static void term_io_add(int fd, uint32_t events); static void term_io_modify(int fd, uint32_t events); static void term_log(const char *fmt, ...); #define array_size(a) (sizeof(a) / sizeof((a)[0])) static _Noreturn void die(const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "Error: "); vfprintf(stderr, fmt, args); va_end(args); fputc('\n', stderr); exit(1); } void * malloc_or_die(size_t size) { void *x = malloc(size); if (size > 0 && x == NULL) die("Failed to allocate memory."); return x; } static void enable_cloexec(int fd) { const char *error_message = "Failed to enable CLOEXEC on file."; int r = fcntl(fd, F_GETFD); if (r == -1) die(error_message); int flags = r; flags |= FD_CLOEXEC; r = fcntl(fd, F_SETFD, flags); if (r == -1) die(error_message); } struct ring { char *buf; int capacity, head, size; }; void ring_init(struct ring *r, void *buf, int capacity); int ring_size(struct ring *r); int ring_space(struct ring *r); char ring_pop(struct ring *r); void ring_push(struct ring *r, char c); void ring_read(struct ring *r, int fd, int *eof); void ring_write(struct ring *r, int fd); #define BOLD 1 #define UNDERLINE 2 #define REVERSE_VIDEO 4 #define ALTERNATE_CHARACTER_SET 8 struct cell { uint32_t bitmap_offset; }; struct line { struct cell *cells; }; struct cursor { int x, y; }; #define STACKSIZE 32 struct term_pty { const char *slave_name; char input_buf[8*1024]; char output_buf[512]; struct ring input; struct ring output; uint32_t events; int fd; }; struct term_machine { int (*func)(struct term_machine *machine, struct ring *input); unsigned int mode_flags; int sp; int stack[STACKSIZE]; struct line *lines; int nr_lines; int line_size; struct cursor cursor; }; struct term { struct term_pty pty; struct term_machine machine; unsigned char *buffer; unsigned int back_buffer:1; unsigned int back_buffer_is_locked:1; unsigned int is_draw_needed:1; uint32_t keyboard_modifiers; uint32_t width, height; int epoll_fd; FILE *log_file; // May be NULL. }; static void init_lines(struct term_machine *machine); static void parse(struct term_machine *machine, struct ring *input); static void term_draw(struct term *term); static void term_launch(struct term *term, uint32_t width, uint32_t height, unsigned char *buffer); static void term_on_input(struct term *term, uint16_t code, int32_t value); static void init_pty(struct term_pty *pty); static void init_io_buffers(struct term_pty *pty); void start_child(struct term_pty *pty, int nr_lines, int line_size); static int parsing_state_default(struct term_machine *machine, struct ring *input); static void term_shutdown(struct term *term); #define BYTES_PER_PIXEL 4 static struct metrics { int line_spacing; int advance; int baseline; int border_size_top; int border_size_bottom; int border_size_left; int border_size_right; } metrics; static void on_buffer_unlocked(struct term *term) { term->back_buffer_is_locked = 0; if (term->is_draw_needed) term_draw(term); } static uint32_t encode_color_xrgb(uint8_t r, uint8_t g, uint8_t b) { union { uint32_t u; uint8_t bytes[4]; } x = { .bytes = { b, g, r, 0 }, }; return x.u; } static void draw_pixmap(void *mem, const uint8_t *bytes, int rows, int width, int x_start, int y_start, uint32_t stride) { // The transformation from input bytes to output color values is the // linear mapping that maps the extremes as follows: // // 0x00 => 0xff // 0xff => 0x47 // // In other words, a clear byte (0x00) is mapped to white (0xff) and a // fully-set byte (0xff) is mapped to a dark gray (0x47). uint32_t *pixels = mem; uint32_t stride_in_pixels = stride / BYTES_PER_PIXEL; int x_end = x_start + width; int y_end = y_start + rows; for (int y = y_start, v = 0; y < y_end; y++, v++) { for (int x = x_start, u = 0; x < x_end; x++, u++) { uint32_t byte = bytes[v * width + u]; uint32_t value = (byte * 0x48 + (0xff - byte) * 0x100) / 0x100; uint32_t color = encode_color_xrgb(value, value, value); pixels[y * stride_in_pixels + x] = color; } } } static int clamp(int x, int a, int b) { if (x < a) return a; if (x > b) return b; return x; } static void draw_text(void *mem, struct term_machine *machine, uint32_t stride) { int pen_y = metrics.border_size_top + metrics.baseline; for (int i = 0; i < machine->nr_lines; i++) { int pen_x = metrics.border_size_left; for (int j = 0; j < machine->line_size; j++) { uint32_t bitmap_offset = machine->lines[i].cells[j].bitmap_offset; int top = bitmap_bytes[bitmap_offset + 0]; if (top > 127) top -= 256; int left = bitmap_bytes[bitmap_offset + 1]; if (left > 127) left -= 256; int rows = bitmap_bytes[bitmap_offset + 2]; int width = bitmap_bytes[bitmap_offset + 3]; draw_pixmap(mem, &bitmap_bytes[bitmap_offset + 4], rows, width, pen_x + left, pen_y - top, stride); pen_x += metrics.advance; } pen_y += metrics.line_spacing; } } static void draw_cursor(void *mem, uint32_t stride, struct cursor cursor) { uint32_t *pixels = mem; uint32_t stride_in_pixels = stride / BYTES_PER_PIXEL; int x_start = metrics.border_size_left + cursor.x * metrics.advance; int y_start = metrics.border_size_top + (cursor.y + 1) * metrics.line_spacing - 5; int x_end = clamp(x_start + metrics.advance, 0, stride_in_pixels); int y_end = y_start + 3; int guide_start = (y_end - 2) * stride_in_pixels; int guide_end = guide_start + stride_in_pixels; { uint32_t color = encode_color_xrgb(0xcc, 0xcc, 0xcc); for (int i = guide_start; i < guide_end; i++) pixels[i] = color; } { uint32_t color = encode_color_xrgb(0x48, 0x48, 0x48); for (int y = y_start; y < y_end; y++) for (int x = x_start; x < x_end; x++) pixels[y * stride_in_pixels + x] = color; } } static void draw_background(void *mem, uint32_t width, uint32_t height) { uint32_t color = encode_color_xrgb(0xff, 0xff, 0xff); uint32_t *pixel = mem; for (uint32_t y = 0; y < height; y++) for (uint32_t x = 0; x < width; x++) *pixel++ = color; } static void send_show(int fd); static void term_draw(struct term *term) { uint32_t stride; unsigned char *buffer; if (term->back_buffer_is_locked) { term->is_draw_needed = 1; return; } buffer = term->buffer + (term->back_buffer * BYTES_PER_PIXEL * term->width * term->height); draw_background(buffer, term->width, term->height); stride = BYTES_PER_PIXEL * term->width; draw_cursor(buffer, stride, term->machine.cursor); draw_text(buffer, &term->machine, stride); send_show(3); term->back_buffer = !term->back_buffer; term->back_buffer_is_locked = 1; term->is_draw_needed = 0; } static void init_metrics(void) { metrics.advance = 10; metrics.line_spacing = 24; metrics.baseline = 14; metrics.border_size_top = 10; metrics.border_size_bottom = 10; metrics.border_size_left = 5; metrics.border_size_right = 5; } static void term_launch(struct term *term, uint32_t width, uint32_t height, unsigned char *buffer) { term->epoll_fd = epoll_create1(EPOLL_CLOEXEC); if (term->epoll_fd == -1) die("Failed to initialize."); term->buffer = buffer; term->back_buffer = 1; term->back_buffer_is_locked = 0; term->is_draw_needed = 0; term->width = width; term->height = height; term->keyboard_modifiers = 0; // TODO Deal with underflow due to very small window. term->machine.line_size = (width - metrics.border_size_left - metrics.border_size_right) / metrics.advance; term->machine.nr_lines = (height - metrics.border_size_top - metrics.border_size_bottom) / metrics.line_spacing; term->machine.lines = malloc_or_die(term->machine.nr_lines * sizeof(struct line)); term->machine.func = parsing_state_default; term->machine.mode_flags = 0; term->machine.sp = 0; term->machine.cursor.x = 0; term->machine.cursor.y = 0; init_lines(&term->machine); init_pty(&term->pty); init_io_buffers(&term->pty); term->pty.events = EPOLLIN; term_io_add(term->pty.fd, term->pty.events); start_child( &term->pty, term->machine.nr_lines, term->machine.line_size); } static int pop(struct term_machine *machine) { if (machine->sp == 0) die("pop: stack underflow"); return machine->stack[--machine->sp]; } static void push(struct term_machine *machine, int a) { if (machine->sp == STACKSIZE) die("push: stack overflow"); machine->stack[machine->sp++] = a; } static uint32_t find_bitmap_offset(wchar_t wc) { { // Hack: Fira Mono doesn't have a glyph for 8208. Use 8211 (endash) // instead. if (wc == 8208) wc = 8211; } // Assumption: interval_starts is not empty. // Assumption: interval_starts is strictly increasing. // Assumption: interval_starts[0] == 0. // Assumption: intervals do not overlap. // Assumption: The null glyph has bitmap offset 0. int i = 0; while (i + 1 < array_size(interval_starts) && interval_starts[i + 1] <= wc) i++; // Invariant: i is the highest index such that interval_starts[i] <= wc. if (wc >= interval_starts[i] + interval_sizes[i]) { // Invariant: wc is not part of any interval. term_log("No glyph for code point %d.", (int)wc); const wchar_t replacement = 0x25c9; return (wc == replacement) ? 0 : find_bitmap_offset(replacement); } // Invariant: wc belongs to interval i. int j = wc - interval_starts[i]; while (--i >= 0) j += interval_sizes[i]; return bitmap_offsets[j]; } static struct cell make_cell(wchar_t wc) { return (struct cell){ .bitmap_offset = find_bitmap_offset(wc), }; } static void call_with_position( struct term_machine *machine, int x, int y, void (*func)(struct term_machine *machine)) { int sx = machine->cursor.x; int sy = machine->cursor.y; machine->cursor.x = x; machine->cursor.y = y; func(machine); machine->cursor.x = sx; machine->cursor.y = sy; } static void move_to(struct term_machine *machine) { int x, y; x = pop(machine); y = pop(machine); x = clamp(x, 0, machine->line_size); y = clamp(y, 0, machine->nr_lines - 1); machine->cursor.x = x; machine->cursor.y = y; } static void move_up(struct term_machine *machine) { if (machine->cursor.y == 0) return; --machine->cursor.y; } static void move_down(struct term_machine *machine) { if (machine->cursor.y + 1 == machine->nr_lines) return; ++machine->cursor.y; } static void move_back(struct term_machine *machine) { if (machine->cursor.x == 0) return; --machine->cursor.x; } static void move_forward(struct term_machine *machine) { if (machine->cursor.x >= machine->line_size) return; ++machine->cursor.x; } static void clear_line(struct term_machine *machine) { struct cell blank = make_cell(L' '); int line_size = machine->line_size; struct cell *cells = machine->lines[machine->cursor.y].cells; for (int x = machine->cursor.x; x < line_size; x++) cells[x] = blank; } static void shift_lines_up(struct term_machine *machine, int top) { struct line wrap_line = machine->lines[top]; memmove(&machine->lines[top], &machine->lines[top + 1], sizeof(struct line) * (machine->nr_lines - 1 - top)); machine->lines[machine->nr_lines - 1] = wrap_line; } static void shift_lines_down(struct term_machine *machine, int top) { struct line wrap_line = machine->lines[machine->nr_lines - 1]; memmove(&machine->lines[top + 1], &machine->lines[top], sizeof(struct line) * (machine->nr_lines - 1 - top)); machine->lines[top] = wrap_line; } static void scroll_up(struct term_machine *machine) { shift_lines_up(machine, 0); call_with_position(machine, 0, machine->nr_lines - 1, clear_line); } static void scroll_down(struct term_machine *machine) { shift_lines_down(machine, 0); call_with_position(machine, 0, 0, clear_line); } static void delete_line(struct term_machine *machine) { shift_lines_up(machine, machine->cursor.y); machine->cursor.x = 0; call_with_position(machine, 0, machine->nr_lines - 1, clear_line); } static void insert_blank_line(struct term_machine *machine) { shift_lines_down(machine, machine->cursor.y); machine->cursor.x = 0; clear_line(machine); } static void delete_char(struct term_machine *machine) { if (machine->cursor.x >= machine->line_size) return; int x = machine->cursor.x; int y = machine->cursor.y; for (; x < machine->line_size - 1; ++x) machine->lines[y].cells[x] = machine->lines[y].cells[x + 1]; machine->lines[y].cells[x] = make_cell(L' '); } static void home(struct term_machine *machine) { machine->cursor.x = 0; machine->cursor.y = 0; } static void clear_below(struct term_machine *machine) { int y; clear_line(machine); for (y = machine->cursor.y + 1; y < machine->nr_lines; ++y) call_with_position(machine, 0, y, clear_line); } static void auto_scroll(struct term_machine *machine) { if (machine->cursor.y == machine->nr_lines) { scroll_up(machine); --machine->cursor.y; } } static void auto_margin(struct term_machine *machine) { if (machine->cursor.x >= machine->line_size) { machine->cursor.x = 0; ++machine->cursor.y; auto_scroll(machine); } } static void set_graphics_mode(struct term_machine *machine) { int i; unsigned int mode_flags; mode_flags = machine->mode_flags; for (i = 0; i < machine->sp; i++) { switch (machine->stack[i]) { case 0: mode_flags = 0; break; case 1: mode_flags |= BOLD; break; case 4: mode_flags |= UNDERLINE; break; case 7: mode_flags |= REVERSE_VIDEO; break; case 10: mode_flags &= ~ALTERNATE_CHARACTER_SET; break; case 11: mode_flags |= ALTERNATE_CHARACTER_SET; break; case 24: mode_flags &= ~UNDERLINE; break; case 27: mode_flags &= ~REVERSE_VIDEO; break; case 30: mode_flags |= BOLD; break; case 31: mode_flags |= BOLD; break; case 32: mode_flags |= BOLD; break; case 33: mode_flags |= BOLD; break; case 34: mode_flags |= BOLD; break; case 35: mode_flags |= BOLD; break; case 36: mode_flags |= BOLD; break; case 37: mode_flags |= BOLD; break; case 40: mode_flags |= REVERSE_VIDEO; break; case 41: mode_flags |= REVERSE_VIDEO; break; case 42: mode_flags |= REVERSE_VIDEO; break; case 43: mode_flags |= REVERSE_VIDEO; break; case 44: mode_flags |= REVERSE_VIDEO; break; case 45: mode_flags |= REVERSE_VIDEO; break; case 46: mode_flags |= REVERSE_VIDEO; break; case 47: mode_flags |= REVERSE_VIDEO; break; } } machine->mode_flags = mode_flags; machine->sp = 0; } static void update(struct term_machine *machine, wchar_t c) { switch (c) { case L'\r': if (machine->cursor.x != 0) machine->cursor.x = 0; break; case L'\n': machine->cursor.x = 0; ++machine->cursor.y; auto_scroll(machine); break; case L'\t': auto_margin(machine); machine->cursor.x = clamp((machine->cursor.x + 8) & ~7, 0, machine->line_size); break; case L'\a': break; case L'\b': if (machine->cursor.x > 0) { machine->cursor.x--; delete_char(machine); } break; default: auto_margin(machine); machine->lines[machine->cursor.y].cells[machine->cursor.x++] = make_cell(c); break; } } struct command { char name; void (*func)(struct term_machine *machine); }; static struct command command_table[] = { { 'A', move_up }, { 'B', move_down }, { 'C', move_forward }, { 'D', move_back }, { 'G', move_to }, { 'H', home }, { 'J', clear_below }, { 'K', clear_line }, { 'L', insert_blank_line }, { 'M', delete_line }, { 'P', delete_char }, { 'R', scroll_up }, { 'S', scroll_down }, { 'm', set_graphics_mode }, }; static struct command * find_command(char name, struct command *command) { unsigned int i; for (i = 0; i < array_size(command_table); ++i) { if (name == command_table[i].name) { *command = command_table[i]; return command; } } return NULL; } static void apply_command(struct term_machine *machine, char name) { struct command command; if (find_command(name, &command) == NULL) die("apply_command: unknown command '%c'", name); command.func(machine); } struct parsing_state { int (*func)(struct parsing_state *, struct ring *); }; static int parsing_state_esc(struct term_machine *, struct ring *); static int parsing_state_default(struct term_machine *machine, struct ring *input) { unsigned char c, limit, mask; wchar_t wc; int nr_bytes = ring_size(input), nr_utf8_bytes; struct ring backup = *input; wc = c = ring_pop(input); if (c == '\033') { machine->func = parsing_state_esc; return 1; } if (c < 0x80) { update(machine, wc); return 1; } if (c >= 0xfe) goto use_replacement; nr_utf8_bytes = 6; mask = 0x01; for (limit = 0xfc; c < limit; limit <<= 1, mask = (mask << 1) | 1) if (--nr_utf8_bytes == 1) goto use_replacement; if (nr_bytes < nr_utf8_bytes) { *input = backup; return 0; } wc = c & mask; while (--nr_utf8_bytes) { c = ring_pop(input); if ((c & 0xc0) != 0x80) { //if (c < 0x80 || 0xc0 <= c) { *input = backup; (void)ring_pop(input); goto use_replacement; } wc = wc << 6 | (c & 0x3f); } update(machine, wc); return 1; use_replacement: update(machine, 0xfffd); return 1; } static int parsing_state_sep(struct term_machine *, struct ring *); static int parsing_state_num(struct term_machine *machine, struct ring *input) { char c; c = ring_pop(input); if (c == ';') machine->func = parsing_state_sep; else if (isdigit(c)) push(machine, pop(machine) * 10 + c - '0'); else { apply_command(machine, c); machine->func = parsing_state_default; } return 1; } static int parsing_state_sep(struct term_machine *machine, struct ring *input) { char c; c = ring_pop(input); if (isdigit(c)) { push(machine, c - '0'); machine->func = parsing_state_num; } else { apply_command(machine, c); machine->func = parsing_state_default; } return 1; } static int parsing_state_esc(struct term_machine *machine, struct ring *input) { char c; c = ring_pop(input); if (c != '[') die("parse: bad control sequence introducer '%c'", c); machine->func = parsing_state_sep; return 1; } static void init_lines(struct term_machine *machine) { int x, y; struct cell cell = make_cell(L' '); for (y = 0; y < machine->nr_lines; y++) { machine->lines[y].cells = malloc_or_die(machine->line_size * sizeof(struct cell)); } for (y = 0; y < machine->nr_lines; ++y) { for (x = 0; x < machine->line_size; ++x) machine->lines[y].cells[x] = cell; } } static void parse(struct term_machine *machine, struct ring *input) { while (machine->func(machine, input) && ring_size(input) > 0) ; } static void lose(struct ring *r, int len) { r->head = (r->head + len) % r->capacity; r->size -= len; } static int tail(struct ring *r) { return (r->head + r->size) % r->capacity; } void ring_init(struct ring *r, void *buf, int capacity) { r->buf = buf; r->capacity = capacity; r->head = 0; r->size = 0; } int ring_size(struct ring *r) { return r->size; } int ring_space(struct ring *r) { return r->capacity - r->size; } char ring_pop(struct ring *r) { char c; if (r->size == 0) die("ring_pop: empty"); c = r->buf[r->head]; lose(r, 1); return c; } void ring_push(struct ring *r, char c) { if (r->size == r->capacity) die("ring_push: full"); r->buf[tail(r)] = c; ++r->size; } void ring_read(struct ring *r, int fd, int *eof) { struct iovec vecs[2]; ssize_t len; char *fail_msg; *eof = 0; if (ring_space(r) == 0) return; if (tail(r) < r->head || r->head == 0) { if ((len = read(fd, r->buf + tail(r), ring_space(r))) == -1) { fail_msg = "read: %m"; goto fail; } } else { vecs[0].iov_base = r->buf + tail(r); vecs[0].iov_len = r->capacity - tail(r); vecs[1].iov_base = r->buf; vecs[1].iov_len = r->head; if ((len = readv(fd, vecs, 2)) == -1) { fail_msg = "readv: %m"; goto fail; } } *eof = (len == 0); r->size += len; return; fail: if (errno == EINTR || errno == EWOULDBLOCK) return; die(fail_msg); } void ring_write(struct ring *r, int fd) { struct iovec vecs[2]; ssize_t len; char *fail_msg; if (ring_size(r) == 0) return; if (r->head < tail(r) || tail(r) == 0) { if ((len = write(fd, r->buf + r->head, r->size)) == -1) { fail_msg = "write: %m"; goto fail; } } else { vecs[0].iov_base = r->buf + r->head; vecs[0].iov_len = r->capacity - r->head; vecs[1].iov_base = r->buf; vecs[1].iov_len = tail(r); if ((len = writev(fd, vecs, 2)) == -1) { fail_msg = "writev: %m"; goto fail; } } lose(r, len); return; fail: if (errno == EINTR || errno == EWOULDBLOCK) return; die(fail_msg); } #define MOD_CONTROL_MASK 1 #define MOD_ALT_MASK 2 #define MOD_SHIFT_MASK 4 static void init_locale(void) { const char *codeset; /* * TODO How to manage locale without setting LANG? Note: LANG seems to * be missing for clients launched by the weston shell even though it * is set in bash shells. */ if (setenv("LANG", "en_CA.UTF-8", 1) == -1) die("setenv: %m"); setlocale(LC_CTYPE, ""); codeset = nl_langinfo(CODESET); if (strcmp(codeset, "ANSI_X3.4-1968") && strcmp(codeset, "UTF-8")) die("character encoding is neither ASCII nor UTF8"); } static void init_child_process(struct term_pty *pty) { if (signal(SIGINT, SIG_DFL) == SIG_ERR) die("signal: %m"); if (signal(SIGQUIT, SIG_DFL) == SIG_ERR) die("signal: %m"); if (setsid() == -1) die("setsid: %m"); if (close(0) == -1) die("close: %m"); if (close(1) == -1) die("close: %m"); if (open(pty->slave_name, O_RDONLY) == -1) die("open: %m"); if (open(pty->slave_name, O_WRONLY) == -1) die("open: %m"); if (dup2(1, 2) == -1) die("dup2: %m"); if (putenv("TERM=ebt")) die("putenv: %m"); } static void init_pts_window_size(int nr_lines, int line_size) { struct winsize win_size = { .ws_row = nr_lines, .ws_col = line_size, }; if (ioctl(0, TIOCSWINSZ, &win_size) == -1) die("ioctl: %m"); } void start_child(struct term_pty *pty, int nr_lines, int line_size) { switch (fork()) { case -1: die("fork: %m"); case 0: if (close(pty->fd) == -1) die("close: %m"); init_child_process(pty); init_pts_window_size(nr_lines, line_size); if (execlp("bash", "bash", (char *)NULL) == -1) die("execlp: %m"); } } static void init_pty(struct term_pty *pty) { int flags; int fd; fd = posix_openpt(O_RDWR | O_NOCTTY); if (fd == -1) die("posix_openpt: %m"); if (grantpt(fd) == -1) die("grantpt: %m"); if (unlockpt(fd) == -1) die("unlockpt: %m"); flags = fcntl(fd, F_GETFL); if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) die("fcntl: %m"); pty->slave_name = ptsname(fd); if (pty->slave_name == NULL) die("ptsname: %m"); pty->slave_name = strdup(pty->slave_name); pty->fd = fd; } static void init_io_buffers(struct term_pty *pty) { ring_init(&pty->input, pty->input_buf, array_size(pty->input_buf)); ring_init(&pty->output, pty->output_buf, array_size(pty->output_buf)); } static void handle_ptm_input(struct term *term) { int eof; ring_read(&term->pty.input, term->pty.fd, &eof); if (eof) term_shutdown(term); if (ring_size(&term->pty.input) > 0) { parse(&term->machine, &term->pty.input); if (term != NULL) term_draw(term); } } static void write_ptm_output(struct term_pty *pty) { ring_write(&pty->output, pty->fd); } static int have_ptm_output_ready(struct term_pty *pty) { return (ring_size(&pty->output) > 0); } static void handle_ptm_events(struct term *term, uint32_t events) { struct term_pty *pty = &term->pty; if (events & EPOLLERR) die("error on terminal connection"); if (events & EPOLLIN) handle_ptm_input(term); if (events & EPOLLOUT) { write_ptm_output(pty); if (!have_ptm_output_ready(pty)) { pty->events &= ~EPOLLOUT; term_io_modify(pty->fd, pty->events); } } if (events & EPOLLHUP) term_shutdown(term); } static void term_on_input(struct term *term, uint16_t code, int32_t value) { { // TODO Handle the following weird case: left-shift is pressed, // right-shift is pressed, left-shift is released: the modifier_mask // should still indicate that shift is pressed. uint32_t modifier_mask = 0; switch (code) { case KEY_CAPSLOCK: modifier_mask = MOD_CONTROL_MASK; break; case KEY_LEFTALT: case KEY_RIGHTALT: modifier_mask = MOD_ALT_MASK; break; case KEY_LEFTSHIFT: case KEY_RIGHTSHIFT: modifier_mask = MOD_SHIFT_MASK; break; } if (modifier_mask != 0) { if (value == 0) term->keyboard_modifiers &= ~modifier_mask; else term->keyboard_modifiers |= modifier_mask; return; } } if (value == 0) return; char ascii = 0, arrow_sym = '?'; switch (code) { case KEY_UP: arrow_sym = 'A'; break; case KEY_DOWN: arrow_sym = 'B'; break; case KEY_RIGHT: arrow_sym = 'C'; break; case KEY_LEFT: arrow_sym = 'D'; break; } if (arrow_sym != '?') { ring_push(&term->pty.output, 27); ring_push(&term->pty.output, '['); ring_push(&term->pty.output, arrow_sym); } else if (code == KEY_BACKSPACE) { ascii = 127; ring_push(&term->pty.output, ascii); } else { if (term->keyboard_modifiers & MOD_SHIFT_MASK) ascii = keymap_lookup_shifted(code); else ascii = keymap_lookup(code); if (term->keyboard_modifiers & MOD_CONTROL_MASK) { // TODO Is there a specification for this behavior somewhere? switch (ascii) { case 51 ... 55: ascii -= 24; break; case 64 ... 95: ascii -= 64; break; case 97 ... 122: ascii -= 96; break; } } if (term->keyboard_modifiers & MOD_ALT_MASK) ring_push(&term->pty.output, 27); /* ESC */ ring_push(&term->pty.output, ascii); } if (term->pty.events & EPOLLOUT) return; if (!have_ptm_output_ready(&term->pty)) return; term->pty.events |= EPOLLOUT; term_io_modify(term->pty.fd, term->pty.events); } static void term_shutdown(struct term *term) { exit(0); } static void receive_welcome(int fd, struct shell_message_welcome *message) { // TODO This function is not consistent with the other "receive" // functions, which all take a (void *data) as first argument. Hmm. ssize_t r = read(fd, message, sizeof(*message)); if (r == -1) die("Failed to read welcome message."); if (message->class != SHELL_MESSAGE_WELCOME) die("Failed to receive welcome."); } static void receive_keyboard_event(void *data, struct term *term) { struct shell_message_keyboard_event *message = data; term_on_input(term, message->code, message->value); } static void receive_buffer_unlocked(void *data, struct term *term) { (void)data; on_buffer_unlocked(term); } static void receive_keyboard_detached(void *data) { (void)data; fprintf(stderr, "Keyboard detached.\n"); } static int fetch_bit(const uint8_t *bits, unsigned i) { return (bits[i / 8] & (1 << (i % 8))) ? 1 : 0; } static void receive_keyboard_attached(void *data, struct term *term) { struct shell_message_keyboard_attached *message = data; const uint8_t *key_bits = message->key_bits; if (fetch_bit(key_bits, KEY_CAPSLOCK)) term->keyboard_modifiers |= MOD_CONTROL_MASK; else term->keyboard_modifiers &= ~MOD_CONTROL_MASK; if (fetch_bit(key_bits, KEY_LEFTALT) || fetch_bit(key_bits, KEY_RIGHTALT)) term->keyboard_modifiers |= MOD_ALT_MASK; else term->keyboard_modifiers &= ~MOD_ALT_MASK; if (fetch_bit(key_bits, KEY_LEFTSHIFT) || fetch_bit(key_bits, KEY_RIGHTSHIFT)) term->keyboard_modifiers |= MOD_SHIFT_MASK; else term->keyboard_modifiers &= ~MOD_SHIFT_MASK; fprintf(stderr, "Keyboard attached.\n"); } static void send_show(int fd) { struct shell_message_show message = { .class = SHELL_MESSAGE_SHOW, }; ssize_t r = write(fd, &message, sizeof(message)); if (r != sizeof(message)) die("Failed to write show message."); } static void handle_shell_message(int fd, struct term *term, uint32_t events) { if (events & EPOLLERR) die("Failed while reading from shell."); if (events & EPOLLHUP) exit(0); if (events & EPOLLIN) { static _Alignas(max_align_t) char packet[SHELL_PACKET_SIZE_MAX]; int r; do { r = read(fd, packet, sizeof(packet)); } while (r == -1 && errno == EINTR); if (r <= 0) die("Failed to read from shell socket."); uint16_t class = *(uint16_t *)packet; switch (class) { case SHELL_MESSAGE_KEYBOARD_EVENT: receive_keyboard_event(packet, term); break; case SHELL_MESSAGE_BUFFER_UNLOCKED: receive_buffer_unlocked(packet, term); break; case SHELL_MESSAGE_KEYBOARD_DETACHED: receive_keyboard_detached(packet); break; case SHELL_MESSAGE_KEYBOARD_ATTACHED: receive_keyboard_attached(packet, term); break; } } } static struct term term; static void term_parse_command_line(int argc, const char *argv[]) { term.log_file = NULL; const char usage[] = "usage: term [--log <file>]"; int i = 1; while (i < argc) { if (strcmp(argv[i], "--log") == 0) { i++; if (i >= argc) die(usage); FILE *file = fopen(argv[i], "a"); if (file == NULL) die("Failed to open log file."); term.log_file = file; i++; } else { die(usage); } } } static void term_init(int argc, const char *argv[]) { term_parse_command_line(argc, argv); init_locale(); init_metrics(); uint32_t width, height; { struct shell_message_welcome message; receive_welcome(3, &message); width = message.width; height = message.height; } enable_cloexec(3); size_t mem_size = 8 * width * height; void *mem = mmap(NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, 4, 0); if (mem == MAP_FAILED) die("Failed to create draw buffers for child process."); close(4); term_launch(&term, width, height, mem); term_io_add(3, EPOLLIN); } static void term_interact(void) { struct epoll_event events[2]; int num_events; { int r; do { r = epoll_wait(term.epoll_fd, events, array_size(events), -1); } while (r == -1 && errno == EINTR); if (r == -1) die("epoll_wait: %m"); num_events = r; } for (int i = 0; i < num_events; i++) { int fd = events[i].data.fd; if (fd == 3) handle_shell_message(3, &term, events[i].events); else handle_ptm_events(&term, events[i].events); } } static void term_io_add(int fd, uint32_t events) { struct epoll_event event = { .data = { .fd = fd }, .events = events, }; int r = epoll_ctl(term.epoll_fd, EPOLL_CTL_ADD, fd, &event); if (r == -1) die("epoll_ctl: %m"); } static void term_io_modify(int fd, uint32_t events) { struct epoll_event event = { .data = { .fd = fd }, .events = events, }; int r = epoll_ctl(term.epoll_fd, EPOLL_CTL_MOD, fd, &event); if (r == -1) die("epoll_ctl: %m"); } static void term_log(const char *fmt, ...) { if (term.log_file == NULL) return; va_list args; va_start(args, fmt); vfprintf(term.log_file, fmt, args); va_end(args); fputc('\n', term.log_file); fflush(term.log_file); }