Skip to content

WIP: Virtualinput pointer and keyboard #778

Draft
zorowk wants to merge 3 commits intolinuxdeepin:masterfrom
zorowk:virtualinput
Draft

WIP: Virtualinput pointer and keyboard #778
zorowk wants to merge 3 commits intolinuxdeepin:masterfrom
zorowk:virtualinput

Conversation

@zorowk
Copy link
Copy Markdown
Contributor

@zorowk zorowk commented Mar 10, 2026

  • add single-pixel-buffer-v1 protocol support
  • add WVirtualInputHelper for virtual keyboard and pointer

These protocols are required for running the wl-find-cursor client,
which is used in automated testing to simulate input events.

Summary by Sourcery

Add support for virtual input devices and single-pixel buffer protocol, and integrate centralized input device initialization and configuration management.

New Features:

  • Introduce WVirtualInputHelper to manage virtual keyboard and pointer devices and attach them to the compositor seat.
  • Add support for the zwlr_virtual_pointer_manager_v1 protocol via WVirtualPointerManagerV1 and its private implementation.
  • Enable the single-pixel-buffer-v1 Wayland protocol on the compositor.

Enhancements:

  • Refactor input device initialization into generic init/deinit hooks with per-device configuration wiring, including pointer acceleration profile updates driven by user config.
  • Simplify WInputMethodHelper by removing direct virtual keyboard management and relying on core virtual input handling.
  • Wire virtual input helper usage into the main shell handler and tinywl example so virtual input is available across environments.

Build:

  • Register new virtual input helper and virtual pointer protocol sources and headers in the server build configuration.

@deepin-ci-robot
Copy link
Copy Markdown

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: zorowk

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Mar 10, 2026

Reviewer's Guide

Adds a dedicated WVirtualInputHelper and WVirtualPointerManagerV1 to manage virtual keyboard and pointer devices, wires them into the compositor and tinywl shell, enables the single-pixel-buffer-v1 protocol, and refactors input device and input method handling to better support virtual input and per-device configuration.

Sequence diagram for virtual pointer creation and attachment

sequenceDiagram
    actor Client
    participant WServer
    participant WVirtualPointerManagerV1
    participant WVirtualInputHelper
    participant WInputDevice
    participant WSeat
    participant WCursor
    participant WOutput

    Client->>WServer: bind zwlr_virtual_pointer_manager_v1
    Client->>WVirtualPointerManagerV1: create_virtual_pointer(suggested_seat, suggested_output)
    WVirtualPointerManagerV1-->>WVirtualInputHelper: newVirtualPointer(event)

    activate WVirtualInputHelper
    WVirtualInputHelper->>WVirtualInputHelper: shouldAcceptSeat(event.suggested_seat)
    alt seat accepted
        WVirtualInputHelper->>WInputDevice: ensureDevice(qw_input_device::from(&event.new_pointer.pointer.base))
        WVirtualInputHelper->>WVirtualInputHelper: attachDevice(device)
        WVirtualInputHelper->>WSeat: attachInputDevice(device)
        WVirtualInputHelper->>WVirtualInputHelper: maybeMapToOutput(device, event.suggested_output)
        WVirtualInputHelper->>WCursor: cursor = seat.cursor()
        WVirtualInputHelper->>WOutput: WOutput::fromHandle(qw_output::from(event.suggested_output))
        WVirtualInputHelper->>WCursor: handle.map_input_to_output(device.handle, event.suggested_output)
    else seat rejected
        WVirtualInputHelper-->>WVirtualPointerManagerV1: ignore event
    end
    deactivate WVirtualInputHelper
Loading

Class diagram for updated InputDevice and per-device config handling

