From 8ad0c0166bc5ee72ca469ec09d1b7d438a1c3c38 Mon Sep 17 00:00:00 2001 From: Daniel De Graaf Date: Tue, 23 Jan 2024 19:30:33 -0500 Subject: [PATCH] seat/keyboard: support sending modifiers when not focused This adds wlr_seat_client_notify_modifiers which sends the current keyboard modifiers to an additional client, tracking what the client last saw to avoid unneeded duplicates. This is useful to make actions like "ctrl+scroll" work correctly when pointer and keyboard focus are on distinct clients. The tracked modifier state is also used to avoid sending unneeded modifiers events in default_keyboard_modifiers. No de-duplication is performed in wlr_seat_keyboard_send_modifiers because that function is used in cases where the protocol unconditionally requires a modifiers event to be sent (such as after wl_keyboard_enter). --- include/wlr/types/wlr_seat.h | 15 ++++++++ types/seat/wlr_seat_keyboard.c | 62 +++++++++++++++++++++++++++------- 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/include/wlr/types/wlr_seat.h b/include/wlr/types/wlr_seat.h index 74b4341dd..03c8c2581 100644 --- a/include/wlr/types/wlr_seat.h +++ b/include/wlr/types/wlr_seat.h @@ -55,6 +55,13 @@ struct wlr_seat_client { struct wlr_serial_ringset serials; bool needs_touch_frame; + // The client's current knowledge of the keyboard modifier state. This + // will lag behind the actual state if the client does not have + // keyboard focus; it is used by wlr_seat_client_notify_modifiers to + // allow actions like ctrl+scroll to work without sending duplicate + // events if modifiers are unchanged. + struct wlr_keyboard_modifiers tracked_modifiers; + // When the client doesn't support high-resolution scroll, accumulate deltas // until we can notify a discrete event. // Some mice have a free spinning wheel, making possible to lock the wheel @@ -526,6 +533,14 @@ void wlr_seat_keyboard_send_key(struct wlr_seat *seat, uint32_t time_msec, void wlr_seat_keyboard_send_modifiers(struct wlr_seat *seat, const struct wlr_keyboard_modifiers *modifiers); +/** + * Update an additional client's view of the keyboard modifier state. This + * does nothing if the target has keyboard focus or if its view of the modifier + * state still matches the current modifier state. + */ +void wlr_seat_client_notify_modifiers(struct wlr_seat *seat, + struct wlr_seat_client *target); + /** * Send a keyboard enter event to the given surface and consider it to be the * focused surface for the keyboard. This will send a leave event to the last diff --git a/types/seat/wlr_seat_keyboard.c b/types/seat/wlr_seat_keyboard.c index 3227035e2..09028e7e1 100644 --- a/types/seat/wlr_seat_keyboard.c +++ b/types/seat/wlr_seat_keyboard.c @@ -10,6 +10,11 @@ #include "types/wlr_data_device.h" #include "types/wlr_seat.h" +static void seat_keyboard_send_modifiers(struct wlr_seat *seat, + const struct wlr_keyboard_modifiers *modifiers, + struct wlr_seat_client *client, + bool force); + static void default_keyboard_enter(struct wlr_seat_keyboard_grab *grab, struct wlr_surface *surface, const uint32_t keycodes[], size_t num_keycodes, const struct wlr_keyboard_modifiers *modifiers) { @@ -27,7 +32,7 @@ static void default_keyboard_key(struct wlr_seat_keyboard_grab *grab, static void default_keyboard_modifiers(struct wlr_seat_keyboard_grab *grab, const struct wlr_keyboard_modifiers *modifiers) { - wlr_seat_keyboard_send_modifiers(grab->seat, modifiers); + seat_keyboard_send_modifiers(grab->seat, modifiers, NULL, false); } static void default_keyboard_cancel(struct wlr_seat_keyboard_grab *grab) { @@ -195,34 +200,67 @@ static void seat_keyboard_handle_surface_destroy(struct wl_listener *listener, wlr_seat_keyboard_clear_focus(state->seat); } -void wlr_seat_keyboard_send_modifiers(struct wlr_seat *seat, - const struct wlr_keyboard_modifiers *modifiers) { - struct wlr_seat_client *client = seat->keyboard_state.focused_client; +static void seat_keyboard_send_modifiers(struct wlr_seat *seat, + const struct wlr_keyboard_modifiers *modifiers, + struct wlr_seat_client *client, + bool force) { + const struct wlr_keyboard_modifiers empty = {}; + struct wl_resource *resource; + if (client == NULL) { + client = seat->keyboard_state.focused_client; + } + if (client == NULL) { + return; + } + if (modifiers == NULL) { + modifiers = ∅ + } + + struct wlr_keyboard_modifiers *tracked = &client->tracked_modifiers; + if (!force && tracked->depressed == modifiers->depressed && + tracked->latched == modifiers->latched && + tracked->locked == modifiers->locked && + tracked->group == modifiers->group) { return; } uint32_t serial = wlr_seat_client_next_serial(client); - struct wl_resource *resource; + *tracked = *modifiers; + wl_resource_for_each(resource, &client->keyboards) { if (seat_client_from_keyboard_resource(resource) == NULL) { continue; } - if (modifiers == NULL) { - wl_keyboard_send_modifiers(resource, serial, 0, 0, 0, 0); - } else { - wl_keyboard_send_modifiers(resource, serial, - modifiers->depressed, modifiers->latched, - modifiers->locked, modifiers->group); - } + wl_keyboard_send_modifiers(resource, serial, + modifiers->depressed, modifiers->latched, + modifiers->locked, modifiers->group); } } +void wlr_seat_keyboard_send_modifiers(struct wlr_seat *seat, + const struct wlr_keyboard_modifiers *modifiers) { + seat_keyboard_send_modifiers(seat, modifiers, NULL, true); +} + +void wlr_seat_client_notify_modifiers(struct wlr_seat *seat, + struct wlr_seat_client *target) { + struct wlr_seat_client *focused = seat->keyboard_state.focused_client; + if (target == NULL || target == focused) { + return; + } + + struct wlr_keyboard_modifiers *modifiers = &seat->keyboard_state.keyboard->modifiers; + seat_keyboard_send_modifiers(seat, modifiers, target, false); +} + void seat_client_send_keyboard_leave_raw(struct wlr_seat_client *seat_client, struct wlr_surface *surface) { uint32_t serial = wlr_seat_client_next_serial(seat_client); struct wl_resource *resource; + const struct wlr_keyboard_modifiers unset = {}; + seat_client->tracked_modifiers = unset; wl_resource_for_each(resource, &seat_client->keyboards) { if (seat_client_from_keyboard_resource(resource) == NULL) { continue;