Skip to content

Add libei/portal fallback for input on KDE/GNOME compositors#2008

Open
r33drichards wants to merge 3 commits into
mainfrom
claude/slack-session-a3tbke
Open

Add libei/portal fallback for input on KDE/GNOME compositors#2008
r33drichards wants to merge 3 commits into
mainfrom
claude/slack-session-a3tbke

Conversation

@r33drichards

@r33drichards r33drichards commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements a fallback input mechanism for compositors that don't support wlroots virtual-pointer protocol (KDE Plasma, GNOME). When the portal-libei feature is enabled, input operations (pointer, scroll, drag, typing) automatically fall back to libei/portal interfaces instead of failing.

Key Changes

  • Fallback infrastructure: Added with_libei_fallback() and with_wtype_libei_fallback() helper functions that detect when wlroots virtual-pointer is unavailable and route operations through libei adapters on portal-enabled builds.

  • Marker-based error detection: Introduced NO_VPTR_MARKER constant to tag "no virtual-pointer" errors, allowing the dispatch layer to distinguish recoverable failures from hard errors.

  • Refactored input functions: Split public input APIs (click, scroll, move_cursor_absolute, drag) into thin wrappers that call either wlroots or libei implementations based on compositor capabilities.

  • libei adapter implementations: Added coordinate/button/key translation layer that bridges wlroots-shaped APIs (output-relative integers, X keysyms, CUA button codes) to libei's logical device-region floats and evdev codes:

    • libei_click(), libei_scroll(), libei_move_absolute(), libei_drag()
    • libei_type_text(), libei_press_key()
    • key_to_evdev() mapping for keyboard input
  • Typing/keyboard fallback: Modified type_text(), press_key(), and hotkey() to detect wtype failures and fall back to libei when available. Note: hotkey() with modifier chords is not yet supported via libei and fails with a clear error message.

  • Stub implementations: For builds without portal-libei feature, libei adapters are compiled as unreachable stubs so closures type-check while the feature-gated dispatch seams prevent their execution.

Implementation Details

  • Drag operations via libei are emulated as move→click (no held-button primitive yet) with interpolated steps.
  • Scroll operations use ±10 logical units per tick to mirror wlroots convention.
  • Synthetic cursor position tracking is maintained across both backends.
  • Error messages include context about which backend failed and what fallback is available.
  • All changes preserve existing anyhow::Result signatures by using string markers rather than typed errors.

Fixes #1982.

https://claude.ai/code/session_01BY1JBgWq3gxNST3htj3Ysy

Summary by CodeRabbit

  • New Features

    • Expanded Wayland input injection with automatic fallback for compositors lacking virtual-pointer/virtual-keyboard support.
    • Improved cursor operations (click, scroll, move, drag) and keyboard typing/press handling via the available fallback path.
  • Bug Fixes

    • Reduced input failures by routing “missing virtual pointer” and wtype-related errors through the fallback when enabled.
    • Enhanced hotkey error handling, including clearer behavior when modifier chords aren’t supported.
  • Chores

    • Updated Git ignore rules to skip local Claude-related files.