classDiagram
    class InputDevice {
        -static InputDevice *m_instance
        -unique_ptr~GestureRecognizer~ m_touchpadRecognizer
        -QHash~WInputDevice*, QVector~QMetaObject::Connection~~ m_deviceConnections
        -uint m_touchpadFingerCount
        +static InputDevice *instance()
        +void initDevice(WInputDevice *device)
        +void deinitDevice(WInputDevice *device)
        +SwipeGesture* registerTouchpadSwipe(SwipeFeedBack feed_back)
        +HoldGesture* registerTouchpadHold(HoldFeedBack feed)
    }

    class Helper {
        +void init(Treeland::Treeland *treeland)
    }

    class WBackend {
        +inputAdded(WInputDevice *device)
        +inputRemoved(WInputDevice *device)
    }

    class WSeat {
        +void attachInputDevice(WInputDevice *device)
        +void detachInputDevice(WInputDevice *device)
    }

    class TreelandUserConfig {
        +int inputAccelProfile()
        +void inputAccelProfileChanged(int profile)
        +void configInitializeSucceed()
    }

    class WInputDevice {
        +qw_input_device* handle()
        +QInputDevice* qtDevice()
    }

    InputDevice ..> WInputDevice : manages
    InputDevice ..> TreelandUserConfig : reads accel profile
    Helper ..> WBackend : uses
    Helper ..> WSeat : uses
    Helper ..> InputDevice : uses
    WBackend --> Helper : emits inputAdded
    WBackend --> Helper : emits inputRemoved
    Helper --> WSeat : attachInputDevice
    Helper --> WSeat : detachInputDevice
    Helper --> InputDevice : initDevice
    Helper --> InputDevice : deinitDevice
    TreelandUserConfig <.. InputDevice : signal connections
    WInputDevice <.. InputDevice : initDevice/deinitDevice
Loading

Class diagram for new WVirtualInputHelper and WVirtualPointerManagerV1

classDiagram
    class WVirtualInputHelper {
        +WVirtualInputHelper(WServer *server, WSeat *seat)
        +~WVirtualInputHelper()
        -void handleNewVKV1(wlr_virtual_keyboard_v1 *virtualKeyboard)
        -void handleNewVPV1(wlr_virtual_pointer_v1_new_pointer_event *event)
        -bool shouldAcceptSeat(wlr_seat *suggestedSeat) const
        -WInputDevice *ensureDevice(qw_input_device *handle) const
        -void attachDevice(WInputDevice *device)
        -void maybeMapToOutput(WInputDevice *device, wlr_output *output) const
    }

    class WVirtualInputHelperPrivate {
        +QPointer~WServer~ server
        +QPointer~WSeat~ seat
        +QPointer~WVirtualKeyboardManagerV1~ virtualKeyboardManagerV1
        +QPointer~WVirtualPointerManagerV1~ virtualPointerManagerV1
        +QList~WInputDevice*~ virtualKeyboards
        +QList~WInputDevice*~ virtualPointers
    }

    class WVirtualKeyboardManagerV1 {
        +void newVirtualKeyboard(wlr_virtual_keyboard_v1 *virtualKeyboard)
    }

    class WVirtualPointerManagerV1 {
        +WVirtualPointerManagerV1(QObject *parent)
        +QByteArrayView interfaceName() const
        +void newVirtualPointer(wlr_virtual_pointer_v1_new_pointer_event *event)
        -void create(WServer *server)
        -wl_global *global() const
    }

    class WServer {
        +qw_server* handle()
        +T* attach~T~()
    }

    class WSeat {
        +void attachInputDevice(WInputDevice *device)
        +WCursor* cursor()
        +wlr_seat* nativeHandle()
    }

    class WCursor {
        +qw_cursor* handle()
    }

    class WInputDevice {
        +static WInputDevice* fromHandle(qw_input_device *handle)
        +qw_input_device* handle()
        +WSeat* seat()
    }

    class WOutput {
        +static WOutput* fromHandle(qw_output *handle)
    }

    WVirtualInputHelper o-- WVirtualInputHelperPrivate : d_ptr
    WVirtualInputHelperPrivate ..> WServer : holds
    WVirtualInputHelperPrivate ..> WSeat : holds
    WVirtualInputHelperPrivate ..> WVirtualKeyboardManagerV1 : manages
    WVirtualInputHelperPrivate ..> WVirtualPointerManagerV1 : manages
    WVirtualInputHelper ..> WInputDevice : creates and attaches
    WVirtualInputHelper ..> WOutput : maybeMapToOutput
    WVirtualInputHelper ..> WCursor : via seat cursor

    WVirtualPointerManagerV1 ..|> WServerInterface
    WVirtualPointerManagerV1 ..|> WObject
    WVirtualPointerManagerV1 ..|> QObject

    WServer ..> WVirtualKeyboardManagerV1 : attach
    WServer ..> WVirtualPointerManagerV1 : attach

    WSeat <.. WVirtualInputHelper : attachInputDevice
    WVirtualKeyboardManagerV1 --> WVirtualInputHelper : newVirtualKeyboard
    WVirtualPointerManagerV1 --> WVirtualInputHelper : newVirtualPointer
