mirror of
https://github.com/games-on-whales/gst-wayland-display.git
synced 2026-10-15 12:51:57 +00:00
[PR #64] feat(input): tablet tool (pen) support via zwp_tablet_v2 #64
Labels
No labels
enhancement
enhancement
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
games-on-whales/gst-wayland-display#64
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/games-on-whales/gst-wayland-display/pull/64
Author: @kmreisi
Created: 9/19/2026
Status: 🔄 Open
Base:
stable← Head:feat/tablet-tool-support📝 Commits (1)
f84d782feat(input): tablet tool (pen) support via zwp_tablet_v2📊 Changes
6 files changed (+810 additions, -5 deletions)
View changed files
📝
wayland-display-core/src/comp/input.rs(+341 -3)📝
wayland-display-core/src/comp/mod.rs(+7 -0)📝
wayland-display-core/src/tests/client.rs(+77 -0)📝
wayland-display-core/src/tests/mod.rs(+1 -0)➕
wayland-display-core/src/tests/test_tablet.rs(+372 -0)📝
wayland-display-core/src/wayland/handlers/seat.rs(+12 -2)📄 Description
Summary
libinput tablet tool events were dropped on the floor:
State::process_input_event()had no
TabletTool*arms and the compositor never exposedzwp_tablet_manager_v2.A virtual pen tablet plugged into the compositor therefore did nothing at all — no
tablet events for clients that speak the protocol, and no pointer events either.
This adds
zwp_tablet_v2support, with a pointer-emulation fallback for clientsthat do not implement the protocol.
Branch is based on current
stable; nothing outside the tablet path is touched.Behaviour
Tablet-aware clients (those that bound
zwp_tablet_seat_v2) get the real thing:proximity_in/proximity_out,motion,down/uppressure,distance,tilt,rotation,slider,wheelzwp_tablet_tool_v2.buttonwith the raw libinput button codearrive as two separate
zwp_tablet_tool_v2objects withtype = penandtype = eraser— that is how a client learns it should erase rather than drawSuch a client deliberately receives no
wl_pointermotion for pen movement: itwould otherwise see every stroke twice (once as tablet motion, once as pointer
motion), which makes drawing applications misbehave. The cursor composited into the
streamed video still follows the pen — it is simply not delivered as pointer input.
Every other client (gamescope, most games, older toolkits) gets pointer
emulation, so a pen still works as a mouse:
wl_pointer.motionBTN_LEFTpress/releaseBTN_STYLUSBTN_RIGHTBTN_MIDDLEEmulated buttons are released when the pen leaves proximity or the tablet is
removed, so a pen that goes away mid-press cannot leave a stuck button behind.
Axes folded into other events. A libinput tablet tool event carries the axes
that changed with it — tip and proximity events included, not just axis events.
Those are forwarded rather than dropped; without that, the pressure going back to 0
on tip up never reaches the client and a hovering pen keeps reporting the pressure
of the last stroke.
Implementation notes
comp/mod.rs:TabletManagerState::new::<State>(dh)creates thezwp_tablet_manager_v2global; atablet_emulated_buttons: HashSet<u32>tracksthe pointer buttons currently held on behalf of a pen so they can always be
released.
wayland/handlers/seat.rs:TabletSeatHandler+delegate_tablet_manager!.tablet_tool_image()assigns the tool's cursor surface to the samecursor_statethe pointer uses, so a client setting a pen cursor works like aclient setting a mouse cursor.
comp/input.rs:DeviceAdded/DeviceRemovedwithDeviceCapability::TabletToolregister andunregister the tablet on the seat's tablet seat; removing the last tablet clears
the tools and releases any emulated button.
TabletToolAxescollects the*_has_changed()axes of anyTabletToolEventinto one struct, so proximity / tip / axis events can all forward what they
carry through the same
send_tool_axes()helper.under the pen, the owning client's objects are walked
(
DisplayHandle::backend_handle().with_all_objects_for()) looking for azwp_tablet_seat_v2. That is what decides between real tablet events andpointer emulation, and it is the reason a mixed session (a drawing app plus a
game) behaves correctly for both.
tablet-aware client that is a render-only update
(
tablet_cursor_only_motion()→set_pointer_location(), nowl_pointerevent); for every other client it goes through the normal
pointer_motion_absolute()path.Space::element_under(), the same lookup thepointer path uses, and passed to smithay's
TabletToolHandle, which emitsproximity_in/proximity_outon focus changes.touch_location_transformed()(a tablet tool reports absolute, normalised coordinates like a touchscreen).
Testing
ci/harness.sh lint build unit— all three phases pass, 30 tests, 1 ignored(the GPU dmabuf test):
New unit tests in
wayland-display-core/src/tests/test_tablet.rs:tablet_aware_client_receives_tablet_events— a client that boundzwp_tablet_seat_v2receives tablet added, tool added, proximity in, down,pressure (0.5 of the 0..65535 protocol range), tilt, motion, stylus button, up,
proximity out, and pressure back to 0 on tip up; and asserts it gets no
wl_pointerbutton or motion events for the pen.eraser_is_reported_as_its_own_tool— flipping the stylus over announces asecond tool with
type = eraser, and both ends can draw.non_tablet_client_gets_pointer_emulation— a client without the tablet protocolsees exactly
BTN_LEFTpress/release (tip) andBTN_RIGHTpress/release(
BTN_STYLUS), plus pointer motion for the pen.pen_leaving_proximity_releases_emulated_buttons— a pen that leaves proximitywhile the tip is down leaves no button held.
The test client (
tests/client.rs) gainedenable_tablet()(bindzwp_tablet_manager_v2→get_tablet_seat) and collectszwp_tablet_seat_v2/zwp_tablet_tool_v2events.End to end
Verified through wolf with a synthetic
Moonlight client that replays a real pen packet sequence (hover, stroke with
pressure ramp, stylus button, tip up, out of proximity) over the ENet control
channel into a wolf session, plus a plain mouse replay as a sanity check. Two
configurations, both against this branch built into the wolf image:
zwp_tablet_manager_v2): receives thetablet events, with pressure and tilt, and no duplicated pointer motion.
wev(verified at image build time not to referencezwp_tabletat all): receives the pen as pointer motion plusBTN_LEFT/BTN_RIGHT.Tablet path (GTK3 client), 287 pen events + 53 mouse events replayed:
(
proximity_in/proximity_outare graded on thezwp_tablet_tool_v2wire tracebecause GTK3/GDK does not deliver proximity events to a widget.)
Pointer-emulation path (
wev):The same change set (on top of
67b1183, which is what wolf currently pins) was alsoconfirmed with a real stylus: Lenovo Idea Tab Pro with Moonlight Android streaming
into a wolf lobby.
The mouse sanity replay that runs before the pen sequence is unaffected in both
modes (pointer enter, 53 motions, button press/release delivered normally).
Note for wolf
This is only the compositor half. wolf needs a companion change to plug its virtual
pen tablet into the lobby compositor — the tablet has to be (re)created against the
compositor that owns the session's Wayland display, otherwise the events never reach
this code. The wolf side I tested with is on
kmreisi/wolf
feature/xbox-ds4-generic-pads(lobby hotplug of the pen tablet + pen event semantics); I can open that separately.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.