From 71dde121e63177b0fc1bc2d6897e63e533d666fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 19 Jun 2019 10:04:47 +0200 Subject: [PATCH] wip: initial input handling --- csi.c | 101 +++++++++++++++++++++++++----- grid.c | 48 +++++---------- grid.h | 2 + input.c | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++ input.h | 9 +++ main.c | 158 ++++++++++++++++++++++++++++++++++++++++++++++- meson.build | 4 +- terminal.h | 26 ++++++++ vt.c | 14 ++++- 9 files changed, 484 insertions(+), 51 deletions(-) create mode 100644 input.c create mode 100644 input.h diff --git a/csi.c b/csi.c index ed223f55..b6f51ffb 100644 --- a/csi.c +++ b/csi.c @@ -107,51 +107,124 @@ csi_dispatch(struct terminal *term, uint8_t final) if (term->vt.intermediates.idx == 0) { switch (final) { case 'c': - write(term->ptmx, "\033[?6c", 5); - return true; + return write(term->ptmx, "\033[?6c", 5) == 5; case 'm': return csi_sgr(term); case 'J': { - assert(term->vt.params.idx == 0); - int start = grid_cursor_linear(&term->grid, term->grid.cursor.row, 0); - int end = term->grid.cols * term->grid.rows; + /* Erase screen */ + + int param = 0; + if (term->vt.params.idx >= 1) + param = term->vt.params.v[0].value; + + int start = -1; + int end = -1; + switch (param) { + case 0: + /* From cursor to end of screen */ + start = term->grid.linear_cursor; + end = term->grid.cols * term->grid.rows; + break; + + case 1: + /* From start of screen to cursor */ + start = 0; + end = term->grid.linear_cursor; + break; + + case 2: + /* Erase entire screen */ + start = 0; + end = term->grid.cols * term->grid.rows; + break; + + default: + LOG_ERR("CSI: J: invalid argument: %d", param); + return false; + } + grid_erase(&term->grid, start, end); - return true; + break; } case 'K': { - assert(term->vt.params.idx == 0); - int start = term->grid.linear_cursor; - int end = grid_cursor_linear( - &term->grid, term->grid.cursor.row, term->grid.cols - 1); - LOG_DBG("K: %d -> %d", start, end); + /* Erase line */ + + int param = 0; + if (term->vt.params.idx >= 0) + param = term->vt.params.v[0].value; + + int start = -1; + int end = -1; + switch (param) { + case 0: + /* From cursor to end of line */ + start = term->grid.linear_cursor; + end = grid_cursor_linear( + &term->grid, term->grid.cursor.row, term->grid.cols - 1); + break; + + case 1: + /* From start of line to cursor */ + start = grid_cursor_linear( + &term->grid, term->grid.cursor.row, 0); + end = term->grid.linear_cursor; + break; + + case 2: + /* Entire line */ + start = grid_cursor_linear( + &term->grid, term->grid.cursor.row, 0); + end = grid_cursor_linear( + &term->grid, term->grid.cursor.row, term->grid.cols - 1); + break; + + default: + LOG_ERR("CSI: K: invalid argument: %d", param); + return false; + } grid_erase(&term->grid, start, end); - return true; + break; + } + + case 'A': { + int count = term->vt.params.idx > 0 ? term->vt.params.v[0].value : 1; + grid_cursor_up(&term->grid, count); + break; + } + + case 'B': { + int count = term->vt.params.idx > 0 ? term->vt.params.v[0].value : 1; + grid_cursor_up(&term->grid, count); + break; } case 'C': { int count = term->vt.params.idx > 0 ? term->vt.params.v[0].value : 1; grid_cursor_right(&term->grid, count); - return true; + break; } case 'D': { int count = term->vt.params.idx > 0 ? term->vt.params.v[0].value : 1; grid_cursor_left(&term->grid, count); - return true; + break; } default: LOG_ERR("CSI: unimplemented final: %c", final); abort(); } + + return true; } else { LOG_ERR("CSI: unimplemented: intermediates: %.*s", (int)term->vt.intermediates.idx, term->vt.intermediates.data); + //abort(); return true; } diff --git a/grid.c b/grid.c index 02ec24a5..d2287af5 100644 --- a/grid.c +++ b/grid.c @@ -53,42 +53,26 @@ void grid_cursor_left(struct grid *grid, int count) { int move_amount = min(grid->cursor.col, count); - int new_linear = grid->linear_cursor - move_amount; - int new_col = grid->cursor.col - move_amount; - - assert(new_linear >= 0); - assert(new_linear < grid->rows * grid->cols); - assert(new_col >= 0); - assert(new_col < grid->cols); - - grid->cells[grid->linear_cursor].dirty = true; - grid->cells[new_linear].dirty = true; - - grid->linear_cursor = new_linear; - grid->cursor.col = new_col; - - grid->dirty = true; - grid->print_needs_wrap = false; + grid_cursor_to(grid, grid->cursor.row, grid->cursor.col - move_amount); } void grid_cursor_right(struct grid *grid, int count) { int move_amount = min(grid->cols - grid->cursor.col - 1, count); - int new_linear = grid->linear_cursor + move_amount; - int new_col = grid->cursor.col + move_amount; - - assert(new_linear >= 0); - assert(new_linear < grid->rows * grid->cols); - assert(new_col >= 0); - assert(new_col < grid->cols); - - grid->cells[grid->linear_cursor].dirty = true; - grid->cells[new_linear].dirty = true; - - grid->linear_cursor = new_linear; - grid->cursor.col = new_col; - - grid->dirty = true; - grid->print_needs_wrap = false; + grid_cursor_to(grid, grid->cursor.row, grid->cursor.col + move_amount); +} + +void +grid_cursor_up(struct grid *grid, int count) +{ + int move_amount = min(grid->cursor.row, count); + grid_cursor_to(grid, grid->cursor.row - move_amount, grid->cursor.col); +} + +void +grid_cursor_down(struct grid *grid, int count) +{ + int move_amount = min(grid->rows - grid->cursor.row - 1, count); + grid_cursor_to(grid, grid->cursor.row + move_amount, grid->cursor.col); } diff --git a/grid.h b/grid.h index 032708ad..3b806296 100644 --- a/grid.h +++ b/grid.h @@ -7,6 +7,8 @@ void grid_erase(struct grid *grid, int start, int end); void grid_cursor_to(struct grid *grid, int row, int col); void grid_cursor_left(struct grid *grid, int count); void grid_cursor_right(struct grid *grid, int count); +void grid_cursor_up(struct grid *grid, int count); +void grid_cursor_down(struct grid *grid, int count); int grid_cursor_linear(const struct grid *grid, int row, int col); diff --git a/input.c b/input.c new file mode 100644 index 00000000..a66e45bd --- /dev/null +++ b/input.c @@ -0,0 +1,173 @@ +#include "input.h" + +#include +#include +#include +#include +#include + +#include +#include + +#define LOG_MODULE "input" +#define LOG_ENABLE_DBG 1 +#include "log.h" +#include "terminal.h" + +static void +keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int32_t fd, uint32_t size) +{ + struct terminal *term = data; + + char *map_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + + /* TODO: free old context + keymap */ + + term->kbd.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + term->kbd.xkb_keymap = xkb_keymap_new_from_string( + term->kbd.xkb, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); + + /* TODO: initialize in enter? */ + term->kbd.xkb_state = xkb_state_new(term->kbd.xkb_keymap); + + munmap(map_str, size); + close(fd); +} + +static void +keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, + struct wl_surface *surface, struct wl_array *keys) +{ + LOG_DBG("enter"); +#if 0 + uint32_t *key; + wl_array_for_each(key, keys) + xkb_state_update_key(xkb_state, *key, 1); +#endif +} + +static void +keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, + struct wl_surface *surface) +{ +#if 0 + struct terminal *term = data; + term->status = EXIT; +#endif +} + +static void +keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, + uint32_t time, uint32_t key, uint32_t state) +{ + static bool mod_masks_initialized = false; + static xkb_mod_mask_t ctrl = -1; + static xkb_mod_mask_t alt = -1; + //static xkb_mod_mask_t shift = -1; + + struct terminal *term = data; + + if (!mod_masks_initialized) { + mod_masks_initialized = true; + ctrl = 1 << xkb_keymap_mod_get_index(term->kbd.xkb_keymap, "Control"); + alt = 1 << xkb_keymap_mod_get_index(term->kbd.xkb_keymap, "Mod1"); + //shift = 1 << xkb_keymap_mod_get_index(term->kbd.xkb_keymap, "Shift"); + } + + if (state == XKB_KEY_UP) { + mtx_lock(&term->kbd.repeat.mutex); + if (term->kbd.repeat.key == key) { + if (term->kbd.repeat.cmd != REPEAT_EXIT) { + term->kbd.repeat.cmd = REPEAT_STOP; + cnd_signal(&term->kbd.repeat.cond); + } + } + mtx_unlock(&term->kbd.repeat.mutex); + return; + } + + key += 8; + xkb_keysym_t sym = xkb_state_key_get_one_sym(term->kbd.xkb_state, key); + + xkb_mod_mask_t mods = xkb_state_serialize_mods( + term->kbd.xkb_state, XKB_STATE_MODS_EFFECTIVE); + xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods(term->kbd.xkb_state, key); + xkb_mod_mask_t significant = ctrl | alt /*| shift*/; + xkb_mod_mask_t effective_mods = mods & ~consumed & significant; + +#if 0 + for (size_t i = 0; i < 32; i++) { + if (mods & (1 << i)) { + LOG_DBG("%s", xkb_keymap_mod_get_name(term->kbd.xkb_keymap, i)); + } + } +#endif + + LOG_DBG("sym=%u, mod=0x%08x, consumed=0x%08x, significant=0x%08x, " + "effective=0x%08x", + sym, mods, consumed, significant, effective_mods); + + if (sym == XKB_KEY_c && effective_mods == ctrl) { + kill(term->slave, SIGINT); + + } else if (effective_mods == 0) { + char buf[128] = {0}; + int count = xkb_state_key_get_utf8(term->kbd.xkb_state, key, buf, sizeof(buf)); + + if (count == 0) + return; + + write(term->ptmx, buf, count); + } + + mtx_lock(&term->kbd.repeat.mutex); + if (!term->kbd.repeat.dont_re_repeat) { + if (term->kbd.repeat.cmd != REPEAT_EXIT) { + term->kbd.repeat.cmd = REPEAT_START; + term->kbd.repeat.key = key - 8; + cnd_signal(&term->kbd.repeat.cond); + } + } + mtx_unlock(&term->kbd.repeat.mutex); +} + +static void +keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, + uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) +{ + struct terminal *term = data; + + LOG_DBG("modifiers: depressed=0x%x, latched=0x%x, locked=0x%x, group=%u", + mods_depressed, mods_latched, mods_locked, group); + + xkb_state_update_mask( + term->kbd.xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); +} + +static void +keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, + int32_t rate, int32_t delay) +{ + struct terminal *term = data; + LOG_DBG("keyboard repeat: rate=%d, delay=%d", rate, delay); + term->kbd.repeat.rate = rate; + term->kbd.repeat.delay = delay; +} + +const struct wl_keyboard_listener keyboard_listener = { + .keymap = &keyboard_keymap, + .enter = &keyboard_enter, + .leave = &keyboard_leave, + .key = &keyboard_key, + .modifiers = &keyboard_modifiers, + .repeat_info = &keyboard_repeat_info, +}; + +void +input_repeat(struct terminal *term, uint32_t key) +{ + keyboard_key(term, NULL, 0, 0, key, XKB_KEY_DOWN); +} diff --git a/input.h b/input.h new file mode 100644 index 00000000..a191ae36 --- /dev/null +++ b/input.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include "terminal.h" + +extern const struct wl_keyboard_listener keyboard_listener; + +void input_repeat(struct terminal *term, uint32_t key); diff --git a/main.c b/main.c index f11d6ee3..d162e3b7 100644 --- a/main.c +++ b/main.c @@ -23,6 +23,7 @@ #include "slave.h" #include "terminal.h" #include "vt.h" +#include "input.h" static const uint32_t default_foreground = 0xffffffff; static const uint32_t default_background = 0x000000ff; @@ -33,6 +34,8 @@ struct wayland { struct wl_compositor *compositor; struct wl_surface *surface; struct wl_shm *shm; + struct wl_seat *seat; + struct wl_keyboard *keyboard; struct xdg_wm_base *shell; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; @@ -248,6 +251,32 @@ static const struct xdg_wm_base_listener xdg_wm_base_listener = { .ping = &xdg_wm_base_ping, }; +static void +seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + enum wl_seat_capability caps) +{ + struct context *c = data; + + if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD)) + return; + + if (c->wl.keyboard != NULL) + wl_keyboard_release(c->wl.keyboard); + + c->wl.keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(c->wl.keyboard, &keyboard_listener, &c->term); +} + +static void +seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name) +{ +} + +static const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, + .name = seat_handle_name, +}; + static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) @@ -272,6 +301,12 @@ handle_global(void *data, struct wl_registry *registry, xdg_wm_base_add_listener(c->wl.shell, &xdg_wm_base_listener, c); } + else if (strcmp(interface, wl_seat_interface.name) == 0) { + c->wl.seat = wl_registry_bind(c->wl.registry, name, &wl_seat_interface, 4); + wl_seat_add_listener(c->wl.seat, &seat_listener, c); + wl_display_roundtrip(c->wl.display); + } + #if 0 else if (strcmp(interface, wl_output_interface.name) == 0) { struct wl_output *output = wl_registry_bind( @@ -370,6 +405,77 @@ static const struct wl_registry_listener registry_listener = { .global_remove = &handle_global_remove, }; +static int +keyboard_repeater(void *arg) +{ + struct terminal *term = arg; + + while (true) { + LOG_DBG("repeater: waiting for start"); + + mtx_lock(&term->kbd.repeat.mutex); + while (term->kbd.repeat.cmd == REPEAT_STOP) + cnd_wait(&term->kbd.repeat.cond, &term->kbd.repeat.mutex); + + if (term->kbd.repeat.cmd == REPEAT_EXIT) { + mtx_unlock(&term->kbd.repeat.mutex); + return 0; + } + + restart: + + LOG_DBG("repeater: started"); + assert(term->kbd.repeat.cmd == REPEAT_START); + + const long rate_delay = 1000000000 / term->kbd.repeat.rate; + long delay = term->kbd.repeat.delay * 1000000; + + while (true) { + assert(term->kbd.repeat.rate > 0); + + struct timespec timeout; + clock_gettime(CLOCK_REALTIME, &timeout); + + timeout.tv_nsec += delay; + if (timeout.tv_nsec >= 1000000000) { + timeout.tv_sec += timeout.tv_nsec / 1000000000; + timeout.tv_nsec %= 1000000000; + } + + int ret = cnd_timedwait(&term->kbd.repeat.cond, &term->kbd.repeat.mutex, &timeout); + if (ret == thrd_success) { + if (term->kbd.repeat.cmd == REPEAT_START) + goto restart; + else if (term->kbd.repeat.cmd == REPEAT_STOP) { + mtx_unlock(&term->kbd.repeat.mutex); + break; + } else if (term->kbd.repeat.cmd == REPEAT_EXIT) { + mtx_unlock(&term->kbd.repeat.mutex); + return 0; + } + } + + assert(ret == thrd_timedout); + assert(term->kbd.repeat.cmd == REPEAT_START); + LOG_DBG("repeater: repeat: %u", term->kbd.repeat.key); + + if (write(term->kbd.repeat.pipe_write_fd, &term->kbd.repeat.key, + sizeof(term->kbd.repeat.key)) != sizeof(term->kbd.repeat.key)) + { + LOG_ERRNO("faile to write repeat key to repeat pipe"); + mtx_unlock(&term->kbd.repeat.mutex); + return 0; + } + + delay = rate_delay; + } + + } + + assert(false); + return 1; +} + int main(int argc, const char *const *argv) { @@ -377,6 +483,12 @@ main(int argc, const char *const *argv) setlocale(LC_ALL, ""); + int repeat_pipe_fds[2] = {-1, -1}; + if (pipe2(repeat_pipe_fds, O_CLOEXEC) == -1) { + LOG_ERRNO("failed to create pipe for repeater thread"); + return ret; + } + struct context c = { .quit = false, .term = { @@ -386,9 +498,22 @@ main(int argc, const char *const *argv) }, .grid = {.foreground = default_foreground, .background = default_background}, + .kbd = { + .repeat = { + .pipe_read_fd = repeat_pipe_fds[0], + .pipe_write_fd = repeat_pipe_fds[1], + .cmd = REPEAT_STOP, + }, + }, }, }; + mtx_init(&c.term.kbd.repeat.mutex, mtx_plain); + cnd_init(&c.term.kbd.repeat.cond); + + thrd_t keyboard_repeater_id; + thrd_create(&keyboard_repeater_id, &keyboard_repeater, &c.term); + const char *font_name = "Dina:pixelsize=12"; c.font = font_from_name(font_name); if (c.font == NULL) @@ -459,8 +584,8 @@ main(int argc, const char *const *argv) wl_display_dispatch_pending(c.wl.display); - pid_t pid = fork(); - switch (pid) { + c.term.slave = fork(); + switch (c.term.slave) { case -1: LOG_ERRNO("failed to fork"); goto out; @@ -472,7 +597,7 @@ main(int argc, const char *const *argv) break; default: - LOG_DBG("slave has PID %d", pid); + LOG_DBG("slave has PID %d", c.term.slave); break; } @@ -480,6 +605,7 @@ main(int argc, const char *const *argv) struct pollfd fds[] = { {.fd = wl_display_get_fd(c.wl.display), .events = POLLIN}, {.fd = c.term.ptmx, .events = POLLIN}, + {.fd = c.term.kbd.repeat.pipe_read_fd, .events = POLLIN}, }; wl_display_flush(c.wl.display); @@ -517,9 +643,29 @@ main(int argc, const char *const *argv) ret = EXIT_SUCCESS; break; } + + if (fds[2].revents & POLLIN) { + uint32_t key; + if (read(c.term.kbd.repeat.pipe_read_fd, &key, sizeof(key)) != sizeof(key)) { + LOG_ERRNO("failed to read repeat key from repeat pipe"); + break; + } + + c.term.kbd.repeat.dont_re_repeat = true; + input_repeat(&c.term, key); + c.term.kbd.repeat.dont_re_repeat = false; + } + + if (fds[2].revents & POLLHUP) + LOG_ERR("keyboard repeat handling thread died"); } out: + mtx_lock(&c.term.kbd.repeat.mutex); + c.term.kbd.repeat.cmd = REPEAT_EXIT; + cnd_signal(&c.term.kbd.repeat.cond); + mtx_unlock(&c.term.kbd.repeat.mutex); + shm_fini(); if (c.wl.xdg_toplevel != NULL) xdg_toplevel_destroy(c.wl.xdg_toplevel); @@ -546,6 +692,12 @@ out: if (c.term.ptmx != -1) close(c.term.ptmx); + thrd_join(keyboard_repeater_id, NULL); + cnd_destroy(&c.term.kbd.repeat.cond); + mtx_destroy(&c.term.kbd.repeat.mutex); + close(c.term.kbd.repeat.pipe_read_fd); + close(c.term.kbd.repeat.pipe_write_fd); + cairo_debug_reset_static_data(); return ret; } diff --git a/meson.build b/meson.build index 249c5f43..05b05a10 100644 --- a/meson.build +++ b/meson.build @@ -20,6 +20,7 @@ add_project_arguments( cc = meson.get_compiler('c') math = cc.find_library('m') +threads = dependency('threads') fontconfig = dependency('fontconfig') cairo = dependency('cairo') cairo_ft = dependency('cairo-ft') @@ -60,6 +61,7 @@ executable( 'csi.c', 'csi.h', 'font.c', 'font.h', 'grid.c', 'grid.h', + 'input.c', 'input.h', 'log.c', 'log.h', 'main.c', 'osc.c', 'osc.h', @@ -69,5 +71,5 @@ executable( 'tllist.h', 'vt.c', 'vt.h', wl_proto_src + wl_proto_headers, - dependencies: [math, cairo, cairo_ft, fontconfig, wayland_client, wayland_cursor, xkb], + dependencies: [threads, math, cairo, cairo_ft, fontconfig, wayland_client, wayland_cursor, xkb], install: true) diff --git a/terminal.h b/terminal.h index efea5106..51c5af3d 100644 --- a/terminal.h +++ b/terminal.h @@ -4,6 +4,11 @@ #include #include +#include + +#include +#include + struct attributes { bool bold; bool italic; @@ -73,8 +78,29 @@ struct vt { bool dim; }; +struct kbd { + struct xkb_context *xkb; + struct xkb_keymap *xkb_keymap; + struct xkb_state *xkb_state; + struct { + mtx_t mutex; + cnd_t cond; + int trigger; + int pipe_read_fd; + int pipe_write_fd; + enum {REPEAT_STOP, REPEAT_START, REPEAT_EXIT} cmd; + + bool dont_re_repeat; + int32_t delay; + int32_t rate; + uint32_t key; + } repeat; +}; + struct terminal { + pid_t slave; int ptmx; struct vt vt; struct grid grid; + struct kbd kbd; }; diff --git a/vt.c b/vt.c index 9c136828..751eece3 100644 --- a/vt.c +++ b/vt.c @@ -157,6 +157,10 @@ action(struct terminal *term, enum action action, uint8_t c) case ACTION_EXECUTE: LOG_DBG("execute: 0x%02x", c); switch (c) { + case '\n': + grid_cursor_down(&term->grid, 1); + break; + case '\r': grid_cursor_left(&term->grid, term->grid.cursor.col); break; @@ -164,6 +168,14 @@ action(struct terminal *term, enum action action, uint8_t c) case '\b': grid_cursor_left(&term->grid, 1); break; + + case '\x07': + LOG_WARN("BELL"); + break; + + default: + LOG_ERR("execute: unimplemented: %c", c); + return false; } return true; @@ -189,7 +201,7 @@ action(struct terminal *term, enum action action, uint8_t c) cell->c[term->vt.utf8.idx] = '\0'; memset(&term->vt.utf8, 0, sizeof(term->vt.utf8)); } else { - //LOG_DBG("print: ASCII: %c", c); + LOG_DBG("print: ASCII: %c", c); cell->c[0] = c; cell->c[1] = '\0'; }