Loading

Class diagram for updated WInputMethodHelper keyboard grab logic

classDiagram
    class WInputMethodHelper {
        +WInputMethodHelper(WServer *server, WSeat *seat)
        +~WInputMethodHelper()
        +WTextInput* focusedTextInput() const
        +WInputMethodV2* activeInputMethod() const
        +qw_input_method_keyboard_grab_v2* activeKeyboardGrab() const
        -void handleNewTI(WTextInput *ti)
        -void handleNewIMV2(qw_input_method_v2 *imv2)
        -void handleNewKGV2(qw_input_method_keyboard_grab_v2 *kgv2)
        -void handleNewIPSV2(qw_input_popup_surface_v2 *ipsv2)
        -void updateAllPopupSurfaces(QRect cursorRect)
        -void updatePopupSurface(WInputPopupSurface *popup, QRect cursorRect)
        -void notifyLeave()
    }

    class WInputMethodHelperPrivate {
        +QPointer~WServer~ server
        +QPointer~WSeat~ seat
        +QPointer~WInputMethodManagerV2~ inputMethodManagerV2
        +QPointer~WTextInputManagerV1~ textInputManagerV1
        +QPointer~WTextInputManagerV2~ textInputManagerV2
        +QPointer~WTextInputManagerV3~ textInputManagerV3
        +WTextInput* enabledTextInput
        +WInputMethodV2* activeInputMethod
        +qw_input_method_keyboard_grab_v2* activeKeyboardGrab
        +wlr_seat_keyboard_grab keyboardGrab
        +wlr_seat_keyboard_grab_interface grabInterface
        +GrabHandlerArg handlerArg
        +QList~WTextInput*~ textInputs
        +QList~WInputPopupSurface*~ popupSurfaces
    }

    class GrabHandlerArg {
        +qw_input_method_keyboard_grab_v2 *grab
    }

    class WSeat {
        +wlr_seat* nativeHandle()
        +wlr_seat_keyboard_state keyboard_state
    }

    class qw_input_method_keyboard_grab_v2 {
        +void set_keyboard(wlr_keyboard *keyboard)
        +void send_key(uint32_t time_msec, int key, uint32_t state)
        +void send_modifiers(wlr_keyboard_modifiers *modifiers)
    }

    WInputMethodHelper o-- WInputMethodHelperPrivate : d_ptr
    WInputMethodHelperPrivate ..> GrabHandlerArg : uses
    WInputMethodHelperPrivate ..> WSeat : holds
    WInputMethodHelperPrivate ..> WInputMethodManagerV2 : manages
    WInputMethodHelperPrivate ..> WTextInputManagerV1 : manages
    WInputMethodHelperPrivate ..> WTextInputManagerV2 : manages
    WInputMethodHelperPrivate ..> WTextInputManagerV3 : manages

    GrabHandlerArg ..> qw_input_method_keyboard_grab_v2 : grab

    class wlr_seat_keyboard_grab {
        +void* data
        +wlr_seat* seat
    }

    class wlr_keyboard_modifiers

    wlr_seat_keyboard_grab --> GrabHandlerArg : data cast
    WSeat ..> wlr_seat_keyboard_grab : keyboardGrab hooks
Loading

File-Level Changes

