From 927ae621c9a3d46dbc401b8e9ea7bbed1bf2e97b Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Sun, 11 Oct 2026 21:53:42 +0300 Subject: initial: Yet Another Surface Library (YASL) 0.1.0 A minimal, modern desktop window surface library in C. Supports Wayland on Linux and Cocoa/Metal on macOS with presentation timing, fractional scaling, IME, and CSD. --- src/wayland/keyboard.c | 389 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 389 insertions(+) create mode 100644 src/wayland/keyboard.c (limited to 'src/wayland/keyboard.c') diff --git a/src/wayland/keyboard.c b/src/wayland/keyboard.c new file mode 100644 index 0000000..7283d58 --- /dev/null +++ b/src/wayland/keyboard.c @@ -0,0 +1,389 @@ +#include "internal.h" + +#define XKB_EVDEV_OFFSET 8 +#define KEYMAP_MAX_BYTES (8u * 1024 * 1024) + +void keyboard_repeat_stop(struct seat *s) +{ + if (!s->repeat_held) return; + s->repeat_held = false; + if (s->repeat_fd >= 0) { + struct itimerspec off = {0}; + timerfd_settime(s->repeat_fd, 0, &off, NULL); + } +} + +void keyboard_repeat_sync(struct seat *s) +{ + if (s->repeat_fd < 0) return; + struct itimerspec timer = {0}; + if (s->repeat_held && s->repeat_rate > 0) { + uint64_t interval = 1000000000ull / (uint32_t)s->repeat_rate; + if (!interval) interval = 1; + uint64_t delay_ns = (uint64_t)s->repeat_delay * 1000000ull; + if (!delay_ns) delay_ns = 1; + timer.it_value = (struct timespec){ (time_t)(delay_ns / 1000000000ull), (long)(delay_ns % 1000000000ull) }; + timer.it_interval = (struct timespec){ (time_t)(interval / 1000000000ull), (long)(interval % 1000000000ull) }; + } + timerfd_settime(s->repeat_fd, 0, &timer, NULL); +} + +static const char *const input_mod_names[8] = { + XKB_MOD_NAME_SHIFT, XKB_MOD_NAME_ALT, + XKB_MOD_NAME_CTRL, XKB_MOD_NAME_LOGO, + "Hyper", "Meta", + XKB_MOD_NAME_CAPS, XKB_MOD_NAME_NUM +}; + +static bool mod_active(const struct seat *s, const char *name) +{ + return xkb_state_mod_name_is_active(s->xkb_state, name, XKB_STATE_MODS_EFFECTIVE) > 0; +} + +static void resolve_input_mod_masks(struct seat *s) +{ + memset(s->input_mod_masks, 0, sizeof(s->input_mod_masks)); + if (!s->xkb_keymap) return; + struct xkb_state *probe = xkb_state_new(s->xkb_keymap); + if (!probe) return; + for (unsigned real = 0; real < 8; real++) { + xkb_state_update_mask(probe, 1u << real, 0, 0, 0, 0, 0); + for (unsigned i = 0; i < 8; i++) { + if (xkb_state_mod_name_is_active(probe, input_mod_names[i], + XKB_STATE_MODS_EFFECTIVE) > 0) + s->input_mod_masks[i] |= 1u << real; + } + } + xkb_state_unref(probe); + s->input_mods_ready = true; +} + +static void keyboard_keymap(void *data, struct wl_keyboard *keyboard, + uint32_t format, int32_t fd, uint32_t size) +{ + (void)keyboard; + struct seat *s = data; + keyboard_repeat_stop(s); + s->input_mods_ready = false; + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + return; + } + struct stat st; + if (size == 0 || size > KEYMAP_MAX_BYTES || + fstat(fd, &st) < 0 || st.st_size < 0 || (uint64_t)st.st_size < size) { + close(fd); + return; + } + char *map_str = malloc(size); + if (!map_str) { + close(fd); + return; + } + size_t off = 0; + while (off < size) { + ssize_t n = pread(fd, map_str + off, size - off, (off_t)off); + if (n > 0) off += (size_t)n; + else if (n == 0 || errno != EINTR) break; + } + close(fd); + if (off < size) { + free(map_str); + return; + } + + if (s->xkb_keymap) + xkb_keymap_unref(s->xkb_keymap); + if (s->xkb_state) + xkb_state_unref(s->xkb_state); + s->xkb_state = NULL; + + s->xkb_keymap = xkb_keymap_new_from_buffer(s->conn->xkb_ctx, map_str, size, + XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + free(map_str); + + if (s->xkb_keymap) { + s->xkb_state = xkb_state_new(s->xkb_keymap); + s->mod_shift = xkb_keymap_mod_get_index(s->xkb_keymap, XKB_MOD_NAME_SHIFT); + s->mod_alt = xkb_keymap_mod_get_index(s->xkb_keymap, XKB_MOD_NAME_ALT); + s->mod_ctrl = xkb_keymap_mod_get_index(s->xkb_keymap, XKB_MOD_NAME_CTRL); + s->mod_super = xkb_keymap_mod_get_index(s->xkb_keymap, XKB_MOD_NAME_LOGO); + resolve_input_mod_masks(s); + } +} + +static void keyboard_reset_keys(struct seat *s, struct yasl_surface *w) +{ + (void)w; + keyboard_repeat_stop(s); +} + +bool conn_keyboard_on(const struct yasl_display *c, const struct yasl_surface *w, + const struct seat *except) +{ + for (const struct seat *s = c->seats; s; s = s->next) + if (s != except && s->keyboard_focus == w) + return true; + return false; +} + +static void push_focus(struct yasl_surface *w, bool focused) +{ + struct yasl_input_event ev = { + .kind = YASL_INPUT_FOCUS, + .surface = w, + .focus = { .focused = focused }, + }; + input_queue_push(&w->input, &ev); +} + +static void keyboard_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, struct wl_array *keys) +{ + (void)keyboard; (void)serial; (void)keys; + struct seat *s = data; + struct yasl_surface *w = surface_root(conn_surface_for(s->conn, surface)); + bool same_window = w && w == s->focus_leaving; + if (same_window) + s->focus_leaving = NULL; + if (!w) { + s->keyboard_focus = NULL; + return; + } + bool focused = conn_keyboard_on(s->conn, w, s); + s->keyboard_focus = w; + keyboard_reset_keys(s, w); + if (!same_window && !focused) + push_focus(w, true); +} + +static void seat_settle_focus(struct seat *s) +{ + struct yasl_surface *w = s->focus_leaving; + if (!w) + return; + s->focus_leaving = NULL; + if (!conn_keyboard_on(s->conn, w, NULL)) + push_focus(w, false); +} + +void keyboard_settle_focus(struct yasl_display *c) +{ + for (struct seat *s = c->seats; s; s = s->next) + seat_settle_focus(s); +} + +static void keyboard_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) +{ + (void)keyboard; (void)serial; (void)surface; + struct seat *s = data; + struct yasl_surface *w = s->keyboard_focus; + s->keyboard_focus = NULL; + if (!w) return; + keyboard_reset_keys(s, w); + seat_settle_focus(s); + s->focus_leaving = w; +} + +static unsigned input_modifiers(struct seat *s) +{ + if (!s->input_mods_ready) + resolve_input_mod_masks(s); + + unsigned mods = 0; + xkb_mod_mask_t reported = 0; + for (unsigned i = 0; i < 8; i++) { + xkb_mod_mask_t mask = s->input_mod_masks[i]; + if (mask && (mask & ~reported) && mod_active(s, input_mod_names[i])) { + mods |= 1u << i; + reported |= mask; + } + } + return mods; +} + +static uint32_t key_codepoint_at_level(struct seat *s, uint32_t key, xkb_layout_index_t layout, xkb_level_index_t level) +{ + const xkb_keysym_t *syms; + int n = xkb_keymap_key_get_syms_by_level(s->xkb_keymap, key + XKB_EVDEV_OFFSET, layout, level, &syms); + return n == 1 ? xkb_keysym_to_utf32(syms[0]) : 0; +} + +static struct yasl_key_event xkb_key_event(struct seat *s, uint32_t key, + enum yasl_key_action action) +{ + uint32_t code = key + XKB_EVDEV_OFFSET; + xkb_keysym_t sym = xkb_state_key_get_one_sym(s->xkb_state, code); + xkb_layout_index_t layout = xkb_state_key_get_layout(s->xkb_state, code); + + struct yasl_key_event k = { + .key = sym, + .keypad = key >= KEY_KP0 && key <= KEY_KPCOMMA, + .action = action, + .mods = input_modifiers(s), + .codepoint = key_codepoint_at_level(s, key, layout, 0), + .shifted = key_codepoint_at_level(s, key, layout, 1), + .base = key, + .text = xkb_state_key_get_utf32(s->xkb_state, code), + }; + if (!k.codepoint) k.codepoint = xkb_keysym_to_utf32(sym); + return k; +} + +static bool keyboard_dispatch_key(struct seat *s, struct yasl_surface *w, uint32_t key, + enum yasl_key_action action) +{ + if (!s->xkb_state || !w->cbs) return false; + + struct yasl_key_event k = xkb_key_event(s, key, action); + struct yasl_input_event ev = { + .kind = YASL_INPUT_KEY, + .surface = w, + .key = k, + }; + input_queue_push(&w->input, &ev); + return action != YASL_KEY_RELEASE; +} + +static void keyboard_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t state) +{ + (void)keyboard; (void)time; + struct seat *s = data; + struct yasl_surface *w = s->keyboard_focus; + if (!w) return; + seat_note_input(s, serial); + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { + if (s->repeat_held && key == s->repeat_key) keyboard_repeat_stop(s); + keyboard_dispatch_key(s, w, key, YASL_KEY_RELEASE); + return; + } + if (state != WL_KEYBOARD_KEY_STATE_PRESSED || !s->xkb_keymap) return; + bool repeatable = xkb_keymap_key_repeats(s->xkb_keymap, key + XKB_EVDEV_OFFSET); + if (repeatable) keyboard_repeat_stop(s); + if (keyboard_dispatch_key(s, w, key, YASL_KEY_PRESS) && repeatable) { + s->repeat_key = key; + s->repeat_held = true; + s->repeat_started_ns = yasl_loop_now_ns(); + keyboard_repeat_sync(s); + } +} + +static int keyboard_repeat_ready(int fd, short revents, void *data) +{ + struct seat *s = data; + uint64_t count; + if (!(revents & POLLIN) || read(fd, &count, sizeof(count)) != sizeof(count)) return 0; + + struct yasl_surface *w = s->keyboard_focus; + if (!w) { + keyboard_repeat_stop(s); + return 0; + } + if (count > 32) count = 32; + while (count-- && s->repeat_held && s->repeat_rate > 0) { + if (!keyboard_dispatch_key(s, w, s->repeat_key, YASL_KEY_REPEAT)) + keyboard_repeat_stop(s); + } + return 0; +} + +static void keyboard_modifiers(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) +{ + (void)keyboard; (void)serial; + struct seat *s = data; + if (s->xkb_state) { + xkb_state_update_mask(s->xkb_state, mods_depressed, mods_latched, + mods_locked, 0, 0, group); + } +} + +static void keyboard_repeat_info(void *data, struct wl_keyboard *keyboard, + int32_t rate, int32_t delay) +{ + (void)keyboard; + struct seat *s = data; + s->repeat_rate = rate > 0 ? rate : 0; + s->repeat_delay = delay > 0 ? delay : 0; + keyboard_repeat_sync(s); +} + +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, +}; + +int keyboard_repeat_init(struct seat *s) +{ + if (s->repeat_fd >= 0) return 0; + s->repeat_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); + if (s->repeat_fd < 0) return -1; + s->repeat_watch = yasl_loop_add_fd(s->conn->loop, s->repeat_fd, POLLIN, + keyboard_repeat_ready, s); + if (s->repeat_watch <= 0) { + s->repeat_watch = 0; + close(s->repeat_fd); + s->repeat_fd = -1; + return -1; + } + keyboard_repeat_sync(s); + return 0; +} + +static bool mod_index_active(const struct seat *s, xkb_mod_index_t mod) +{ + return mod != XKB_MOD_INVALID && + xkb_state_mod_index_is_active(s->xkb_state, mod, + XKB_STATE_MODS_EFFECTIVE) > 0; +} + +int seat_modifiers(const struct seat *s) +{ + if (!s || !s->xkb_state) return 0; + int mods = 0; + if (mod_index_active(s, s->mod_shift)) mods |= YASL_MOD_SHIFT; + if (mod_index_active(s, s->mod_alt)) mods |= YASL_MOD_ALT; + if (mod_index_active(s, s->mod_ctrl)) mods |= YASL_MOD_CTRL; + if (mod_index_active(s, s->mod_super)) mods |= YASL_MOD_SUPER; + return mods; +} + +int yasl_display_modifiers(const struct yasl_display *c) +{ + return c ? seat_modifiers(conn_active_seat(c)) : 0; +} + +void yasl_display_copy(struct yasl_display *c, enum yasl_selection sel, const char *text) +{ + struct seat *s = c ? conn_active_seat(c) : NULL; + if (!s || !s->clipboard) + return; + if (sel == YASL_PRIMARY) + clipboard_copy_primary(s->clipboard, text, s->input_serial); + else + clipboard_copy(s->clipboard, text, s->input_serial); +} + +void yasl_display_paste(struct yasl_display *c, enum yasl_selection sel, struct yasl_surface *target) +{ + struct seat *s = c ? conn_active_seat(c) : NULL; + if (!s || !s->clipboard) + return; + c->paste_target = target; + if (sel == YASL_PRIMARY) + clipboard_paste_primary(s->clipboard); + else + clipboard_paste(s->clipboard); +} + +bool yasl_surface_input_poll(struct yasl_surface *w, struct yasl_input_event *ev) +{ + return w && ev && input_queue_pop(&w->input, ev); +} -- cgit v1.2.3