Skip to content

feat(input): implement wlr-virtual-pointer-unstable-v1#96

Merged
nongio merged 2 commits into
mainfrom
feat/wlr-virtual-pointer-pr
Apr 14, 2026
Merged

feat(input): implement wlr-virtual-pointer-unstable-v1#96
nongio merged 2 commits into
mainfrom
feat/wlr-virtual-pointer-pr

Conversation

@nongio

@nongio nongio commented Apr 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • Implements the wlr-virtual-pointer-unstable-v1 protocol, enabling tools like wlrctl, wtype, and automation drivers to synthesize pointer events
  • Hand-rolled GlobalDispatch/Dispatch since Smithay doesn't ship a virtual-pointer delegate
  • Supports: motion, motion_absolute, button, axis, axis_source, axis_stop, axis_discrete, frame
  • Axis events accumulate into a single AxisFrame and flush on frame, matching real libinput batching
  • Click-to-focus: virtual pointer button presses now trigger focus_window_under_cursor + layers_engine button hooks, so synthesized clicks behave like real ones

Completes the automation trio alongside the existing virtual-keyboard-unstable-v1 and wlr-foreign-toplevel-management-unstable-v1.

Test plan

  • wlrctl pointer click focuses and clicks windows
  • wlrctl pointer move moves the cursor
  • wtype types text via virtual keyboard (existing, unaffected)
  • wlrctl toplevel focus <app> still works (existing, unaffected)

nongio added 2 commits April 15, 2026 00:01
Advertise the wlr-virtual-pointer-unstable-v1 protocol so clients
like \`wlrctl\`, \`ydotool\` (its wayland-native mode), \`wtype\`, and
bespoke automation drivers (MCP / test harnesses) can synthesize
pointer events that feed straight into Smithay's \`PointerHandle\`.

Smithay does not ship a virtual-pointer delegate, so the
\`GlobalDispatch\`/\`Dispatch\` plumbing is hand-rolled in
\`state::virtual_pointer\`. Scope is the minimum useful subset:

  motion            relative displacement, applied cumulatively
  motion_absolute   normalized absolute position mapped to the first
                    output's geometry
  button            wl_pointer button press/release
  axis              accumulated via an AxisFrame
  axis_source       recorded on the pending AxisFrame
  axis_stop         stop notification on an axis
  axis_discrete     discrete (v120) scroll step
  frame             flush of the coalesced event sequence

Axis events accumulate across multiple axis / axis_source / axis_stop
/ axis_discrete requests into a single AxisFrame, committed to the
pointer on \`frame\`. This matches how real libinput pointer sequences
are batched before dispatch.

No constraints, no relative-motion reporting, no clamp to screen
bounds — those are real-pointer concerns and the client driving the
virtual pointer is assumed to be well-behaved.

Paired with the \`otto-remote-control\` Claude skill in the user's
skills directory, which documents how to drive Otto via \`wlrctl\`
(\`wlrctl pointer click\`, \`wlrctl keyboard type\`,
\`wlrctl toplevel focus\`, etc.). Otto already implements
\`virtual-keyboard-unstable-v1\` and
\`wlr-foreign-toplevel-management-unstable-v1\`; this commit completes
the trio.
Real libinput clicks run focus_window_under_cursor + layers_engine
button notifications from on_pointer_button, but virtual-pointer
events were bypassing that handler and dispatching to whatever
surface already held pointer focus. Test harnesses couldn't use
motion_absolute + click to focus a specific window.

Mirror the same focus + button-down/up hooks in the Frame flush
so synthesized clicks behave like real ones.
@nongio nongio force-pushed the feat/wlr-virtual-pointer-pr branch from b6c8e40 to 96ecc51 Compare April 14, 2026 22:02
@nongio nongio merged commit e7f63a5 into main Apr 14, 2026
6 checks passed
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.

1 participant