foot/input.c
Daniel Eklöf bcd111d203
wip: initial scroll back support
Can scroll up and down, and stops when the beginning/end of history is
reached.

However, it probably breaks when the entire scrollback buffer has been
filled - we need to detect when the view has wrapped around to the
current terminal offset.

The detection of when we've reached the bottom of the history is also
flawed, and only works when we overshoot the bottom with at least a
page.

Resizing the windows while in a view most likely doesn't work.

The view will not detect a wrapped around scrollback buffer. I.e. if
the user has scrolled back, and is stationary at a view, but there is
still output being produced. Then eventually the scrollback buffer
will wrap around. In this case, the correct thing to do is make the
view start following the beginning of the history. Right now it
doesn't, meaning once the scrollback buffer wraps around, you'll start
seeing command output...
2019-07-09 16:26:36 +02:00

380 lines
11 KiB
C

#include "input.h"
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <threads.h>
#include <locale.h>
#include <sys/mman.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-keysyms.h>
#include <xkbcommon/xkbcommon-compose.h>
#define LOG_MODULE "input"
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "terminal.h"
#include "render.h"
#include "keymap.h"
#include "commands.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);
term->kbd.mod_shift = xkb_keymap_mod_get_index(term->kbd.xkb_keymap, "Shift");
term->kbd.mod_alt = xkb_keymap_mod_get_index(term->kbd.xkb_keymap, "Mod1") ;
term->kbd.mod_ctrl = xkb_keymap_mod_get_index(term->kbd.xkb_keymap, "Control");
/* Compose (dead keys) */
term->kbd.xkb_compose_table = xkb_compose_table_new_from_locale(
term->kbd.xkb, setlocale(LC_CTYPE, NULL), XKB_COMPOSE_COMPILE_NO_FLAGS);
term->kbd.xkb_compose_state = xkb_compose_state_new(
term->kbd.xkb_compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
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)
{
struct terminal *term = data;
mtx_lock(&term->kbd.repeat.mutex);
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);
}
static void
keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
uint32_t time, uint32_t key, uint32_t state)
{
struct terminal *term = data;
const xkb_mod_mask_t ctrl = 1 << term->kbd.mod_ctrl;
const xkb_mod_mask_t alt = 1 << term->kbd.mod_alt;
const xkb_mod_mask_t shift = 1 << term->kbd.mod_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);
#if 0
char foo[100];
xkb_keysym_get_name(sym, foo, sizeof(foo));
LOG_ERR("%s", foo);
#endif
xkb_compose_state_feed(term->kbd.xkb_compose_state, sym);
enum xkb_compose_status compose_status = xkb_compose_state_get_status(
term->kbd.xkb_compose_state);
if (compose_status == XKB_COMPOSE_COMPOSING)
return;
xkb_mod_mask_t mods = xkb_state_serialize_mods(
term->kbd.xkb_state, XKB_STATE_MODS_DEPRESSED);
//xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods(term->kbd.xkb_state, key);
xkb_mod_mask_t consumed = 0x0;
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);
bool found_map = false;
enum modifier keymap_mods = MOD_NONE;
keymap_mods |= term->kbd.shift ? MOD_SHIFT : MOD_NONE;
keymap_mods |= term->kbd.alt ? MOD_ALT : MOD_NONE;
keymap_mods |= term->kbd.ctrl ? MOD_CTRL : MOD_NONE;
if (effective_mods == shift) {
if (sym == XKB_KEY_Page_Up) {
cmd_scrollback_up(term);
found_map = true;
}
else if (sym == XKB_KEY_Page_Down) {
cmd_scrollback_down(term);
found_map = true;
}
}
for (size_t i = 0; i < sizeof(key_map) / sizeof(key_map[0]) && !found_map; i++) {
const struct key_map *k = &key_map[i];
if (k->sym != sym)
continue;
for (size_t j = 0; j < k->count; j++) {
const struct key_data *info = &k->data[j];
if (info->modifiers != MOD_ANY && info->modifiers != keymap_mods)
continue;
if (info->cursor_keys_mode != CURSOR_KEYS_DONTCARE &&
info->cursor_keys_mode != term->cursor_keys_mode)
continue;
if (info->keypad_keys_mode != KEYPAD_DONTCARE &&
info->keypad_keys_mode != term->keypad_keys_mode)
continue;
write(term->ptmx, info->seq, strlen(info->seq));
found_map = true;
if (term->grid->view != term->grid->offset) {
term->grid->view = term->grid->offset;
/* TODO: damage view */
term_damage_all(term);
}
break;
}
}
if (!found_map) {
char buf[64] = {0};
int count = 0;
if (compose_status == XKB_COMPOSE_COMPOSED) {
count = xkb_compose_state_get_utf8(
term->kbd.xkb_compose_state, buf, sizeof(buf));
xkb_compose_state_reset(term->kbd.xkb_compose_state);
} else {
count = xkb_state_key_get_utf8(
term->kbd.xkb_state, key, buf, sizeof(buf));
}
if (count > 0) {
if (effective_mods & alt)
write(term->ptmx, "\x1b", 1);
write(term->ptmx, buf, count);
if (term->grid->view != term->grid->offset) {
term->grid->view = term->grid->offset;
/* TODO: damage view */
term_damage_all(term);
}
}
}
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);
/* Update state of modifiers we're interrested in for e.g mouse events */
term->kbd.shift = xkb_state_mod_index_is_active(
term->kbd.xkb_state, term->kbd.mod_shift, XKB_STATE_MODS_DEPRESSED);
term->kbd.alt = xkb_state_mod_index_is_active(
term->kbd.xkb_state, term->kbd.mod_alt, XKB_STATE_MODS_DEPRESSED);
term->kbd.ctrl = xkb_state_mod_index_is_active(
term->kbd.xkb_state, term->kbd.mod_ctrl, XKB_STATE_MODS_DEPRESSED);
}
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);
}
static void
wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, struct wl_surface *surface,
wl_fixed_t surface_x, wl_fixed_t surface_y)
{
struct terminal *term = data;
int x = wl_fixed_to_int(surface_x) * 1; //scale
int y = wl_fixed_to_int(surface_y) * 1; //scale
term->mouse.col = x / term->cell_width;
term->mouse.row = y / term->cell_height;
render_update_cursor_surface(term);
}
static void
wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, struct wl_surface *surface)
{
}
static void
wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y)
{
struct terminal *term = data;
int x = wl_fixed_to_int(surface_x) * 1;//backend->monitor->scale;
int y = wl_fixed_to_int(surface_y) * 1;//backend->monitor->scale;
int col = x / term->cell_width;
int row = y / term->cell_height;
if (col == term->mouse.col && row == term->mouse.row)
return;
term->mouse.col = col;
term->mouse.row = row;
term_mouse_motion(
term, term->mouse.button, term->mouse.row, term->mouse.col,
term->kbd.shift, term->kbd.alt, term->kbd.ctrl);
}
static void
wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
{
LOG_DBG("BUTTON: button=%x, state=%u", button, state);
struct terminal *term = data;
switch (state) {
case WL_POINTER_BUTTON_STATE_PRESSED:
term->mouse.button = button; /* For motion events */
term_mouse_down(term, button, term->mouse.row, term->mouse.col,
term->kbd.shift, term->kbd.alt, term->kbd.ctrl);
break;
case WL_POINTER_BUTTON_STATE_RELEASED:
term->mouse.button = 0; /* For motion events */
term_mouse_up(term, button, term->mouse.row, term->mouse.col,
term->kbd.shift, term->kbd.alt, term->kbd.ctrl);
break;
}
}
static void
wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
uint32_t time, uint32_t axis, wl_fixed_t value)
{
}
static void
wl_pointer_frame(void *data, struct wl_pointer *wl_pointer)
{
}
static void
wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer,
uint32_t axis_source)
{
}
static void
wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer,
uint32_t time, uint32_t axis)
{
}
static void
wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer,
uint32_t axis, int32_t discrete)
{
}
const struct wl_pointer_listener pointer_listener = {
.enter = wl_pointer_enter,
.leave = wl_pointer_leave,
.motion = wl_pointer_motion,
.button = wl_pointer_button,
.axis = wl_pointer_axis,
.frame = wl_pointer_frame,
.axis_source = wl_pointer_axis_source,
.axis_stop = wl_pointer_axis_stop,
.axis_discrete = wl_pointer_axis_discrete,
};