PasteService-Hardening: Terminal-Paste, Clipboard-Restore, CopyQ-Cleanup - #38
Conversation
xdotool-based active-window detection lets PasteService send Ctrl+Shift+V in known terminal emulators instead of Ctrl+V, which is usually a no-op or copy there. X11-only, falls back cleanly on Wayland/missing xdotool. First step of the PasteService-Hardening roadmap item (clipboard-restore and CopyQ-cleanup follow separately).
Reads the previous clipboard content before writing the transcribed text, then restores it once ydotool has delivered the paste keystrokes. Wayland (wl-paste) and X11 (xclip -o) are supported; restore is best-effort and never blocks the main paste path on failure. Only applies to the autopaste flow — clipboard_only() leaves the transcribed text in the clipboard for manual paste, as intended. Second step of the PasteService-Hardening roadmap item (CopyQ cleanup follows separately).
Popen() without check=True never raises CalledProcessError, so that branch in _read_clipboard() was dead. Also documents why paste() and _ydotool_paste() each sleep _PASTE_DELAY for a different reason, so a future reader doesn't mistake it for an accidental double wait.
After a successful autopaste-and-restore cycle, remove the transient pasted text from CopyQ's clipboard history if CopyQ is installed and its top entry matches exactly (trailing newline tolerant). No hard CopyQ dependency: missing binary, timeouts, and other errors are swallowed and never affect the main paste path. Third and final step of the PasteService-Hardening roadmap item.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f95e72eeb6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| self._restore_clipboard(previous_clipboard) | ||
| self._cleanup_copyq(text) |
There was a problem hiding this comment.
Clean CopyQ before restoring the old clipboard
When CopyQ is running and there was previous clipboard text, _restore_clipboard() writes that old value back first, which CopyQ records or moves to history index 0. _cleanup_copyq() then only reads/removes index 0, so it sees the restored value instead of the newly pasted text and leaves the transcript in CopyQ history; clean the pasted entry before restoring, or locate the matching history item after restore.
Useful? React with 👍 / 👎.
| run_mock.side_effect = side_effect | ||
| svc.paste("hallo welt", force_autopaste=True) | ||
| assert run_mock.call_count == 2 | ||
| assert run_mock.call_count == 3 |
There was a problem hiding this comment.
Mock the saved clipboard before expecting restore
With this test's mocks, _read_clipboard() is not mocked and the real wl-paste/xclip -o probe commonly returns None in headless CI; then _restore_clipboard(None) is a no-op and subprocess.run is only called for the clipboard write and ydotool (I reproduced 2 calls with the same mocks). This assertion is therefore host-dependent unless the test stubs _read_clipboard to return saved text before expecting the restore call.
Useful? React with 👍 / 👎.
test_force_autopaste_override_enables_ydotool asserted call_count==3 but only mocked subprocess.run, not the subprocess.Popen used by _read_clipboard(). On a desktop with wl-copy/xclip actually installed this reads the real clipboard and the assertion holds; in CI, where those binaries are absent, _read_clipboard() returns None and the restore step never fires a subprocess.run call, dropping the count to 2 and failing the build. Mock _read_clipboard() directly so the test no longer depends on whichever clipboard tooling happens to be present on the runner.
Summary
Implements the roadmap item "PasteService-Hardening — Clipboard-Restore + Terminal-Paste" (Project #10), in three independently reviewed commits.
Test plan
pytest tests/test_paste_service.py tests/test_state_machine.py -v— all greenpytest -q(full suite) — 356 passed, 70 skipped, 0 failed