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