summaryrefslogtreecommitdiff
path: root/src/wayland/keyboard.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/wayland/keyboard.c')
-rw-r--r--src/wayland/keyboard.c389
1 files changed, 389 insertions, 0 deletions
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);
+}