Skip to content

Add native KWin support (libei pointer + ScreenShot2 target detection)#97

Open
mcdax wants to merge 15 commits into
moverest:mainfrom
mcdax:feat/kde-pointer-support
Open

Add native KWin support (libei pointer + ScreenShot2 target detection)#97
mcdax wants to merge 15 commits into
moverest:mainfrom
mcdax:feat/kde-pointer-support

Conversation

@mcdax

@mcdax mcdax commented Jun 14, 2026

Copy link
Copy Markdown

Implements #44 — native KWin support, gated behind a new optional kde Meson feature so the wlroots path and minimal builds are unchanged.

Pointer movement & clicks

KWin doesn't implement wlr-virtual-pointer, so the pointer is driven through libei over an EIS socket obtained from KWin's org.kde.KWin.EIS.RemoteDesktop.connectToEIS D-Bus method — the approach described by @Gliese852 in #44 (thanks!). move_pointer() is now a small dispatcher that picks the wlroots backend when wl_virtual_pointer is present, otherwise the KDE backend.

A couple of things that were needed to make it reliable on KWin:

  • the D-Bus connection that called connectToEIS must stay alive for the lifetime of the EIS session (KWin tears the session down otherwise);
  • ei_dispatch() has to run before poll() so the initial libei handshake is driven, and an ei_ping() round-trip flushes the final click before teardown.

Target auto-detection (floating + detect)

Since KWin also lacks wlr-screencopy, the screen is captured via KWin's org.kde.KWin.ScreenShot2 D-Bus interface (CaptureArea) and fed into the existing OpenCV pipeline unchanged. This path is built only when both opencv and kde are enabled. Because ScreenShot2 is a restricted interface, the installed desktop file now carries X-KDE-DBUS-Restricted-Interfaces=org.kde.KWin.ScreenShot2 and uses an absolute Exec (generated via configure_file); capture only works for an installed binary, not from the build tree.

Build

meson setup build -Dkde=enabled            # pointer support
meson setup build -Dkde=enabled -Dopencv=enabled   # + auto-detection

New dependencies (only with -Dkde): libei and an sd-bus implementation (libsystemd, or basu on non-systemd systems). wl-kbptr --version prints (kde).

Testing

Tested on KWin Plasma 6 (Wayland):

  • pointer jumps to exact global coordinates including on a second monitor; left/middle/right clicks work;
  • auto-detection captures via ScreenShot2 and the OpenCV pipeline returns sensible targets;
  • a build without -Dkde and a wlroots compositor behave exactly as before;
  • added a unit test for the global→region coordinate mapper.

Known limitations

  • ScreenShot2 auto-detection requires the program to be installed (allow-list entry), so it can't be exercised from the build tree.

Happy to adjust naming, the feature split, or anything else to fit the project's conventions.

mcdax added 12 commits June 14, 2026 10:06
… poll

Two defects found via live testing on KWin (Plasma 6, Wayland):

- KWin ties the EIS session to the D-Bus connection that called
  connectToEIS. eis_dbus_connect() unref'd the bus immediately, so KWin
  tore the session down ("Disconnected by EIS") before any input could
  be sent. eis_dbus now returns an eis_connection {bus, fd} the caller
  keeps open until after ei_unref(), via eis_dbus_disconnect().

- The handshake pump poll()ed before the first ei_dispatch(), so the
  initial libei connection handshake (and queued capability binding) was
  never driven and the device never resumed (2s timeout). Dispatch now
  runs at the top of both pump loops, before poll().

Verified: cursor moves to exact global coordinates incl. a second
monitor (e.g. 3000,500), no disconnect, click path flushes via ei_ping.
@mcdax mcdax mentioned this pull request Jun 14, 2026
@moverest

moverest commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Thank your for your PR, this is an interesting addition. I don't use KDE thus why I didn't actively worked on supporting it, however it's still something I'm interested in supporting.

I like the fact that this is all optional since it inevitably brings new dependencies. In fact, that's the reason why the detection feature is optional.

I've tested it on my machine and I have a few observations:

  • When the program doesn't have access to query the org.kde.KWin.ScreenShot2 API it will run into a segmentation fault when trying to do the detection.
  • It doesn't reliably click on my machine. I'm not sure if it's because the overlay surface doesn't disappear fast enough thus gets the click event or if it's something else. I tried adding a one second delay but it didn't seem to reliably work either (but maybe it help?). I need to investigate further. Is it doing this on your machine? I've ran it with Arch's plasma-desktop 6.6.5-1.
  • Setup:
    • Since I don't use KDE, I'm not sure what would be the best way to configure the software. From my understanding, it has to run through the wl-kbptr.desktop to get the necessary permissions. I've been able to set a short cut in the config panel, but I'm not sure how we can pass arguments to it (e.g. to change the modes or just use a different configuration file). The way I've ran it is by creating a secondary file with the options I needed. That can't be the only way, is it?
    • I'm also asking so that this could be put into the README with a sub-section in the Setting the bindings section with instructions on how to configure it for KDE. Though, this doesn't have to be done in this PR.
    • The following is a bit of a nitpick: The terminology is a bit fuzzy, we're talking about KWin but the option is kde. I think it would make sense to just say everywhere kwin, i.e. the compositor (and only mention KDE in the README) as it's really what we're targeting.