Change Details Files
Refactor input device initialization to support generic device setup and per-device configuration, with cleanup on removal.
  • Replace initTouchPad with initDevice/deinitDevice to handle generic libinput-backed devices.
  • Enable tap-to-click for touchpads and register for accel profile configuration changes for mice and touchpads via TreelandUserConfig.
  • Track per-WInputDevice signal connections in a QHash and disconnect them when devices are deinitialized.
  • Wire backend inputAdded/inputRemoved to the new initDevice/deinitDevice functions.
src/input/inputdevice.cpp
src/input/inputdevice.h
src/seat/helper.cpp
Simplify WInputMethodHelper virtual keyboard handling by relying on wlroots virtual keyboard detection instead of tracking devices explicitly.
  • Remove explicit WVirtualKeyboardManagerV1 and virtualKeyboards list from WInputMethodHelper.
  • Update keyboard grab handlers to forward events when the current seat keyboard is a wlroots virtual keyboard, without consulting tracked device lists.
  • Adjust setKeyboard logic to use raw wlr_input_device handles from WInputDevice::handle()->handle().
  • Drop the handleNewVKV1 path and related header declarations and includes.
waylib/src/server/protocols/winputmethodhelper.cpp
waylib/src/server/protocols/winputmethodhelper.h
Introduce WVirtualInputHelper and WVirtualPointerManagerV1 to manage virtual keyboard and pointer devices and attach them to the seat.
  • Add WVirtualInputHelper class that listens for new virtual keyboard and virtual pointer events, wraps them in WInputDevice, and attaches them to the seat if appropriate.
  • Track created virtual keyboard and pointer devices, detaching and deleting them when their qw_input_device is destroyed.
  • Implement output mapping for virtual pointers based on suggested_output using the seat cursor and WOutput lookup.
  • Add WVirtualPointerManagerV1 as a WServerInterface that creates the zwlr_virtual_pointer_manager_v1 global and emits newVirtualPointer signals.
  • Wire WVirtualInputHelper into ShellHandler and the tinywl example helper so both environments support virtual input.
waylib/src/server/protocols/wvirtualinputhelper.cpp
waylib/src/server/protocols/wvirtualinputhelper.h
waylib/src/server/protocols/private/wvirtualpointerv1.cpp
waylib/src/server/protocols/private/wvirtualpointerv1_p.h
waylib/src/server/CMakeLists.txt
waylib/examples/tinywl/helper.cpp
waylib/examples/tinywl/helper.h
waylib/src/server/protocols/WVirtualInputHelper
src/core/shellhandler.cpp
src/core/shellhandler.h
Enable the single-pixel-buffer-v1 protocol in the compositor initialization path.
  • Include qwsinglepixelbufferv1 header in core helper.
  • Create a qw_single_pixel_buffer_manager_v1 instance during server/compositor setup.
src/seat/helper.cpp
Minor metadata and copyright updates.
  • Update copyright years in touched files to 2024-2026 or 2026 as appropriate.
  • Add (or keep) JSON dconfig stub file unchanged in diff context.
src/input/inputdevice.cpp
src/input/inputdevice.h
waylib/examples/tinywl/helper.cpp
waylib/examples/tinywl/helper.h

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@zorowk zorowk force-pushed the virtualinput branch 2 times, most recently from 80320ce to 9d04804 Compare March 17, 2026 07:20
@zorowk zorowk marked this pull request as ready for review March 19, 2026 02:44
Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In InputDevice::initDevice the lambda connected to TreelandUserConfig signals captures a raw WInputDevice* without any disconnection or lifetime coordination, which risks calling libinput_device_handle on a freed device; consider tying the connection to the device’s lifetime (e.g., using the device as the receiver or disconnecting on device destruction).
  • WVirtualInputHelper currently only filters virtual pointers by seat (shouldAcceptSeat) but not virtual keyboards; if multiple seats are ever used, you may want to apply the same seat check for virtual keyboards to avoid attaching keyboards to unintended seats.
  • When WVirtualInputHelper is destroyed, attached virtual input devices remain on the seat and are only cleaned up via before_destroy, which may outlive the helper; consider whether you should explicitly detach or otherwise clean up virtualKeyboards/virtualPointers when the helper goes away to avoid dangling connections or unexpected behavior.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In InputDevice::initDevice the lambda connected to TreelandUserConfig signals captures a raw WInputDevice* without any disconnection or lifetime coordination, which risks calling libinput_device_handle on a freed device; consider tying the connection to the device’s lifetime (e.g., using the device as the receiver or disconnecting on device destruction).
