From b5780aceb169f9d36e6d5639db15052b8f51a8d2 Mon Sep 17 00:00:00 2001 From: elviosak <33790211+elviosak@users.noreply.github.com> Date: Fri, 17 Apr 2026 22:04:59 -0300 Subject: [PATCH 1/2] add SendKey action and identifier --- src/action.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/action.c b/src/action.c index 34435e02..7f10fd0c 100644 --- a/src/action.c +++ b/src/action.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "action-prompt-codes.h" #include "common/buf.h" @@ -137,6 +138,7 @@ struct action_arg_list { X(TOGGLE_SHOW_DESKTOP, "ToggleShowDesktop") \ X(WARP_CURSOR, "WarpCursor") \ X(HIDE_CURSOR, "HideCursor") \ + X(SEND_KEY, "SendKey") \ X(DEBUG_TOGGLE_KEY_STATE_INDICATOR, "DebugToggleKeyStateIndicator") /* @@ -531,6 +533,12 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char goto cleanup; } break; + case ACTION_TYPE_SEND_KEY: + if (!strcasecmp(argument, "identifier")) { + action_arg_add_str(action, "identifier", content); + goto cleanup; + } + break; case ACTION_TYPE_IF: if (!strcmp(argument, "message.prompt")) { action_arg_add_str(action, "message.prompt", content); @@ -648,6 +656,9 @@ action_is_valid(struct action *action) case ACTION_TYPE_SNAP_TO_REGION: arg_name = "region"; break; + case ACTION_TYPE_SEND_KEY: + arg_name = "identifier"; + break; case ACTION_TYPE_IF: case ACTION_TYPE_FOR_EACH: return action_branches_are_valid(action); @@ -1428,6 +1439,35 @@ run_action(struct view *view, struct action *action, } break; } + case ACTION_TYPE_SEND_KEY: { + struct view *target_view; + struct wlr_seat *wlr_seat = server.seat.wlr_seat; + struct wlr_keyboard *kb = wlr_seat_get_keyboard(server.seat.wlr_seat); + if (!kb) break; + struct timespec now = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &now); + uint32_t time_msec = now.tv_nsec / 1000000; + for_each_view(target_view, &server.views, LAB_VIEW_CRITERIA_NONE) { + if (!strcasecmp(target_view->app_id, action_get_str(action, "identifier", NULL))) { + uint32_t *pressed_sent_keycodes = key_state_pressed_sent_keycodes(); + int nr_pressed_sent_keycodes = key_state_nr_pressed_sent_keycodes(); + wlr_seat_keyboard_notify_enter(wlr_seat, target_view->surface, + pressed_sent_keycodes, nr_pressed_sent_keycodes, &kb->modifiers); + wlr_seat_keyboard_notify_modifiers(wlr_seat, &kb->modifiers); + for (size_t i = 0; i < kb->num_keycodes; i++) { + wlr_seat_keyboard_notify_key(wlr_seat, time_msec, kb->keycodes[i], WL_KEYBOARD_KEY_STATE_PRESSED); + } + for (size_t i = 0; i < kb->num_keycodes; i++) { + wlr_seat_keyboard_notify_key(wlr_seat, time_msec, kb->keycodes[i], WL_KEYBOARD_KEY_STATE_RELEASED); + } + if (view) { + wlr_seat_keyboard_notify_enter(wlr_seat, view->surface, + pressed_sent_keycodes, nr_pressed_sent_keycodes, &kb->modifiers); + } + } + } + break; + } case ACTION_TYPE_IF: { /* At least one of the queries was matched or there was no query */ if (action_get_str(action, "message.prompt", NULL)) { From d058f89cb8fb768cccfa192ff49a611d030175df Mon Sep 17 00:00:00 2001 From: elviosak <33790211+elviosak@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:25:42 -0300 Subject: [PATCH 2/2] add "key" argument to SendKey --- include/config/keybind.h | 1 + src/action.c | 170 ++++++++++++++++++++++++++++++++------- src/config/keybind.c | 4 +- src/config/rcxml.c | 4 +- 4 files changed, 145 insertions(+), 34 deletions(-) diff --git a/include/config/keybind.h b/include/config/keybind.h index d02de387..50ca67a3 100644 --- a/include/config/keybind.h +++ b/include/config/keybind.h @@ -45,6 +45,7 @@ bool keybind_the_same(struct keybind *a, struct keybind *b); bool keybind_contains_keycode(struct keybind *keybind, xkb_keycode_t keycode); bool keybind_contains_keysym(struct keybind *keybind, xkb_keysym_t keysym); +bool keybind_contains_any_keysym(struct keybind *keybind, const xkb_keysym_t *syms, int nr_syms); void keybind_update_keycodes(void); #endif /* LABWC_KEYBIND_H */ diff --git a/src/action.c b/src/action.c index 7f10fd0c..5359ec7d 100644 --- a/src/action.c +++ b/src/action.c @@ -19,6 +19,7 @@ #include "common/spawn.h" #include "common/string-helpers.h" #include "config/rcxml.h" +#include "config/keybind.h" #include "cycle.h" #include "debug.h" #include "input/keyboard.h" @@ -534,8 +535,8 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char } break; case ACTION_TYPE_SEND_KEY: - if (!strcasecmp(argument, "identifier")) { - action_arg_add_str(action, "identifier", content); + if (!strcasecmp(argument, "identifier") || !strcasecmp(argument, "key")) { + action_arg_add_str(action, argument, content); goto cleanup; } break; @@ -1061,6 +1062,141 @@ warp_cursor(struct view *view, const char *to, const char *x, const char *y) cursor_update_focus(); } +static void +action_key_update_keycodes_iter(struct xkb_keymap *keymap, xkb_keycode_t key, void *data) +{ + struct keybind *keybind = (struct keybind *)data; + const xkb_keysym_t *syms; + + xkb_layout_index_t layouts = xkb_keymap_num_layouts(keymap); + for (xkb_layout_index_t layout = 0; layout < layouts; layout++) { + int nr_syms = xkb_keymap_key_get_syms_by_level(keymap, key, layout, 0, &syms); + if (!nr_syms) { + continue; + } + if (keybind->keycodes_layout >= 0 + && (xkb_layout_index_t)keybind->keycodes_layout != layout) { + /* Prevent storing keycodes from multiple layouts */ + continue; + } + if (keybind_contains_any_keysym(keybind, syms, nr_syms)) { + if (keybind_contains_keycode(keybind, key)) { + /* Prevent storing the same keycode twice */ + continue; + } + if (keybind->keycodes_len == MAX_KEYCODES) { + wlr_log(WLR_ERROR, + "Already stored %lu keycodes for keybind", + keybind->keycodes_len); + continue; + } + keybind->keycodes[keybind->keycodes_len++] = key; + keybind->keycodes_layout = layout; + } + } +} + +static void +action_key_update_keycodes(struct keybind *keybind, struct xkb_keymap *keymap) +{ + keybind->keycodes_len = 0; + keybind->keycodes_layout = -1; + + xkb_keymap_key_for_each(keymap, action_key_update_keycodes_iter, keybind); +} + +static bool +action_key_parse(const char *key, struct wlr_keyboard_modifiers *modifiers, + uint32_t *keycodes, size_t *num_keycodes) +{ + struct wlr_keyboard *kb = wlr_seat_get_keyboard(server.seat.wlr_seat); + if (!kb) { + return false; + } + if (!key) { + *modifiers = kb->modifiers; + *num_keycodes = kb->num_keycodes > MAX_KEYCODES ? MAX_KEYCODES : kb->num_keycodes; + for (size_t i = 0; i < *num_keycodes; ++i) { + keycodes[i] = kb->keycodes[i]; + } + return true; + } + if (!kb->keymap) { + return false; + } + struct keybind *k = NULL; + k = keybind_create(key); + if (!k) { + return false; + } + + action_key_update_keycodes(k, kb->keymap); + modifiers->depressed = k->modifiers; + *num_keycodes = k->keycodes_len; + for (size_t i = 0; i < *num_keycodes; ++i) { + if (k->keycodes[i] < 8) { + keybind_destroy(k); + return false; + } + keycodes[i] = k->keycodes[i] - 8; + } + keybind_destroy(k); + if (modifiers->depressed == 0 && *num_keycodes == 0) { + return false; + } + return true; +} + +static void +action_send_key(struct action *action, struct view *view) +{ + struct view *target_view; + struct wlr_seat *wlr_seat = server.seat.wlr_seat; + struct wlr_keyboard *kb = wlr_seat_get_keyboard(server.seat.wlr_seat); + if (!kb) { + return; + } + struct timespec now = {0}; + clock_gettime(CLOCK_MONOTONIC, &now); + uint64_t time_msec = now.tv_sec * 1000 + now.tv_nsec / 1000000; + const char *identifier = action_get_str(action, "identifier", NULL); + if (!identifier) { + return; + } + uint32_t *sent_keycodes = key_state_pressed_sent_keycodes(); + int num_sent_keycodes = key_state_nr_pressed_sent_keycodes(); + const char *key = action_get_str(action, "key", NULL); + struct wlr_keyboard_modifiers modifiers = {0}; + uint32_t keycodes[MAX_KEYCODES]; + size_t num_keycodes = 0; + if (!action_key_parse(key, &modifiers, keycodes, &num_keycodes)) { + wlr_log(WLR_ERROR, "Failed to parse key: %s", key); + return; + } + bool sent = false; + for_each_view(target_view, &server.views, LAB_VIEW_CRITERIA_NONE) { + if (target_view->surface && !strcasecmp(target_view->app_id, identifier)) { + wlr_seat_keyboard_notify_enter(wlr_seat, target_view->surface, + sent_keycodes, num_sent_keycodes, &modifiers); + wlr_seat_keyboard_notify_modifiers(wlr_seat, &modifiers); + for (size_t i = 0; i < num_keycodes; i++) { + wlr_seat_keyboard_notify_key(wlr_seat, (uint32_t)time_msec, + keycodes[i], WL_KEYBOARD_KEY_STATE_PRESSED); + } + for (size_t i = num_keycodes; i > 0; i--) { + wlr_seat_keyboard_notify_key(wlr_seat, (uint32_t)time_msec, + keycodes[i-1], WL_KEYBOARD_KEY_STATE_RELEASED); + } + sent = true; + } + } + if (sent && view && view->surface) { + wlr_seat_keyboard_notify_enter(wlr_seat, view->surface, + sent_keycodes, num_sent_keycodes, &kb->modifiers); + wlr_seat_keyboard_notify_modifiers(wlr_seat, &kb->modifiers); + } +} + static void run_action(struct view *view, struct action *action, struct cursor_context *ctx) @@ -1439,35 +1575,9 @@ run_action(struct view *view, struct action *action, } break; } - case ACTION_TYPE_SEND_KEY: { - struct view *target_view; - struct wlr_seat *wlr_seat = server.seat.wlr_seat; - struct wlr_keyboard *kb = wlr_seat_get_keyboard(server.seat.wlr_seat); - if (!kb) break; - struct timespec now = { 0 }; - clock_gettime(CLOCK_MONOTONIC, &now); - uint32_t time_msec = now.tv_nsec / 1000000; - for_each_view(target_view, &server.views, LAB_VIEW_CRITERIA_NONE) { - if (!strcasecmp(target_view->app_id, action_get_str(action, "identifier", NULL))) { - uint32_t *pressed_sent_keycodes = key_state_pressed_sent_keycodes(); - int nr_pressed_sent_keycodes = key_state_nr_pressed_sent_keycodes(); - wlr_seat_keyboard_notify_enter(wlr_seat, target_view->surface, - pressed_sent_keycodes, nr_pressed_sent_keycodes, &kb->modifiers); - wlr_seat_keyboard_notify_modifiers(wlr_seat, &kb->modifiers); - for (size_t i = 0; i < kb->num_keycodes; i++) { - wlr_seat_keyboard_notify_key(wlr_seat, time_msec, kb->keycodes[i], WL_KEYBOARD_KEY_STATE_PRESSED); - } - for (size_t i = 0; i < kb->num_keycodes; i++) { - wlr_seat_keyboard_notify_key(wlr_seat, time_msec, kb->keycodes[i], WL_KEYBOARD_KEY_STATE_RELEASED); - } - if (view) { - wlr_seat_keyboard_notify_enter(wlr_seat, view->surface, - pressed_sent_keycodes, nr_pressed_sent_keycodes, &kb->modifiers); - } - } - } + case ACTION_TYPE_SEND_KEY: + action_send_key(action, view); break; - } case ACTION_TYPE_IF: { /* At least one of the queries was matched or there was no query */ if (action_get_str(action, "message.prompt", NULL)) { diff --git a/src/config/keybind.c b/src/config/keybind.c index 8205ea93..46f237cc 100644 --- a/src/config/keybind.c +++ b/src/config/keybind.c @@ -7,7 +7,6 @@ #include #include #include -#include "common/list.h" #include "common/mem.h" #include "config/rcxml.h" #include "labwc.h" @@ -72,7 +71,7 @@ keybind_contains_keysym(struct keybind *keybind, xkb_keysym_t keysym) return false; } -static bool +bool keybind_contains_any_keysym(struct keybind *keybind, const xkb_keysym_t *syms, int nr_syms) { @@ -205,7 +204,6 @@ keybind_create(const char *keybind) if (!k) { return NULL; } - wl_list_append(&rc.keybinds, &k->link); k->keysyms = xmalloc(k->keysyms_len * sizeof(xkb_keysym_t)); memcpy(k->keysyms, keysyms, k->keysyms_len * sizeof(xkb_keysym_t)); wl_list_init(&k->actions); diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 20c236b4..6f56f7ae 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -593,6 +593,8 @@ fill_keybind(xmlNode *node) keybind = keybind_create(keyname); if (!keybind) { wlr_log(WLR_ERROR, "Invalid keybind: %s", keyname); + } else { + wl_list_append(&rc.keybinds, &keybind->link); } } if (!keybind) { @@ -1590,7 +1592,7 @@ load_default_key_bindings(void) if (!k) { continue; } - + wl_list_append(&rc.keybinds, &k->link); action = action_create(current->action); wl_list_append(&k->actions, &action->link);