claude added 2 commits June 24, 2026 03:07
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BY1JBgWq3gxNST3htj3Ysy
…Wayland input dispatch (#1982)

Wire the existing libei/xdg-desktop-portal RemoteDesktop backend into the
Wayland input dispatch as the fallback when the compositor exposes no
zwlr_virtual_pointer_v1 (KWin/Plasma, Mutter/GNOME) or no virtual-keyboard
(wtype). The libei backend was implemented but only reachable from the
example; now click/scroll/move/drag/type_text/press_key fall back to it on a
portal-libei build, via a single seam (with_libei_fallback /
with_wtype_libei_fallback) keyed on a NO_VPTR_MARKER sentinel.

This fixes the silent no-op on KDE/GNOME Wayland where input events were
dropped because no wlroots virtual-pointer manager is present. No behavior
change for the published no-feature build (the libei branch is cfg'd out).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BY1JBgWq3gxNST3htj3Ysy
@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview Jun 24, 2026 5:17am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b50b9e8d-47a7-4dcd-a330-383e3f10497e

📥 Commits

Reviewing files that changed from the base of the PR and between c146a31 and 124e711.

📒 Files selected for processing (1)
  • libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs

📝 Walkthrough

Walkthrough

Adds a Wayland libei fallback path for missing virtual-pointer support and routes pointer/keyboard input through it. Also adds a Git ignore rule for .claude/.

Changes

Wayland libei fallback for non-wlroots input

Layer / File(s) Summary
Sentinel and session setup
libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs
Defines NO_VPTR_MARKER, adds fallback classification for the missing virtual-pointer-manager case, and updates open_vptr_session error handling and manager requirements.
Pointer APIs and helpers
libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs
Adds output sizing and click normalization helpers, then routes click, scroll, move_cursor_absolute, and drag through with_libei_fallback.
Keyboard fallback routing
libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs
Reworks type_text, press_key, and hotkey to use wtype result matching and libei fallback handling.
libei adapters and key mapping
libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs
Adds the libei adapter implementations, feature-gated stubs, and key_to_evdev mapping used by raw key presses.

.gitignore update

Layer / File(s) Summary
Ignore .claude/
.gitignore
Adds .claude/ to the Git ignore list.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • trycua/cua#1966: Introduces the native Wayland input paths that this PR extends with fallback routing and libei handling.
  • trycua/cua#1992: Also changes wayland/mod.rs around virtual-pointer session setup and portal-libei feature behavior.

Poem

🐇 A bunny on Wayland gave a hop and a spin,
When wlroots was absent, libei came in.
A marker, a fallback, a cursor so bright,
And .claude/ now stays out of Git’s sight.
Hop, click, and type — all working anew!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The .gitignore change for .claude/ is unrelated to the Wayland input fallback objectives. Remove the .gitignore-only housekeeping change or split it into a separate PR if it is intentional.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding libei/portal fallback input support for KDE/GNOME Wayland.
Linked Issues check ✅ Passed The PR implements the requested portal-libei fallback so input can work when wlroots virtual-pointer is unavailable.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/slack-session-a3tbke

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs (2)

935-941: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Normalize libei click coordinates like the vptr path.

click(…, 0, 0, …) currently means “center” in the wlroots path, but the libei fallback clicks the top-left corner and records (0, 0). Share the same default/clamp behavior before calling libei::move_absolute / libei::click.

Also applies to: 1359-1368

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs` around lines
935 - 941, The libei click path is not normalizing coordinates the same way as
the wlroots/vptr path, so `click(…, 0, 0, …)` ends up using the top-left instead
of center. Update the coordinate handling in the Wayland click flow (the code
around the `px`/`py` normalization and the `libei::move_absolute` /
`libei::click` calls, including the duplicate location noted in the comment) to
apply the same “0,0 ნიშნავს center” default and clamp behavior used by the vptr
path before dispatching the click.

856-874: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Check virtual-pointer before unrelated foreign-toplevel failures.

Line 857 can bail before Line 867 emits NO_VPTR_MARKER, so with_libei_fallback never runs on compositors that lack both protocols. Move the vptr_manager marker check earlier, and only require zwlr_foreign_toplevel_manager_v1 when activation is actually needed.

🐛 Proposed routing fix
-    if state.manager.is_none() {
-        anyhow::bail!("compositor does not expose zwlr_foreign_toplevel_manager_v1");
-    }
     for _ in 0..4 {
         queue.roundtrip(&mut state)?;
     }
 
+    let mgr = state.vptr_manager.clone().ok_or_else(|| {
+        if PORTAL_LIBEI_ENABLED {
+            anyhow::anyhow!(
+                "compositor does not expose zwlr_virtual_pointer_manager_v1 \
+                 ({NO_VPTR_MARKER})"
+            )
+        } else {
+            anyhow::anyhow!(
+                "no input backend for this compositor ({NO_VPTR_MARKER}): it \
+                 exposes no zwlr_virtual_pointer_manager_v1 and this build was \
+                 compiled without libei/portal support (`#1982`). Use the \
+                 portal-enabled Linux build for input on KDE Plasma / GNOME, or \
+                 a wlroots compositor (sway, labwc, hyprland)."
+            )
+        }
+    })?;
+
+    if activate_window_id.is_some() && state.manager.is_none() {
+        anyhow::bail!("compositor does not expose zwlr_foreign_toplevel_manager_v1");
+    }
+
     let seat = state
         .seat
         .clone()
         .ok_or_else(|| anyhow::anyhow!("compositor exposed no wl_seat for virtual-pointer input"))?;
-    let mgr = state.vptr_manager.clone().ok_or_else(|| {
-        if PORTAL_LIBEI_ENABLED {
-            anyhow::anyhow!(
-                "compositor does not expose zwlr_virtual_pointer_manager_v1 \
-                 ({NO_VPTR_MARKER})"
-            )
-        } else {
-            anyhow::anyhow!(
-                "no input backend for this compositor ({NO_VPTR_MARKER}): it \
-                 exposes no zwlr_virtual_pointer_manager_v1 and this build was \
-                 compiled without libei/portal support (`#1982`). Use the \
-                 portal-enabled Linux build for input on KDE Plasma / GNOME, or \
-                 a wlroots compositor (sway, labwc, hyprland)."
-            )
-        }
-    })?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs` around lines
856 - 874, The virtual-pointer fallback path in the Wayland logic is checking
`state.manager` before it verifies the `vptr_manager` marker, so compositors
that lack both protocols bail out too early and never trigger
`with_libei_fallback`. Reorder the checks in the affected flow around
`state.manager`, `state.vptr_manager`, and `PORTAL_LIBEI_ENABLED` so the
`NO_VPTR_MARKER` path is evaluated first, then only enforce
`zwlr_foreign_toplevel_manager_v1` when foreign-toplevel activation is actually
required.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs`:
- Around line 1398-1423: The libei_drag fallback currently simulates a drag with
move→click-at-end, but that does not perform a real drag and still returns
success. Update libei_drag to either use a proper libei press/move/release
sequence with the existing libei adapter helpers, or change it to return an
explicit unsupported error when button-hold is unavailable. Make sure the
behavior of libei_drag and its call sites no longer report Ok(()) for non-drag
click-only fallback.
- Around line 1199-1211: The portal-libei branch in the hotkey handling path is
rejecting all keys, even when `mods` is empty and the input is just a single key
press. Update the logic around the `portal-libei` fallback to allow non-chord
hotkeys to proceed by reusing `libei_press_key`, and keep the existing bail-out
only for modifier chords in the relevant hotkey delivery function.
- Around line 1434-1465: The libei input path in libei_press_key/key_to_evdev
only handles a small set of navigation and control keys, so portal-libei builds
still fail for common inputs like letters, digits, and function keys. Extend
key_to_evdev to recognize basic alphanumeric keys and F1–F12, and keep
libei_press_key using the expanded mapping so the fallback can press the same
common keys as the other input path.

---

Outside diff comments:
In `@libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs`:
- Around line 935-941: The libei click path is not normalizing coordinates the
same way as the wlroots/vptr path, so `click(…, 0, 0, …)` ends up using the
top-left instead of center. Update the coordinate handling in the Wayland click
flow (the code around the `px`/`py` normalization and the `libei::move_absolute`
/ `libei::click` calls, including the duplicate location noted in the comment)
to apply the same “0,0 ნიშნავს center” default and clamp behavior used by the
vptr path before dispatching the click.
- Around line 856-874: The virtual-pointer fallback path in the Wayland logic is
checking `state.manager` before it verifies the `vptr_manager` marker, so
compositors that lack both protocols bail out too early and never trigger
`with_libei_fallback`. Reorder the checks in the affected flow around
`state.manager`, `state.vptr_manager`, and `PORTAL_LIBEI_ENABLED` so the
`NO_VPTR_MARKER` path is evaluated first, then only enforce
`zwlr_foreign_toplevel_manager_v1` when foreign-toplevel activation is actually
required.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3e55c545-3737-4f50-a6a0-2705a4094544

📥 Commits

Reviewing files that changed from the base of the PR and between 1a3e9b3 and c146a31.

📒 Files selected for processing (2)
  • .gitignore
  • libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs

Comment thread libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs
Comment thread libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs Outdated
Comment thread libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Linux visual regression artifacts

Matrix jobs now run independently. Download visual artifacts from this workflow run.
Each background-GUI job uploads a .gif of the interaction plus two annotated PNGs (<app>.png raw, <app>-atspi.png with AT-SPI element boxes); the cua-driver-linux-som-overlays artifact adds <app>-som.png cua Set-of-Marks overlays:

  • cua-driver-linux-cursor-click-gif
  • cua-driver-linux-background-terminal-gif
  • cua-driver-linux-parallel-drag-xserver
  • cua-driver-linux-background-gui-chromium
  • cua-driver-linux-background-gui-tk
  • cua-driver-linux-background-gui-gtk3-gedit
  • cua-driver-linux-background-gui-gtk3-mousepad
  • cua-driver-linux-background-gui-gtk3-scite
  • cua-driver-linux-background-gui-gtk4-characters
  • cua-driver-linux-background-gui-qt5-manuskript
  • cua-driver-linux-background-gui-qt5-klog
  • cua-driver-linux-background-gui-qt5-openambit
  • cua-driver-linux-background-gui-qt6-kate
  • cua-driver-linux-background-gui-qt6-kcalc
  • cua-driver-linux-background-gui-qt6-okular
  • cua-driver-linux-background-gui-qt6-qownnotes
  • cua-driver-linux-background-gui-electron-zettlr
  • cua-driver-linux-background-gui-electron-joplin
  • cua-driver-linux-background-gui-electron-logseq
  • cua-driver-linux-som-overlays

Open workflow run and download artifacts

- Routing order: open_vptr_session now emits NO_VPTR_MARKER (via the
  vptr_manager check) BEFORE requiring zwlr_foreign_toplevel_manager_v1,
  and only requires foreign-toplevel when activate_window_id is set. This
  lets with_libei_fallback trigger on KDE/GNOME (which expose neither
  wlroots protocol) instead of erroring out before the marker is reached.
- Coordinate normalization: libei click/move now reproduce the vptr
  path's default-to-centre + clamp behaviour via a wl_output dimension
  probe, so click(.., 0, 0, ..) lands on centre rather than top-left.
- Drag honesty: libei_drag now bails with an explicit "unsupported"
  error instead of simulating a drag as move->click-at-end (which holds
  no button), since the libei worker exposes no button-hold primitive.
- Non-chord hotkey: a modifier-less hotkey now routes through the
  wtype->libei fallback as a single key press; only true modifier chords
  remain unsupported on the libei backend.
- Keymap coverage: key_to_evdev now maps a-z, 0-9 and F1-F12 to their
  Linux evdev codes so the portal-libei path can press letters/digits/
  function keys when wtype is unavailable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BY1JBgWq3gxNST3htj3Ysy

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Addressed all five review findings in commit 124e711 (libs/cua-driver/rust/crates/platform-linux/src/wayland/mod.rs):

  • Routing order: open_vptr_session now evaluates the vptr_manager / NO_VPTR_MARKER path first and only requires zwlr_foreign_toplevel_manager_v1 when window activation is requested, so the libei fallback actually triggers on KDE/GNOME.
  • Click coordinates: libei click now applies the same (0,0)→center default and clamp as the virtual-pointer path (via a wl_output-probed normalize_click_xy).
  • Drag honesty: the libei worker exposes only a combined click (no button-hold), so libei_drag now returns an explicit "unsupported" error instead of reporting a click-at-end as a successful drag.
  • Non-chord hotkey: when there are no modifiers, the portal-libei path routes through libei_press_key; the bail-out is kept only for true modifier chords.
  • Keymap coverage: key_to_evdev now maps a–z, 0–9, and F1–F12.

The .gitignore (.claude/) out-of-scope warning is a separate decision still under discussion.


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

@r33drichards: Thanks for the detailed summary of the fixes! I'll kick off a fresh review of the PR to verify the changes in commit 124e711.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

cua-driver-rs (Linux/Wayland): KDE Plasma 6.7 — agent cursor renders but input doesn't dispatch (portal-libei feature gated off in published binary)

2 participants