// SPDX-License-Identifier: GPL-2.0-only #include #include #include "action.h" #include "buffer.h" #include "key-state.h" #include "labwc.h" #include "workspaces.h" static bool should_cancel_cycling_on_next_key_release; static void change_vt(struct server *server, unsigned int vt) { if (!wlr_backend_is_multi(server->backend)) { return; } struct wlr_session *session = wlr_backend_get_session(server->backend); if (session) { wlr_session_change_vt(session, vt); } } bool keyboard_any_modifiers_pressed(struct wlr_keyboard *keyboard) { xkb_mod_index_t i; for (i = 0; i < xkb_keymap_num_mods(keyboard->keymap); i++) { if (xkb_state_mod_index_is_active (keyboard->xkb_state, i, XKB_STATE_MODS_DEPRESSED)) { return true; } } return false; } static void end_cycling(struct server *server) { desktop_focus_and_activate_view(&server->seat, server->osd_state.cycle_view); desktop_move_to_front(server->osd_state.cycle_view); /* osd_finish() additionally resets cycle_view to NULL */ osd_finish(server); should_cancel_cycling_on_next_key_release = false; } static void keyboard_modifiers_notify(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, keyboard_modifiers); struct server *server = seat->server; if (server->osd_state.cycle_view || seat->workspace_osd_shown_by_modifier) { struct wlr_keyboard_key_event *event = data; struct wlr_keyboard *keyboard = &seat->keyboard_group->keyboard; if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED && !keyboard_any_modifiers_pressed(keyboard)) { if (server->osd_state.cycle_view) { if (key_state_nr_keys()) { should_cancel_cycling_on_next_key_release = true; } else { end_cycling(server); } } if (seat->workspace_osd_shown_by_modifier) { workspaces_osd_hide(seat); } } } wlr_seat_keyboard_notify_modifiers(seat->seat, &seat->keyboard_group->keyboard.modifiers); } static bool handle_keybinding(struct server *server, uint32_t modifiers, xkb_keysym_t sym) { struct keybind *keybind; wl_list_for_each_reverse (keybind, &rc.keybinds, link) { if (modifiers ^ keybind->modifiers) { continue; } for (size_t i = 0; i < keybind->keysyms_len; i++) { if (xkb_keysym_to_lower(sym) == keybind->keysyms[i]) { actions_run(NULL, server, &keybind->actions, 0); return true; } } } return false; } static bool is_modifier_key(xkb_keysym_t sym) { return sym == XKB_KEY_Shift_L || sym == XKB_KEY_Shift_R || sym == XKB_KEY_Alt_L || sym == XKB_KEY_Alt_R || sym == XKB_KEY_Control_L || sym == XKB_KEY_Control_R || sym == XKB_KEY_Super_L || sym == XKB_KEY_Super_R; } static bool handle_compositor_keybindings(struct wl_listener *listener, struct wlr_keyboard_key_event *event) { struct seat *seat = wl_container_of(listener, seat, keyboard_key); struct server *server = seat->server; struct wlr_keyboard *keyboard = &seat->keyboard_group->keyboard; /* Translate libinput keycode -> xkbcommon */ uint32_t keycode = event->keycode + 8; /* Get a list of keysyms based on the keymap for this keyboard */ const xkb_keysym_t *syms; int nsyms = xkb_state_key_get_syms(keyboard->xkb_state, keycode, &syms); bool handled = false; key_state_set_pressed(keycode, event->state == WL_KEYBOARD_KEY_STATE_PRESSED); /* * Ignore labwc keybindings if input is inhibited * It's important to do this after key_state_set_pressed() to ensure * _all_ key press/releases are registered */ if (seat->active_client_while_inhibited) { return false; } /* * If a user lets go of the modifier (e.g. alt) before the 'normal' key * (e.g. tab) when window-cycling, we do not end the cycling until both * keys have been released. If we end the window-cycling on release of * the modifier only, some XWayland clients such as hexchat realise that * tab is pressed (even though we did not forward the event) and because * we absorb the equivalent release event it gets stuck on repeat. */ if (should_cancel_cycling_on_next_key_release && event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { end_cycling(server); handled = true; goto out; } /* * If a press event was handled by a compositor binding, then do not * forward the corresponding release event to clients */ if (key_state_corresponding_press_event_was_bound(keycode) && event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { key_state_bound_key_remove(keycode); return true; } uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard); /* Catch C-A-F1 to C-A-F12 to change tty */ if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { for (int i = 0; i < nsyms; i++) { unsigned int vt = syms[i] - XKB_KEY_XF86Switch_VT_1 + 1; if (vt >= 1 && vt <= 12) { change_vt(server, vt); /* * Don't send any key events to clients when * changing tty */ handled = true; goto out; } } } if (server->osd_state.cycle_view) { if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { for (int i = 0; i < nsyms; i++) { if (syms[i] == XKB_KEY_Escape) { /* * Cancel view-cycle * * osd_finish() additionally resets * cycle_view to NULL */ osd_preview_restore(server); osd_finish(server); handled = true; goto out; } } /* cycle to next */ bool backwards = modifiers & WLR_MODIFIER_SHIFT; /* ignore if this is a modifier key being pressed */ bool ignore = false; for (int i = 0; i < nsyms; i++) { ignore |= is_modifier_key(syms[i]); } if (!ignore) { enum lab_cycle_dir dir = backwards ? LAB_CYCLE_DIR_BACKWARD : LAB_CYCLE_DIR_FORWARD; server->osd_state.cycle_view = desktop_cycle_view(server, server->osd_state.cycle_view, dir); osd_update(server); } } /* don't send any key events to clients when osd onscreen */ handled = true; goto out; } /* Handle compositor key bindings */ if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { for (int i = 0; i < nsyms; i++) { handled |= handle_keybinding(server, modifiers, syms[i]); } } out: if (handled) { key_state_store_pressed_keys_as_bound(); } return handled; } static void keyboard_key_notify(struct wl_listener *listener, void *data) { /* This event is raised when a key is pressed or released. */ struct seat *seat = wl_container_of(listener, seat, keyboard_key); struct server *server = seat->server; struct wlr_keyboard_key_event *event = data; struct wlr_seat *wlr_seat = server->seat.seat; struct wlr_keyboard *keyboard = &seat->keyboard_group->keyboard; wlr_idle_notify_activity(seat->wlr_idle, seat->seat); bool handled = handle_compositor_keybindings(listener, event); if (!handled) { wlr_seat_set_keyboard(wlr_seat, keyboard); wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec, event->keycode, event->state); } } void keyboard_init(struct seat *seat) { seat->keyboard_group = wlr_keyboard_group_create(); struct wlr_keyboard *kb = &seat->keyboard_group->keyboard; struct xkb_rule_names rules = { 0 }; struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); struct xkb_keymap *keymap = xkb_map_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); if (keymap) { wlr_keyboard_set_keymap(kb, keymap); xkb_keymap_unref(keymap); } else { wlr_log(WLR_ERROR, "Failed to create xkb keymap"); } xkb_context_unref(context); wlr_keyboard_set_repeat_info(kb, rc.repeat_rate, rc.repeat_delay); seat->keyboard_key.notify = keyboard_key_notify; wl_signal_add(&kb->events.key, &seat->keyboard_key); seat->keyboard_modifiers.notify = keyboard_modifiers_notify; wl_signal_add(&kb->events.modifiers, &seat->keyboard_modifiers); } void keyboard_finish(struct seat *seat) { if (seat->keyboard_group) { /* * Caution - these event listeners are connected to * seat->keyboard_group->keyboard and must be * unregistered before wlr_keyboard_group_destroy(), * otherwise a use-after-free occurs. */ wl_list_remove(&seat->keyboard_key.link); wl_list_remove(&seat->keyboard_modifiers.link); wlr_keyboard_group_destroy(seat->keyboard_group); seat->keyboard_group = NULL; } }