Add native KWin support (libei pointer + ScreenShot2 target detection) - #97
Add native KWin support (libei pointer + ScreenShot2 target detection)#97mcdax wants to merge 20 commits into
Conversation
… 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.
|
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:
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. |
|
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. |
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).
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) <noreply@anthropic.com>
|
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 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.
a9d3bd3 to
cbbb0a9
Compare
|
btw. Most of the code can also be used for GNOME support. (In the long run) |
|
Tested this branch at cbbb0a9 on Arch Linux with Plasma 6.7 (kwin_wayland, libei 1.6.0), built with Results:
Two small build issues on current Arch, where OpenCV is now version 5 (pkg-config name
With those two changes it builds and runs cleanly. Happy to provide logs or test further revisions. Transparency note: this comment was written by Claude Code (an AI assistant), which set up and tested the build together with this account's owner on their machine; the owner reviewed and approved it before posting. |
|
Follow-up on the unreliable-click issue @moverest reported: we can now Symptom: with the branch at cbbb0a9 (Arch, Plasma 6.7.3, libei 1.6.0), Cause (as far as we can tell): the per-frame round-trips order the events Fix that works for us: sleeping ~60 ms before the press and again before @@ pointer_kwin_move()
+ const char *delay_env = getenv("WL_KBPTR_KWIN_CLICK_DELAY_MS");
+ long click_delay_us = (delay_env ? atol(delay_env) : 60) * 1000;
+
uint64_t t_motion = ei_now(s->ei);
ei_device_pointer_motion_absolute(device, tx, ty);
ei_device_frame(device, t_motion);
flush_until_pong(s);
@@
+ if (click_delay_us > 0) {
+ usleep(click_delay_us);
+ }
t_press = ei_now(s->ei);
ei_device_button_button(device, btn, true);
ei_device_frame(device, t_press);
flush_until_pong(s);
+ if (click_delay_us > 0) {
+ usleep(click_delay_us);
+ }
t_release = ei_now(s->ei);For upstream it probably makes more sense as a proper config option (or just a |
|
Thanks a lot for the analysis and recommendations! Sadly, I hadn't much time to look into it yet. I'll try go though all your points in detail this week. |
|
I am actively using this now and doing a few tweaks that could be system specific. Anything that isn't I will try to contribute. Thank you so much for everything!
On Wed, Jul 29, 2026, at 13:58, mcdax wrote:
*mcdax* left a comment (moverest/wl-kbptr#97) <#97 (comment)>
Thanks a lot for the analysis and recommendations! Sadly, I hadn't much time to look into it yet. I'll try go though all your points in detail this week.
—
Reply to this email directly, view it on GitHub <#97?email_source=notifications&email_token=BCEBN5QUIGYRWGS253GXGN35HIUM5A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJSGA4TONRQGQ32M4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5120976047>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/BCEBN5R2XIS6NYC5HXNSRCL5HIUM5AVCNFSNUABFKJSXA33TNF2G64TZHM3DGMBRHE3DSOBYHNEXG43VMU5TINRVHEYDQMZWGU22C5QC>.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS <https://github.com/notifications/mobile/ios/BCEBN5WJ7ANGTPJLEK33GNL5HIUM5A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJSGA4TONRQGQ32M4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJKTGN5XXIZLSL5UW64Y> and Android <https://github.com/notifications/mobile/android/BCEBN5UGW7GDC6EWU5PJNKT5HIUM5A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJSGA4TONRQGQ32M4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJLTGN5XXIZLSL5QW4ZDSN5UWI>. Download it today!
You are receiving this because you commented.Message ID: ***@***.***>
_S
|
OpenCV 5 ships under the pkg-config name opencv5 and moved cv::boundingRect into the new geometry module. Look up opencv5 first then fall back to opencv4, and include opencv2/geometry.hpp when CV_VERSION_MAJOR >= 5 so target_detection.cpp compiles on both.
The per-frame flush_until_pong() round-trips order the press and release frames correctly, but do not advance the ei_now() clock fast enough to separate them: press and release still go out only tens of microseconds apart, carrying the same-millisecond ei_now() timestamp, i.e. a zero-duration click. Qt accepts that, but Chromium-based Wayland clients discard it, so the click is silently dropped there. Sleep 60 ms before the press and again before the release so the press->release gap resembles real hardware and every toolkit sees a distinct press and release.
Document the user-friendly way to bind wl-kbptr on KDE Plasma: a plain custom shortcut with no arguments (the XDG config file is picked up automatically), and clarify that the installed desktop file is only needed for ScreenShot2-based auto-detection, not for pointer/click or stdin-driven floating mode.
8909007 to
d688ca8
Compare
Remove the temporary per-click LOG_INFO diagnostics and the device_count field from the KWin pointer backend (flagged "remove before merge"), and extract the duplicated 60ms click-pacing sleep into a named KWIN_CLICK_GAP_US constant carrying the rationale. Also add the missing NULL check on the screenshot result allocation in the ScreenShot2 backend, and fix the README pointer flag (--only-print, not --print-only).
12502bc to
e270d61
Compare
read_metadata() ignored every sd_bus_message_read() return value. sd_bus leaves the destination untouched on failure, so a malformed reply left `value` uninitialized while still setting the corresponding have_* flag, propagating a garbage width/height/stride into the buffer arithmetic and malloc() below. The unchecked key read could also hand strcmp() an uninitialized pointer. Check each read and treat any failure as an unusable reply. Also validate the reported geometry before allocating: every format we accept is 4 bytes per pixel, so a zero-sized image or a stride too small for one row means the size arithmetic would not describe the pixel data. While here, trace the EIS region-mapping fallback (the one path that can silently place the pointer somewhere unexpected, including when a layout exceeds MAX_REGIONS) and correct the move_pointer() doc comment, which claimed the click came from state->click rather than the parameter.
Implements #44 — native KWin support, gated behind a new optional
kdeMeson 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'sorg.kde.KWin.EIS.RemoteDesktop.connectToEISD-Bus method — the approach described by @Gliese852 in #44 (thanks!).move_pointer()is now a small dispatcher that picks the wlroots backend whenwl_virtual_pointeris present, otherwise the KDE backend.A couple of things that were needed to make it reliable on KWin:
connectToEISmust stay alive for the lifetime of the EIS session (KWin tears the session down otherwise);ei_dispatch()has to run beforepoll()so the initial libei handshake is driven, and anei_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'sorg.kde.KWin.ScreenShot2D-Bus interface (CaptureArea) and fed into the existing OpenCV pipeline unchanged. This path is built only when bothopencvandkdeare enabled. BecauseScreenShot2is a restricted interface, the installed desktop file now carriesX-KDE-DBUS-Restricted-Interfaces=org.kde.KWin.ScreenShot2and uses an absoluteExec(generated viaconfigure_file); capture only works for an installed binary, not from the build tree.Build
New dependencies (only with
-Dkde):libeiand an sd-bus implementation (libsystemd, orbasuon non-systemd systems).wl-kbptr --versionprints(kde).Testing
Tested on KWin Plasma 6 (Wayland):
ScreenShot2and the OpenCV pipeline returns sensible targets;-Dkdeand a wlroots compositor behave exactly as before;Known limitations
ScreenShot2auto-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.