- WVirtualInputHelper currently only filters virtual pointers by seat (shouldAcceptSeat) but not virtual keyboards; if multiple seats are ever used, you may want to apply the same seat check for virtual keyboards to avoid attaching keyboards to unintended seats.
- When WVirtualInputHelper is destroyed, attached virtual input devices remain on the seat and are only cleaned up via before_destroy, which may outlive the helper; consider whether you should explicitly detach or otherwise clean up virtualKeyboards/virtualPointers when the helper goes away to avoid dangling connections or unexpected behavior.

## Individual Comments

### Comment 1
<location path="src/input/inputdevice.cpp" line_range="368-377" />
<code_context>
-    return false;
+
+    auto config = Helper::instance()->config();
+    auto applyAccelProfile = [device, config]() {
+        auto deviceType = device->qtDevice()->type();
+        if (deviceType == QInputDevice::DeviceType::Mouse ||
+            deviceType == QInputDevice::DeviceType::TouchPad) {
+            configAccelProfile(libinput_device_handle(device->handle()),
+                               static_cast<libinput_config_accel_profile>(config->inputAccelProfile()));
+        }
+    };
+
+    connect(config, &TreelandUserConfig::inputAccelProfileChanged, this, applyAccelProfile);
+    connect(config, &TreelandUserConfig::configInitializeSucceed, this, applyAccelProfile);
 }

</code_context>
<issue_to_address>
**issue (bug_risk):** Lifetime of `device` in the accel-profile lambda can outlive the actual input device

Because the `TreelandUserConfig` connection uses the global `InputDevice` singleton as the receiver and captures `device` by value, the lambda can be invoked after the physical device is destroyed, leaving `device` dangling and causing UB/crashes on later `inputAccelProfileChanged` / `configInitializeSucceed` signals.

Please update this so the connection is scoped to the device’s lifetime (e.g., use `device` as the receiver/context), or explicitly disconnect it in the device teardown path. Alternatively, capture only a safe/validated handle (e.g., `libinput_device*` or a weak/guarded reference) and check it before use. Note that with multiple devices this creates multiple long‑lived connections on the singleton, which worsens the issue.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@zorowk zorowk changed the title WIP: Virtualinput pointer and keyboard WIP: Virtualinput pointer and keyboard Mar 19, 2026
@zorowk zorowk force-pushed the virtualinput branch 2 times, most recently from 454be58 to 1b83b38 Compare March 19, 2026 05:21
@zorowk zorowk requested review from wineee and zccrs and removed request for wineee and zccrs March 19, 2026 05:32
@zorowk zorowk requested review from wineee and zccrs March 19, 2026 06:31
@zorowk zorowk marked this pull request as draft March 19, 2026 06:35
@zorowk zorowk force-pushed the virtualinput branch 2 times, most recently from f1da01c to b04f671 Compare March 19, 2026 07:34
@zorowk zorowk marked this pull request as ready for review March 19, 2026 11:22
Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 5 issues, and left some high level feedback:

  • In WVirtualInputHelper::maybeMapToOutput(), the WOutput::fromHandle(qw_output::from(output)) result is only used for its side‑effect as a validity check; consider making that explicit (e.g. via Q_ASSERT or a named local) so the intent is clearer and future refactors don’t accidentally remove it as “unused.”
  • WVirtualPointerManagerV1 currently ignores the QObject *parent passed to its constructor and doesn’t forward it to QObject; if you intend to use QObject’s lifetime/parenting here, call QObject(parent) in the initializer list or drop the parent parameter to avoid confusion.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In WVirtualInputHelper::maybeMapToOutput(), the WOutput::fromHandle(qw_output::from(output)) result is only used for its side‑effect as a validity check; consider making that explicit (e.g. via Q_ASSERT or a named local) so the intent is clearer and future refactors don’t accidentally remove it as “unused.”
