diff options
| author | sl0p <sl0p@sl0p.foo> | 2026-08-13 22:08:30 +0200 |
|---|---|---|
| committer | sl0p <sl0p@sl0p.foo> | 2026-08-13 22:08:30 +0200 |
| commit | 7521f217a5ed98cef16db5837a72fae32dce9552 (patch) | |
| tree | 396837adb0cd65066d78f32eb3b6a9de7eabf67a /src/input.c | |
| parent | sl0ptty: M0 — libghostty-vt vendored, one pane, compositor, diff renderer (diff) | |
| download | slosh-7521f217a5ed98cef16db5837a72fae32dce9552.tar.gz slosh-7521f217a5ed98cef16db5837a72fae32dce9552.tar.xz slosh-7521f217a5ed98cef16db5837a72fae32dce9552.zip | |
sl0ptty: input decoder + per-pane re-encode
The half libghostty-vt does not do: decode what the outer terminal sends us
into semantic events, then re-encode each event for the target pane against
that pane's own negotiated modes.
Decoder (src/input.c): legacy keys and modifiers, SS3, tilde keys, the kitty
keyboard protocol (CSI u, shifted/base layout sub-params, press/repeat/release,
associated text), SGR mouse incl. drag and wheel, bracketed paste, focus
events, alt-prefix, UTF-8, and partial sequences retained across reads.
Re-encoding (pane_send_key/mouse/paste) drives lib-vt's key and mouse encoders
via setopt_from_terminal, so a pane gets exactly what it negotiated. The mouse
encoder wants surface pixels; we declare a 1x1 pixel cell so cell coordinates
are exact rather than approximated.
The prefix key is handled on decoded events, not bytes, so it cannot be
confused by a byte that merely looks like C-a inside a UTF-8 or escape
sequence.
Tests: 40 table-driven decoder cases (tests/input_test.c) + a live check that
drives the same client into a legacy pane and a kitty/mouse pane and asserts
each receives its own encoding (tests/live_input.py). 59 checks, all green.
Diffstat (limited to 'src/input.c')
| -rw-r--r-- | src/input.c | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..75da2fd --- /dev/null +++ b/src/input.c @@ -0,0 +1,525 @@ +/* Input decoder: outer-terminal bytes -> semantic events. See input.h. */ +#define _GNU_SOURCE +#include "input.h" + +#include <ghostty/vt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct input_parser { + uint8_t *buf; + size_t len, cap; + bool in_paste; + size_t paste_start; /* offset of paste payload within buf */ +}; + +input_parser_t *input_new(void) { + input_parser_t *p = calloc(1, sizeof *p); + p->cap = 4096; + p->buf = malloc(p->cap); + return p; +} + +void input_free(input_parser_t *p) { + if (!p) return; + free(p->buf); + free(p); +} + +static void buf_append(input_parser_t *p, const uint8_t *d, size_t n) { + if (p->len + n > p->cap) { + while (p->cap < p->len + n) p->cap *= 2; + p->buf = realloc(p->buf, p->cap); + } + memcpy(p->buf + p->len, d, n); + p->len += n; +} + +static void buf_consume(input_parser_t *p, size_t n) { + if (n >= p->len) { + p->len = 0; + return; + } + memmove(p->buf, p->buf + n, p->len - n); + p->len -= n; +} + +/* ---- key tables --------------------------------------------------------- */ + +/* Printable ASCII -> physical key. Layout-independent codes; the text we send + * separately is what actually reaches the app for ordinary typing. */ +static int ascii_key(uint8_t c) { + if (c >= 'a' && c <= 'z') return GHOSTTY_KEY_A + (c - 'a'); + if (c >= 'A' && c <= 'Z') return GHOSTTY_KEY_A + (c - 'A'); + if (c >= '0' && c <= '9') return GHOSTTY_KEY_DIGIT_0 + (c - '0'); + switch (c) { + case ' ': return GHOSTTY_KEY_SPACE; + case '`': case '~': return GHOSTTY_KEY_BACKQUOTE; + case '-': case '_': return GHOSTTY_KEY_MINUS; + case '=': case '+': return GHOSTTY_KEY_EQUAL; + case '[': case '{': return GHOSTTY_KEY_BRACKET_LEFT; + case ']': case '}': return GHOSTTY_KEY_BRACKET_RIGHT; + case '\\': case '|': return GHOSTTY_KEY_BACKSLASH; + case ';': case ':': return GHOSTTY_KEY_SEMICOLON; + case '\'': case '"': return GHOSTTY_KEY_QUOTE; + case ',': case '<': return GHOSTTY_KEY_COMMA; + case '.': case '>': return GHOSTTY_KEY_PERIOD; + case '/': case '?': return GHOSTTY_KEY_SLASH; + case '!': return GHOSTTY_KEY_DIGIT_1; + case '@': return GHOSTTY_KEY_DIGIT_2; + case '#': return GHOSTTY_KEY_DIGIT_3; + case '$': return GHOSTTY_KEY_DIGIT_4; + case '%': return GHOSTTY_KEY_DIGIT_5; + case '^': return GHOSTTY_KEY_DIGIT_6; + case '&': return GHOSTTY_KEY_DIGIT_7; + case '*': return GHOSTTY_KEY_DIGIT_8; + case '(': return GHOSTTY_KEY_DIGIT_9; + case ')': return GHOSTTY_KEY_DIGIT_0; + default: return GHOSTTY_KEY_UNIDENTIFIED; + } +} + +/* CSI <n> ~ */ +static int tilde_key(int n) { + switch (n) { + case 1: case 7: return GHOSTTY_KEY_HOME; + case 2: return GHOSTTY_KEY_INSERT; + case 3: return GHOSTTY_KEY_DELETE; + case 4: case 8: return GHOSTTY_KEY_END; + case 5: return GHOSTTY_KEY_PAGE_UP; + case 6: return GHOSTTY_KEY_PAGE_DOWN; + case 11: return GHOSTTY_KEY_F1; + case 12: return GHOSTTY_KEY_F2; + case 13: return GHOSTTY_KEY_F3; + case 14: return GHOSTTY_KEY_F4; + case 15: return GHOSTTY_KEY_F5; + case 17: return GHOSTTY_KEY_F6; + case 18: return GHOSTTY_KEY_F7; + case 19: return GHOSTTY_KEY_F8; + case 20: return GHOSTTY_KEY_F9; + case 21: return GHOSTTY_KEY_F10; + case 23: return GHOSTTY_KEY_F11; + case 24: return GHOSTTY_KEY_F12; + default: return GHOSTTY_KEY_UNIDENTIFIED; + } +} + +/* final byte of a CSI/SS3 cursor-ish sequence */ +static int final_key(uint8_t f) { + switch (f) { + case 'A': return GHOSTTY_KEY_ARROW_UP; + case 'B': return GHOSTTY_KEY_ARROW_DOWN; + case 'C': return GHOSTTY_KEY_ARROW_RIGHT; + case 'D': return GHOSTTY_KEY_ARROW_LEFT; + case 'E': return GHOSTTY_KEY_NUMPAD_BEGIN; + case 'H': return GHOSTTY_KEY_HOME; + case 'F': return GHOSTTY_KEY_END; + case 'P': return GHOSTTY_KEY_F1; + case 'Q': return GHOSTTY_KEY_F2; + case 'R': return GHOSTTY_KEY_F3; + case 'S': return GHOSTTY_KEY_F4; + default: return GHOSTTY_KEY_UNIDENTIFIED; + } +} + +/* kitty functional codepoints (CSI u form) */ +static int kitty_key(uint32_t cp) { + switch (cp) { + case 27: return GHOSTTY_KEY_ESCAPE; + case 13: return GHOSTTY_KEY_ENTER; + case 9: return GHOSTTY_KEY_TAB; + case 127: return GHOSTTY_KEY_BACKSPACE; + case 57358: return GHOSTTY_KEY_CAPS_LOCK; + case 57359: return GHOSTTY_KEY_SCROLL_LOCK; + case 57360: return GHOSTTY_KEY_NUM_LOCK; + case 57361: return GHOSTTY_KEY_PRINT_SCREEN; + case 57362: return GHOSTTY_KEY_PAUSE; + case 57363: return GHOSTTY_KEY_CONTEXT_MENU; + case 57441: return GHOSTTY_KEY_SHIFT_LEFT; + case 57442: return GHOSTTY_KEY_CONTROL_LEFT; + case 57443: return GHOSTTY_KEY_ALT_LEFT; + case 57444: return GHOSTTY_KEY_META_LEFT; + case 57447: return GHOSTTY_KEY_SHIFT_RIGHT; + case 57448: return GHOSTTY_KEY_CONTROL_RIGHT; + case 57449: return GHOSTTY_KEY_ALT_RIGHT; + case 57450: return GHOSTTY_KEY_META_RIGHT; + default: + if (cp >= 57376 && cp <= 57398) return GHOSTTY_KEY_F13 + (int)(cp - 57376); + if (cp < 128) return ascii_key((uint8_t)cp); + return GHOSTTY_KEY_UNIDENTIFIED; + } +} + +/* kitty/xterm modifier param (already decremented) -> our bits */ +static uint16_t decode_mods(int m) { + uint16_t out = 0; + if (m & 1) out |= MOD_SHIFT; + if (m & 2) out |= MOD_ALT; + if (m & 4) out |= MOD_CTRL; + if (m & 8) out |= MOD_SUPER; + if (m & 64) out |= MOD_CAPS; + if (m & 128) out |= MOD_NUM; + return out; +} + +static size_t utf8_len(uint8_t b) { + if (b < 0x80) return 1; + if ((b & 0xe0) == 0xc0) return 2; + if ((b & 0xf0) == 0xe0) return 3; + if ((b & 0xf8) == 0xf0) return 4; + return 1; /* invalid lead: consume one byte rather than stall */ +} + +static uint32_t utf8_decode(const uint8_t *s, size_t n) { + if (n == 1) return s[0]; + if (n == 2) return (uint32_t)(s[0] & 0x1f) << 6 | (s[1] & 0x3f); + if (n == 3) + return (uint32_t)(s[0] & 0x0f) << 12 | (uint32_t)(s[1] & 0x3f) << 6 | + (s[2] & 0x3f); + return (uint32_t)(s[0] & 0x07) << 18 | (uint32_t)(s[1] & 0x3f) << 12 | + (uint32_t)(s[2] & 0x3f) << 6 | (s[3] & 0x3f); +} + +/* Capture instead of dispatch; used by the ESC-prefix path. */ +static void capture_cb(const input_event_t *ev, void *ud) { + *(input_event_t *)ud = *ev; +} + +static void emit_key(input_cb_t cb, void *ud, int key, uint16_t mods, + uint8_t action, const char *text, size_t text_len, + uint32_t unshifted) { + input_event_t ev = {0}; + ev.kind = EV_KEY; + ev.key = key; + ev.mods = mods; + ev.action = action; + ev.unshifted = unshifted; + if (text && text_len) { + if (text_len > sizeof ev.text) text_len = sizeof ev.text; + memcpy(ev.text, text, text_len); + ev.text_len = (uint8_t)text_len; + } + cb(&ev, ud); +} + +/* Parse one event from the front of the buffer. + * Returns bytes consumed, or 0 if the buffer holds an incomplete sequence. */ +static size_t parse_one(input_parser_t *p, input_cb_t cb, void *ud) { + const uint8_t *b = p->buf; + size_t n = p->len; + if (n == 0) return 0; + + /* --- inside a bracketed paste: swallow until the end marker ---------- */ + if (p->in_paste) { + static const char end[] = "\x1b[201~"; + for (size_t i = 0; i + sizeof end - 1 <= n; i++) { + if (memcmp(b + i, end, sizeof end - 1) == 0) { + input_event_t ev = {.kind = EV_PASTE, + .paste = (const char *)b, + .paste_len = i}; + cb(&ev, ud); + p->in_paste = false; + return i + sizeof end - 1; + } + } + return 0; /* keep accumulating */ + } + + uint8_t c = b[0]; + + if (c == 0x1b) { + if (n == 1) return 0; /* bare ESC: resolved by input_timeout() */ + + /* --- CSI ---------------------------------------------------------- */ + if (b[1] == '[') { + size_t i = 2; + while (i < n && (b[i] < 0x40 || b[i] > 0x7e)) i++; + if (i >= n) return 0; /* no final byte yet */ + uint8_t final = b[i]; + const uint8_t *params = b + 2; + size_t plen = i - 2; + + /* bracketed paste start */ + if (final == '~' && plen == 3 && memcmp(params, "200", 3) == 0) { + p->in_paste = true; + return i + 1; + } + + /* focus in/out */ + if (final == 'I' || final == 'O') { + input_event_t ev = {.kind = EV_FOCUS, .focused = final == 'I'}; + cb(&ev, ud); + return i + 1; + } + + /* SGR mouse: CSI < b ; x ; y M|m */ + if (plen >= 1 && params[0] == '<' && (final == 'M' || final == 'm')) { + int f[3] = {0, 0, 0}, fi = 0; + for (size_t k = 1; k < plen && fi < 3; k++) { + if (params[k] == ';') { fi++; continue; } + if (params[k] >= '0' && params[k] <= '9') + f[fi] = f[fi] * 10 + (params[k] - '0'); + } + input_event_t ev = {.kind = EV_MOUSE}; + int btn = f[0]; + ev.mx = (uint16_t)(f[1] > 0 ? f[1] - 1 : 0); + ev.my = (uint16_t)(f[2] > 0 ? f[2] - 1 : 0); + if (btn & 4) ev.mods |= MOD_SHIFT; + if (btn & 8) ev.mods |= MOD_ALT; + if (btn & 16) ev.mods |= MOD_CTRL; + bool motion = (btn & 32) != 0; + int base = btn & 0xc3; /* strip mods+motion, keep high wheel bits */ + if (base >= 64 && base <= 67) { + ev.button = (uint8_t)(MBTN_FOUR + (base - 64)); /* wheel up/down/l/r */ + ev.maction = MOUSE_PRESS; + } else { + switch (base & 3) { + case 0: ev.button = MBTN_LEFT; break; + case 1: ev.button = MBTN_MIDDLE; break; + case 2: ev.button = MBTN_RIGHT; break; + default: ev.button = MBTN_UNKNOWN; break; /* 3 = release/none */ + } + ev.maction = motion ? MOUSE_MOTION + : (final == 'M' ? MOUSE_PRESS : MOUSE_RELEASE); + } + cb(&ev, ud); + return i + 1; + } + + /* numeric params, ':' sub-params kept for the kitty forms */ + int nums[8] = {0}, subs[8][3] = {{0}}, ncount = 0, sub_i = 0; + bool any = false; + for (size_t k = 0; k < plen && ncount < 8; k++) { + uint8_t ch = params[k]; + if (ch >= '0' && ch <= '9') { + any = true; + if (sub_i == 0) nums[ncount] = nums[ncount] * 10 + (ch - '0'); + else if (sub_i <= 2) subs[ncount][sub_i] = subs[ncount][sub_i] * 10 + (ch - '0'); + } else if (ch == ':') { + if (sub_i < 2) sub_i++; + } else if (ch == ';') { + ncount++; + sub_i = 0; + } + } + if (any) ncount++; + + int mod_param = ncount >= 2 ? nums[1] : 1; + uint16_t mods = decode_mods(mod_param > 0 ? mod_param - 1 : 0); + uint8_t action = KEY_PRESS; + if (ncount >= 2 && subs[1][1]) { + int et = subs[1][1]; + action = et == 3 ? KEY_RELEASE : (et == 2 ? KEY_REPEAT : KEY_PRESS); + } + + /* kitty CSI u */ + if (final == 'u') { + uint32_t cp = (uint32_t)nums[0]; + uint32_t shifted = (uint32_t)subs[0][1]; + uint32_t base_layout = (uint32_t)subs[0][2]; + int key = kitty_key(base_layout ? base_layout : cp); + char text[16]; + size_t tlen = 0; + /* associated text (3rd param) wins; else derive from the codepoint */ + uint32_t out_cp = 0; + if (ncount >= 3 && nums[2]) out_cp = (uint32_t)nums[2]; + else if (!(mods & (MOD_CTRL | MOD_SUPER)) && cp >= 32 && cp != 127) + out_cp = (mods & MOD_SHIFT) && shifted ? shifted : cp; + if (out_cp) { + if (out_cp < 0x80) text[tlen++] = (char)out_cp; + else if (out_cp < 0x800) { + text[tlen++] = (char)(0xc0 | (out_cp >> 6)); + text[tlen++] = (char)(0x80 | (out_cp & 0x3f)); + } else if (out_cp < 0x10000) { + text[tlen++] = (char)(0xe0 | (out_cp >> 12)); + text[tlen++] = (char)(0x80 | ((out_cp >> 6) & 0x3f)); + text[tlen++] = (char)(0x80 | (out_cp & 0x3f)); + } else { + text[tlen++] = (char)(0xf0 | (out_cp >> 18)); + text[tlen++] = (char)(0x80 | ((out_cp >> 12) & 0x3f)); + text[tlen++] = (char)(0x80 | ((out_cp >> 6) & 0x3f)); + text[tlen++] = (char)(0x80 | (out_cp & 0x3f)); + } + } + emit_key(cb, ud, key, mods, action, text, tlen, cp); + return i + 1; + } + + if (final == '~') { + emit_key(cb, ud, tilde_key(nums[0]), mods, action, NULL, 0, 0); + return i + 1; + } + + int key = final_key(final); + if (key != GHOSTTY_KEY_UNIDENTIFIED) { + emit_key(cb, ud, key, mods, action, NULL, 0, 0); + return i + 1; + } + return i + 1; /* unrecognised CSI: consume, do not forward */ + } + + /* --- SS3 ---------------------------------------------------------- */ + if (b[1] == 'O') { + if (n < 3) return 0; + int key = final_key(b[2]); + if (key != GHOSTTY_KEY_UNIDENTIFIED) + emit_key(cb, ud, key, 0, KEY_PRESS, NULL, 0, 0); + return 3; + } + + /* --- ESC <byte>: legacy alt ---------------------------------------- */ + /* Parse the tail as its own event and add ALT, so alt+<utf8> and + * alt+ctrl-x both fall out of the same code path. */ + { + input_parser_t tail = {.buf = p->buf + 1, .len = n - 1, .cap = n - 1}; + input_event_t captured = {0}; + size_t used = parse_one(&tail, capture_cb, &captured); + if (used == 0) return 0; /* incomplete: wait for more bytes */ + if (captured.kind == EV_KEY) captured.mods |= MOD_ALT; + if (captured.kind != EV_NONE) cb(&captured, ud); + return used + 1; + } + } + + /* --- C0 controls ----------------------------------------------------- */ + if (c == '\r') { emit_key(cb, ud, GHOSTTY_KEY_ENTER, 0, KEY_PRESS, "\r", 1, 13); return 1; } + if (c == '\n') { emit_key(cb, ud, GHOSTTY_KEY_ENTER, 0, KEY_PRESS, "\r", 1, 13); return 1; } + if (c == '\t') { emit_key(cb, ud, GHOSTTY_KEY_TAB, 0, KEY_PRESS, "\t", 1, 9); return 1; } + if (c == 0x7f) { emit_key(cb, ud, GHOSTTY_KEY_BACKSPACE, 0, KEY_PRESS, NULL, 0, 127); return 1; } + if (c == 0x08) { emit_key(cb, ud, GHOSTTY_KEY_BACKSPACE, 0, KEY_PRESS, NULL, 0, 8); return 1; } + if (c == 0x00) { emit_key(cb, ud, GHOSTTY_KEY_SPACE, MOD_CTRL, KEY_PRESS, NULL, 0, ' '); return 1; } + if (c < 0x20) { + int key; + switch (c) { + case 0x1c: key = GHOSTTY_KEY_BACKSLASH; break; + case 0x1d: key = GHOSTTY_KEY_BRACKET_RIGHT; break; + case 0x1e: key = GHOSTTY_KEY_DIGIT_6; break; + case 0x1f: key = GHOSTTY_KEY_MINUS; break; + default: key = GHOSTTY_KEY_A + (c - 1); break; + } + uint32_t unshifted = c < 0x1b ? (uint32_t)('a' + c - 1) : 0; + emit_key(cb, ud, key, MOD_CTRL, KEY_PRESS, NULL, 0, unshifted); + return 1; + } + + /* --- text ------------------------------------------------------------ */ + size_t ulen = utf8_len(c); + if (ulen > n) return 0; /* split UTF-8 across reads */ + uint32_t cp = utf8_decode(b, ulen); + int key = cp < 128 ? ascii_key((uint8_t)cp) : GHOSTTY_KEY_UNIDENTIFIED; + uint16_t mods = 0; + if (cp < 128 && cp >= 'A' && cp <= 'Z') mods |= MOD_SHIFT; + uint32_t unshifted = cp; + if (cp >= 'A' && cp <= 'Z') unshifted = cp + 32; + emit_key(cb, ud, key, mods, KEY_PRESS, (const char *)b, ulen, unshifted); + return ulen; +} + +void input_feed(input_parser_t *p, const uint8_t *data, size_t len, + input_cb_t cb, void *ud) { + buf_append(p, data, len); + for (;;) { + size_t used = parse_one(p, cb, ud); + if (used == 0) break; + buf_consume(p, used); + if (p->len == 0) break; + } +} + +bool input_pending(const input_parser_t *p) { return p->len > 0; } + +void input_timeout(input_parser_t *p, input_cb_t cb, void *ud) { + if (p->len == 0) return; + if (p->buf[0] == 0x1b) { + emit_key(cb, ud, GHOSTTY_KEY_ESCAPE, 0, KEY_PRESS, NULL, 0, 27); + buf_consume(p, 1); + /* whatever followed is now re-parsed as ordinary input */ + input_feed(p, NULL, 0, cb, ud); + return; + } + p->len = 0; /* an unterminated non-ESC sequence is garbage; drop it */ +} + +/* ---- description (tests, logs) ------------------------------------------ */ + +static const char *key_name(int key) { + switch (key) { + case GHOSTTY_KEY_ARROW_UP: return "ARROW_UP"; + case GHOSTTY_KEY_ARROW_DOWN: return "ARROW_DOWN"; + case GHOSTTY_KEY_ARROW_LEFT: return "ARROW_LEFT"; + case GHOSTTY_KEY_ARROW_RIGHT: return "ARROW_RIGHT"; + case GHOSTTY_KEY_ENTER: return "ENTER"; + case GHOSTTY_KEY_TAB: return "TAB"; + case GHOSTTY_KEY_ESCAPE: return "ESCAPE"; + case GHOSTTY_KEY_BACKSPACE: return "BACKSPACE"; + case GHOSTTY_KEY_SPACE: return "SPACE"; + case GHOSTTY_KEY_HOME: return "HOME"; + case GHOSTTY_KEY_END: return "END"; + case GHOSTTY_KEY_INSERT: return "INSERT"; + case GHOSTTY_KEY_DELETE: return "DELETE"; + case GHOSTTY_KEY_PAGE_UP: return "PAGE_UP"; + case GHOSTTY_KEY_PAGE_DOWN: return "PAGE_DOWN"; + case GHOSTTY_KEY_UNIDENTIFIED: return "UNIDENTIFIED"; + default: break; + } + if (key >= GHOSTTY_KEY_A && key <= GHOSTTY_KEY_Z) { + static char buf[2]; + buf[0] = (char)('a' + (key - GHOSTTY_KEY_A)); + buf[1] = 0; + return buf; + } + if (key >= GHOSTTY_KEY_DIGIT_0 && key <= GHOSTTY_KEY_DIGIT_9) { + static char buf[2]; + buf[0] = (char)('0' + (key - GHOSTTY_KEY_DIGIT_0)); + buf[1] = 0; + return buf; + } + if (key >= GHOSTTY_KEY_F1 && key <= GHOSTTY_KEY_F12) { + static char buf[4]; + snprintf(buf, sizeof buf, "F%d", key - GHOSTTY_KEY_F1 + 1); + return buf; + } + static char other[16]; + snprintf(other, sizeof other, "key#%d", key); + return other; +} + +void input_event_describe(const input_event_t *ev, char *buf, size_t cap) { + char mods[64] = ""; + if (ev->mods & MOD_SHIFT) strcat(mods, "S"); + if (ev->mods & MOD_CTRL) strcat(mods, "C"); + if (ev->mods & MOD_ALT) strcat(mods, "A"); + if (ev->mods & MOD_SUPER) strcat(mods, "M"); + + switch (ev->kind) { + case EV_KEY: { + const char *act = ev->action == KEY_PRESS ? "press" + : ev->action == KEY_RELEASE ? "release" : "repeat"; + char text[32] = ""; + if (ev->text_len) { + snprintf(text, sizeof text, " text=%.*s", (int)ev->text_len, ev->text); + } + snprintf(buf, cap, "key %s mods=%s %s%s", key_name(ev->key), + *mods ? mods : "-", act, text); + break; + } + case EV_MOUSE: { + const char *act = ev->maction == MOUSE_PRESS ? "press" + : ev->maction == MOUSE_RELEASE ? "release" : "motion"; + snprintf(buf, cap, "mouse btn=%u %s at=%u,%u mods=%s", ev->button, act, + ev->mx, ev->my, *mods ? mods : "-"); + break; + } + case EV_PASTE: + snprintf(buf, cap, "paste len=%zu text=%.*s", ev->paste_len, + (int)(ev->paste_len > 20 ? 20 : ev->paste_len), ev->paste); + break; + case EV_FOCUS: + snprintf(buf, cap, "focus %s", ev->focused ? "in" : "out"); + break; + default: + snprintf(buf, cap, "none"); + break; + } +} |