I haven't looked into the code in detail yet. I also haven't tested a multiple monitor setup either. I will do that a bit later.

@mcdax

mcdax commented Jun 18, 2026

Copy link
Copy Markdown
Author

Thank you very much for your input! I'll analyse the issues over the next few days.

Interestingly, I don't experience any delay or click issues at all (KDE Plasma 6.6.4 on Kubuntu 26.04). I'm going to test on a few more machines.

▎ The way I've ran it is by creating a secondary file with the options I needed. That can't be the only way, is it?

That's exactly how I tested it too, since I can't think of another way that wouldn't make things even more complex. I'll give it more thought. There's also the issue that you can start the binary multiple times, which leads to strange effects.

Multi-monitor is still a limitation: it currently only works on the active output.

mcdax added 2 commits June 18, 2026 14:35
query_screenshot() can return NULL on the KWin backend when the
compositor denies the org.kde.KWin.ScreenShot2 request (the wlroots
backend never returned NULL). Detection dereferenced the buffer
unconditionally, segfaulting. Bail out with an empty area list instead.
The feature targets the KWin compositor specifically, so name it after
the compositor everywhere in the code and build (option, KWIN_ENABLED
macro, file/symbol names, --version marker). 'KDE' is kept only in
user-facing prose in the README and in the actual D-Bus interface names
(org.kde.KWin.*, X-KDE-DBUS-Restricted-Interfaces).
mcdax pushed a commit to mcdax/wl-kbptr that referenced this pull request Jun 23, 2026
Open the EIS/libei session once and reuse it for every pointer move
instead of reconnecting -- plus running pointer_kwin_available()'s extra
connect/close probe -- on every keystroke. Per-keystroke session churn
made KWin repeatedly build and tear down a virtual pointer device, and
left the final click racing against the teardown of the preview move's
session: a likely cause of the unreliable clicks reported in moverest#97.

Also inject motion, press and release as separate frames with a
flush_until_pong() round-trip between each, mirroring the working
wlr_virtual_pointer path. Without it KWin received the whole burst at
once with near-identical ei_now() timestamps, producing a zero-duration
click. Reusing the session also requires a monotonically increasing
start_emulating() sequence number.

Adds temporary diagnostic logging (device capabilities, regions, click
timestamps) to compare behaviour across machines; to be removed before
merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@mcdax

mcdax commented Jun 23, 2026

Copy link
Copy Markdown
Author

Hi @moverest,

I can't reproduce it on my other machine either, unfortunately. (Arch with recent plasma-desktop)

I did a small refactor around the roundtrips and the EIS connection handling. It now opens one connection and reuses it instead of reconnecting on every keystroke and I also added some extra debug logging.

Could you pull the latest commit, build with -Dkwin=enabled, run it again, and check if the problem still happens? If you paste the logs (run it from a terminal, the info: lines), that'd really help to see where it breaks on your machine.

Btw, I don't think the overlay is stealing the click: its input region is empty, so clicks pass straight through to the window underneath. That probably also explains why the 1s delay didn't help.

Thanks!

PS: Most of the code is AI generated, I still have to revise esp. the comments.

Open the EIS/libei session once and reuse it for every pointer move
instead of reconnecting -- plus running pointer_kwin_available()'s extra
connect/close probe -- on every keystroke. Per-keystroke session churn
made KWin repeatedly build and tear down a virtual pointer device, and
left the final click racing against the teardown of the preview move's
session: a likely cause of the unreliable clicks reported in moverest#97.

Also inject motion, press and release as separate frames with a
flush_until_pong() round-trip between each, mirroring the working
wlr_virtual_pointer path. Without it KWin received the whole burst at
once with near-identical ei_now() timestamps, producing a zero-duration
click. Reusing the session also requires a monotonically increasing
start_emulating() sequence number.

Adds temporary diagnostic logging (device capabilities, regions, click
timestamps) to compare behaviour across machines; to be removed before
merge.
@mcdax mcdax force-pushed the feat/kde-pointer-support branch from a9d3bd3 to cbbb0a9 Compare June 23, 2026 10:25
@mcdax

mcdax commented Jun 23, 2026

Copy link
Copy Markdown
Author

btw. Most of the code can also be used for GNOME support. (In the long run)

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