- WVirtualPointerManagerV1 currently ignores the QObject *parent passed to its constructor and doesn’t forward it to QObject; if you intend to use QObject’s lifetime/parenting here, call QObject(parent) in the initializer list or drop the parent parameter to avoid confusion.

## Individual Comments

### Comment 1
<location path="src/input/inputdevice.cpp" line_range="365-374" />
<code_context>
+        deviceType == QInputDevice::DeviceType::Mouse) {
+        auto config = Helper::instance()->config();
+        auto applyAccelProfile = [device, config]() {
+            auto deviceType = device->qtDevice()->type();
+            if (deviceType == QInputDevice::DeviceType::Mouse ||
+                deviceType == QInputDevice::DeviceType::TouchPad) {
+                configAccelProfile(libinput_device_handle(device->handle()),
+                                   static_cast<libinput_config_accel_profile>(config->inputAccelProfile()));
+            }
+        };
+
+        auto &connections = m_deviceConnections[device];
+        connections.append(connect(config, &TreelandUserConfig::inputAccelProfileChanged, this, applyAccelProfile));
+        connections.append(connect(config, &TreelandUserConfig::configInitializeSucceed, this, applyAccelProfile));
+    }
+}
</code_context>
<issue_to_address>
**issue (bug_risk):** The accel profile is never applied immediately on device init and may miss if config is already initialized.

In `initDevice`, `applyAccelProfile` is only triggered by `inputAccelProfileChanged` and `configInitializeSucceed`. For devices added after config initialization, if the user never changes the accel profile, it’s never applied and the device stays on the libinput default. Please ensure the current accel profile is applied once when the device is initialized (after wiring the signals), or otherwise use the config state to apply the existing value immediately on attachment.
</issue_to_address>

