System 59

File

system59-0.1/term.c

#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);
}