diff --git a/waylib/src/server/protocols/wxwayland.cpp b/waylib/src/server/protocols/wxwayland.cpp index 9bf800f5b..3a881b47b 100644 --- a/waylib/src/server/protocols/wxwayland.cpp +++ b/waylib/src/server/protocols/wxwayland.cpp @@ -20,6 +20,9 @@ #include #include +// For wl_keyboard_send_modifiers +#include + #include #include @@ -59,6 +62,15 @@ class Q_DECL_HIDDEN WXWaylandPrivate : public WWaylandResourcePrivate QMetaObject::Connection cursorPositionChangedConnection; + // Keyboard modifiers sync to the Xwayland server. + void syncModifiersToXWayland(); + void watchSeatKeyboard(); + void on_keyboard_device_destroy(); + + WScopedListener keyboardModifiersListener; + WScopedListener keyboardDeviceDestroyListener; + QMetaObject::Connection seatKeyboardChangedConnection; + // Async property reading struct PerWindowProps { @@ -485,6 +497,89 @@ void WXWaylandPrivate::watchSeatCursor() } } +void WXWaylandPrivate::syncModifiersToXWayland() +{ + W_Q(WXWayland); + + auto *xwayland = q->handle(); + if (!xwayland || !xwayland->server || !xwayland->server->client || !xwayland->seat) + return; + + auto *seat = xwayland->seat; + auto *keyboard = wlr_seat_get_keyboard(seat); + if (!keyboard) + return; + + // When Xwayland itself holds the keyboard focus, the seat already + // delivers the modifiers to it through the normal path. + auto *focusedClient = seat->keyboard_state.focused_client; + if (focusedClient && focusedClient->client == xwayland->server->client) + return; + + auto *seatClient = wlr_seat_client_for_wl_client(seat, xwayland->server->client); + if (!seatClient || wl_list_empty(&seatClient->keyboards)) + return; + + // The Xwayland server applies every wl_keyboard.modifiers event to its + // XKB state (keyboard_handle_modifiers in xwayland-input.c) even when it + // doesn't hold the keyboard focus, so push the current state explicitly + // to keep the state seen by X11 APIs (e.g. XkbGetState) fresh. + const auto &mods = keyboard->modifiers; + uint32_t serial = wlr_seat_client_next_serial(seatClient); + wl_resource *resource; + wl_resource_for_each(resource, &seatClient->keyboards) { + wl_keyboard_send_modifiers(resource, serial, + mods.depressed, mods.latched, + mods.locked, mods.group); + } +} + +void WXWaylandPrivate::watchSeatKeyboard() +{ + W_Q(WXWayland); + + keyboardModifiersListener.disconnect(); + keyboardDeviceDestroyListener.disconnect(); + + // setSeat() may switch to another seat (or clear it), so rebind the + // keyboardChanged connection to the current seat on every call. + if (seatKeyboardChangedConnection) { + QObject::disconnect(seatKeyboardChangedConnection); + seatKeyboardChangedConnection = {}; + } + + auto *xwayland = q->handle(); + auto *seat = xwayland ? xwayland->seat : nullptr; + if (!seat) + return; + + // The seat keyboard can be replaced (e.g. by the input method's virtual + // keyboard) or destroyed at any time, so re-attach on changes. + if (auto *seatObject = WSeat::fromHandle(seat)) { + seatKeyboardChangedConnection = QObject::connect(seatObject, + &WSeat::keyboardChanged, + q, [this] { + watchSeatKeyboard(); + syncModifiersToXWayland(); + }); + } + + if (auto *keyboard = wlr_seat_get_keyboard(seat)) { + keyboardModifiersListener.init(&keyboard->events.modifiers, + this, &WXWaylandPrivate::syncModifiersToXWayland); + keyboardDeviceDestroyListener.init(&keyboard->base.events.destroy, + this, &WXWaylandPrivate::on_keyboard_device_destroy); + } +} + +void WXWaylandPrivate::on_keyboard_device_destroy() +{ + // The keyboard is going away; detach our listeners from it before the + // wlr_keyboard memory is freed. + keyboardModifiersListener.disconnect(); + keyboardDeviceDestroyListener.disconnect(); +} + void WXWayland::setSeat(WSeat *seat) { W_D(WXWayland); @@ -494,6 +589,8 @@ void WXWayland::setSeat(WSeat *seat) d->watchSeatCursor(); d->syncCursorPositionToXWayland(); + d->watchSeatKeyboard(); + d->syncModifiersToXWayland(); } WSeat *WXWayland::seat() const @@ -612,9 +709,11 @@ void WXWayland::create(WServer *server) listeners()->add(&handle->events.ready, this, [this, d] (void *) { d->init(); // The seat may be set before the Xwayland server starts; sync the - // current cursor position once the server client is available. + // current cursor position and keyboard state once the server client + // is available. d->watchSeatCursor(); d->syncCursorPositionToXWayland(); + d->syncModifiersToXWayland(); Q_EMIT ready(); });