### Comment 2
<location path="waylib/src/server/protocols/wvirtualinputhelper.cpp" line_range="111-120" />
<code_context>
+    d->seat->cursor()->handle()->map_input_to_output(device->handle()->handle(), output);
+}
+
+void WVirtualInputHelper::handleNewVKV1(::wlr_virtual_keyboard_v1 *virtualKeyboard)
+{
+    W_D(WVirtualInputHelper);
+    auto *device = ensureDevice(qw_input_device::from(&virtualKeyboard->keyboard.base));
+    if (!device || device->seat() || d->virtualKeyboards.contains(device))
+        return;
+
+    d->virtualKeyboards.append(device);
+    attachDevice(device);
+    device->safeConnect(&qw_input_device::before_destroy, this, [d, device] {
+        d->virtualKeyboards.removeOne(device);
+        device->safeDeleteLater();
</code_context>
<issue_to_address>
**issue (bug_risk):** Virtual keyboards are never detached from the seat on destruction.

In `handleNewVKV1`, the device is attached to the seat via `attachDevice`, but in the `before_destroy` handler you only remove it from `virtualKeyboards` and delete it. You never call `seat->detachInputDevice(device)`, unlike the previous `WInputMethodHelper::handleNewVKV1` implementation. This can leave the seat referencing a destroyed device. Please detach the device from `d->seat` here (guarded by a null check) before scheduling deletion.
</issue_to_address>

### Comment 3
<location path="waylib/src/server/protocols/wvirtualinputhelper.cpp" line_range="126-120" />
<code_context>
+    });
+}
+
+void WVirtualInputHelper::handleNewVPV1(::wlr_virtual_pointer_v1_new_pointer_event *event)
+{
+    W_D(WVirtualInputHelper);
+    if (!event || !shouldAcceptSeat(event->suggested_seat))
+        return;
+
+    auto *device = ensureDevice(qw_input_device::from(&event->new_pointer->pointer.base));
+    if (!device || device->seat() || d->virtualPointers.contains(device))
+        return;
+
+    d->virtualPointers.append(device);
+    attachDevice(device);
+    maybeMapToOutput(device, event->suggested_output);
+    device->safeConnect(&qw_input_device::before_destroy, this, [d, device] {
+        d->virtualPointers.removeOne(device);
+        device->safeDeleteLater();
</code_context>
<issue_to_address>
**issue (bug_risk):** Virtual pointers are also not detached from the seat when destroyed.

In `handleNewVPV1`, the `before_destroy` handler only updates `virtualPointers` and deletes the device; it never calls `seat->detachInputDevice(device)`. This can leave the seat referencing a destroyed device. As with the physical input path, detach the device from the seat before `safeDeleteLater()` to keep the lifecycle consistent and avoid stale references in `WSeat`.
</issue_to_address>

### Comment 4
<location path="src/input/inputdevice.cpp" line_range="357" />
<code_context>
 }

-bool InputDevice::initTouchPad(WInputDevice *device)
+void InputDevice::initDevice(WInputDevice *device)
 {
-    if (device->handle()->is_libinput()
</code_context>
<issue_to_address>
**issue (complexity):** Consider simplifying initDevice by removing manual connection bookkeeping and redundant device-type checks, relying instead on QObject-based auto-disconnection and a single type filter before creating the lambda.

You can simplify this by avoiding explicit connection bookkeeping and by removing the duplicated device-type logic.

### 1. Remove `m_deviceConnections` and `deinitDevice` using a context object

Instead of storing `QMetaObject::Connection`s and manually disconnecting, use a `QObject*` as the connection context so Qt auto‑disconnects when the context is destroyed. Since the lambda is per‑device, `device` is a natural context if `WInputDevice` derives from `QObject` (it almost certainly does, given the Qt pattern):

```cpp
void InputDevice::initDevice(WInputDevice *device)
{
    if (!device || !device->handle() || !device->handle()->is_libinput()) {
        return;
    }

    const auto deviceType = device->qtDevice()->type();
    if (deviceType == QInputDevice::DeviceType::TouchPad) {
        configTapEnabled(libinput_device_handle(device->handle()), LIBINPUT_CONFIG_TAP_ENABLED);
    }

    if (deviceType != QInputDevice::DeviceType::TouchPad &&
        deviceType != QInputDevice::DeviceType::Mouse) {
        return;
    }

    auto config = Helper::instance()->config();
    auto applyAccelProfile = [device, config, deviceType]() {
        // No need to re-check type; already filtered above and fixed per device.
        configAccelProfile(libinput_device_handle(device->handle()),
                           static_cast<libinput_config_accel_profile>(config->inputAccelProfile()));
    };

    // Use `device` as context: signals auto-disconnect when `device` is destroyed.
    connect(config, &TreelandUserConfig::inputAccelProfileChanged,
            device, applyAccelProfile);
    connect(config, &TreelandUserConfig::configInitializeSucceed,
            device, applyAccelProfile);
}
```

With this pattern:

- `m_deviceConnections` can be removed.
- `deinitDevice` can be either removed (if not used outside) or reduced to a no‑op, because disconnection is handled automatically by Qt when `device` is destroyed.

If you need `deinitDevice` API to stay, you can keep an empty implementation:

```cpp
void InputDevice::deinitDevice(WInputDevice *device)
{
    Q_UNUSED(device);
    // No manual disconnect needed; connections use `device` as context.
}
```

### 2. Avoid repeated type checks inside the lambda

The lambda currently re-checks `device->qtDevice()->type()`. Since you already filter by type in `initDevice`, you can capture the `deviceType` or simply rely on the earlier guard. This reduces cognitive load without changing behavior (the outer `if` ensures only mouse/touchpad devices get these connections). The example above uses `deviceType` captured in the lambda.
</issue_to_address>

### Comment 5
<location path="src/input/inputdevice.h" line_range="68" />
<code_context>

     static InputDevice *m_instance;
     std::unique_ptr<GestureRecognizer> m_touchpadRecognizer;
+    QHash<WInputDevice *, QVector<QMetaObject::Connection>> m_deviceConnections;
     uint m_touchpadFingerCount = 0;
 };
</code_context>
<issue_to_address>
**issue (complexity):** Consider relying on Qt’s built-in connection management instead of tracking per-device QMetaObject::Connection objects in m_deviceConnections.

You can avoid the extra lifecycle bookkeeping (`m_deviceConnections`) by using Qt’s existing connection management, which simplifies `InputDevice`’s state and API surface without changing behavior.

Instead of storing `QMetaObject::Connection` objects per `WInputDevice*`, you can:

1. Use the device (or a small per-device helper `QObject`) as the connection context.
2. Rely on `QObject::disconnect` by sender/receiver in `deinitDevice()` rather than tracking individual connections.

### Example: use device as connection context

In the cpp, where you currently do something like:

```cpp
auto c1 = connect(device, &WInputDevice::accelProfileChanged,
                  this, &InputDevice::onAccelProfileChanged);
auto c2 = connect(device, &WInputDevice::somethingElse,
                  this, &InputDevice::onSomethingElse);

m_deviceConnections[device].append(c1);
m_deviceConnections[device].append(c2);
```

You can instead write:

```cpp
connect(device, &WInputDevice::accelProfileChanged,
        this, &InputDevice::onAccelProfileChanged);
connect(device, &WInputDevice::somethingElse,
        this, &InputDevice::onSomethingElse);
```

and drop the hash entirely. Then `deinitDevice()` only needs to disconnect by sender/receiver:

```cpp
void InputDevice::deinitDevice(WInputDevice *device)
{
    // Remove all connections between this and `device`
    QObject::disconnect(device, nullptr, this, nullptr);

    // existing deinitialization logic...
}
```

If `device` is a `QObject` and will be destroyed at some point, Qt will additionally auto-disconnect on destruction, so you don’t need explicit bookkeeping for cleanup.

### If you need per-device isolation

If you want stricter separation, introduce a per-device helper instead of a global hash:

```cpp
class DeviceConnections : public QObject
{
    Q_OBJECT
public:
    DeviceConnections(WInputDevice *device, InputDevice *input, QObject *parent = nullptr)
        : QObject(parent)
    {
        connect(device, &WInputDevice::accelProfileChanged,
                input, &InputDevice::onAccelProfileChanged);
        // other device-specific connections...
    }
};
```

Then in `initDevice`:

```cpp
void InputDevice::initDevice(WInputDevice *device)
{
    new DeviceConnections(device, this, device); // parented to device; auto-destroyed
}
```

With either approach, you can remove:

```cpp
QHash<WInputDevice *, QVector<QMetaObject::Connection>> m_deviceConnections;
```

from the header and avoid the cross-cutting connection bookkeeping, while keeping the new `initDevice` / `deinitDevice` API intact.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@zorowk zorowk marked this pull request as draft March 20, 2026 08:23
@zorowk zorowk force-pushed the virtualinput branch 2 times, most recently from a4bdeee to bb508d0 Compare March 23, 2026 05:58
zoro wk and others added 3 commits March 23, 2026 14:32
Introduce WVirtualInputHelper to manage virtual input devices created
via Wayland protocols. This moves virtual keyboard handling out of
WInputMethodHelper and adds support for zwlr_virtual_pointer_v1.

Virtual devices are automatically attached to the seat and cleaned up
when destroyed.

Log: add WVirtualInputHelper for virtual keyboard and pointer
Influence: virtual input pointer and keyboard
Create the single-pixel-buffer-v1 manager during compositor
initialization to support clients creating single pixel buffers.

This protocol allows clients to efficiently represent solid color
surfaces without allocating a full wl_buffer.

Log: add single-pixel-buffer-v1 protocol support
Add libinput accel speed/profile dconfig options and wire them into input
device setup. Apply settings on device hotplug and config changes, and
reuse a helper to update all current devices.

Log: apply libinput accel config
Influence: pointer, touchpad, virtual input
@zorowk zorowk marked this pull request as ready for review March 24, 2026 01:33
Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @zorowk, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@zorowk zorowk marked this pull request as draft April 3, 2026 09:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants