Skip to content

fix(text-injection): route XWayland xdotool fallback through clipboard paste - #680

Open
AmirF194 wants to merge 3 commits into
VocaHQ:mainfrom
AmirF194:fix/657-xdotool-layout-garbling
Open

fix(text-injection): route XWayland xdotool fallback through clipboard paste#680
AmirF194 wants to merge 3 commits into
VocaHQ:mainfrom
AmirF194:fix/657-xdotool-layout-garbling

Conversation

@AmirF194

Copy link
Copy Markdown

Description

On the XWayland fallback path (DesktopEnvironment.WAYLAND_XDOTOOL), _inject_with_xdotool() always types text with xdotool type, which simulates keypresses against the active X keyboard layout. A character the current layout doesn't map (or maps differently) comes out as the wrong glyph, garbling non-Latin dictation whenever the active layout doesn't match the dictated language.

The native ydotool path already avoids this: _inject_with_wayland_tool() prefers clipboard + simulated Ctrl+V over ydotool type, because a clipboard paste is layout-independent. The XWayland xdotool fallback never got the same treatment, even though it shares the same underlying problem.

This PR routes _inject_with_xdotool() through _inject_via_clipboard_paste() first when running on the XWayland fallback and ydotool is installed, falling back to raw xdotool type only when no paste tool is available or the paste itself fails (mirroring the existing pattern in _inject_with_wayland_tool()).

Scope: this only touches the WAYLAND_XDOTOOL branch. A genuinely native X11 session (DesktopEnvironment.X11) is unaffected and keeps using xdotool type as before, since #657 is specifically about the XWayland fallback.

Related Issue

Fixes #657

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)

Verification

  • New regression tests in tests/test_text_injector.py: test_inject_with_xdotool_xwayland_prefers_clipboard_paste fails against unmodified main (asserts xdotool type is never called when ydotool is available) and passes on this branch; test_inject_with_xdotool_xwayland_falls_back_without_ydotool confirms the fallback path is unchanged when no ydotool is installed.
  • Full tests/ suite (pytest) passes on Python 3.9 and 3.13 (the matrix's low and high ends), run as a non-root user; flake8 --select=E9,F63,F7,F82, black --check, and isort --check-only --profile black are all clean on both. Did not separately run 3.10/3.11; the change is plain Python with no version-specific syntax.
  • Not verified: an actual XWayland session with a non-US layout and a real ydotool/ydotoold (no display server or uinput device in this environment), so the fix is verified at the subprocess-call level (clipboard copy + ydotool key invoked, xdotool type not invoked), not end-to-end against a live compositor.

Checklist

  • My code follows the code style of this project (black, isort)
  • I have added tests to cover my changes
  • All new and existing tests pass locally

…d paste

xdotool type simulates keypresses against the active X keyboard layout, so
a character the layout doesn't map comes out as the wrong glyph or garbled
(VocaHQ#657). The native ydotool path already avoids this by pasting via the
clipboard instead of typing (_inject_with_wayland_tool); the XWayland
xdotool fallback (_inject_with_xdotool) never got the same treatment.

Prefer clipboard paste on that fallback too when ydotool is installed,
falling back to raw xdotool type only when no paste tool is available or
the paste itself fails.

Fixes VocaHQ#657
@github-actions github-actions Bot added app Core Python application (src, packaging) tests Test suite changes labels Aug 15, 2026

@jatinkrmalik jatinkrmalik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@AmirF194 Thank you for raising this. Did a quick adversarial review. CI is green, which I appreciate, but I do not think this fixes #657 as reported.

The #657 AppImage/GNOME log already ran _try_recover_from_fallback() and then Text injected using xdotool into Telegram (X11 window id 27262988). AppImage does not bundle ydotool (see packaging/appimage/build.sh).

On inject_text():

  • ydotool type "" works → recover switches to WAYLAND and _inject_with_wayland_tool already pastes. This block never runs.
  • ydotool missing → which is false. No change.
  • ydotool present, daemon down → ydotool key almost certainly fails too, then we xdotool type the same garbled glyphs.

Can we drop "Fixes #657" until the xdotool-only fallback can paste without ydotool? The tool that exists on this path is xdotool. I would rather see xclip/xsel (X11 CLIPBOARD, what XWayland/Telegram actually paste) plus:

xdotool key --clearmodifiers ctrl+v

ydotool + wl-copy is the native Wayland recipe, not the XWayland one.

Product truth: text has to land in the focused field. Returning True skips the xdotool fallback whenever _inject_via_clipboard_paste gets rc=0 from ydotool key. That is not evidence of a paste.

Two races vs the old type path:

  1. _inject_with_wayland_tool waits for modifiers to release before paste, because a held PTT/Alt turns Ctrl+V into Ctrl+Alt+V (nothing pastes, command still succeeds). This caller does not wait.
  2. _copy_to_clipboard prefers wl-copy. The #657 target is XWayland. Mutter/KWin clipboard bridging is async, so Ctrl+V can paste the previous X11 CLIPBOARD, or nothing. We then report success.

If you keep a ydotool paste attempt at all: wait for modifiers, write the X11 clipboard (xclip/xsel) for WAYLAND_XDOTOOL, and do not treat ydotool rc=0 as "text is in the field."

The new tests call _inject_with_xdotool after overwriting environment. They never call inject_text(), so they miss recover stealing the working-ydotool case (the actual #657 log). any(c[:2] == ["ydotool", "key"]) is also true for the ydotool key --help probe, even if Ctrl+V never ran. Can we assert the paste argv and that wl-copy/xclip received the dictated string?

Also missing:

  • X11 environment still uses xdotool type
  • paste failure still falls through to xdotool type
  • inject_text() on WAYLAND_XDOTOOL with no ydotool still types (the reported setup)

Gateway-green pytest is not proof that привет landed in the focused XWayland field.

Happy to re-review once that path works without ydotool.

jatinkrmalik's review on VocaHQ#680 pointed out the AppImage build does not
bundle ydotool, so the prior fix's ydotool clipboard paste never ran on
the actual reported setup; it fell through to the same layout-dependent
xdotool type as before. It also flagged a real paste-target mismatch:
an XWayland window reads the X11 CLIPBOARD selection, not the Wayland
clipboard wl-copy/ydotool write to.

The XWayland fallback now copies via xclip/xsel and pastes with
xdotool key --clearmodifiers ctrl+v when either is installed, waiting
for held modifiers to release first (the same wait already used by the
native ydotool path). ydotool clipboard paste stays as a second
fallback when xclip/xsel are absent; xdotool type is the last resort.

New regression test drives inject_text() end-to-end with no ydotool on
PATH, reproducing the reported AppImage/GNOME log rather than calling
the fallback method directly, so it also covers the recovery check
that could otherwise skip this path.
@AmirF194

Copy link
Copy Markdown
Author

Thanks, this was a real gap, appreciate the detailed read. Pushed a fix that addresses the core problem: the XWayland fallback now copies via xclip/xsel (X11 CLIPBOARD, what Telegram/XWayland actually reads) and pastes with xdotool key --clearmodifiers ctrl+v, tried before the ydotool path rather than after. That matches your AppImage trace: no ydotool on PATH means the recovery check finds nothing and the environment stays WAYLAND_XDOTOOL, so this new path is what actually runs.

Also added the modifier-release wait before the paste (same helper _inject_with_wayland_tool already uses), so a held PTT key can't turn Ctrl+V into a no-op the way you described.

On the test critique: added a new test that drives inject_text() directly with no ydotool available, rather than calling _inject_with_xdotool() with a preset environment, so it exercises the recovery check the same way your AppImage log did. Also tightened the existing ydotool-path assertion to check the resolved paste command, not just that ydotool key was called at all (that also matches the --help probe).

One thing I left alone: native X11 (DesktopEnvironment.X11, no XWayland involved) still types via xdotool type. #657 is scoped to the Wayland/XWayland path and this PR doesn't change X11 behavior either way, so I'd rather not fold that in here. Happy to open a separate issue for it if you'd like the same clipboard-paste treatment there.

Full suite plus black/isort/flake8 green locally. Let me know if you'd like anything else changed.

@AmirF194

Copy link
Copy Markdown
Author

Pushed the fix for the changes you requested a week ago (963c65c) and don't seem to have a way to re-request your review as an outside contributor, so flagging it here instead: could you take another look when you have a chance?

@netlify

netlify Bot commented Aug 27, 2026

Copy link
Copy Markdown

Deploy Preview for voca-linux canceled.

Name Link
🔨 Latest commit aff6833
🔍 Latest deploy log https://app.netlify.com/projects/voca-linux/deploys/6a8fd4842703d100095183c0

@jatinkrmalik

Copy link
Copy Markdown
Member

@AmirF194 It seems like the CI is still red and failing on couple of steps. Would you mind fixing them?

@AmirF194

Copy link
Copy Markdown
Author

Thanks for merging main in. Vocalinux CI finished green on the merge commit (aff6833, completed 06:15:27Z), nothing red on my end to fix right now. Let me know if you'd like anything else addressed.

@jatinkrmalik

Copy link
Copy Markdown
Member

Thanks for merging main in. Vocalinux CI finished green on the merge commit (aff6833, completed 06:15:27Z), nothing red on my end to fix right now. Let me know if you'd like anything else addressed.

Sorry, seems like there are merge conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Core Python application (src, packaging) tests Test suite changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Layout-dependent garbling

2 participants