Skip to content

Release 0.3.3: Linux terminal clipboard, selection sync, Gdk log filter#104

Merged
d0dg3r merged 1 commit intomainfrom
release/v0.3.3-terminal-clipboard
Apr 6, 2026
Merged

Release 0.3.3: Linux terminal clipboard, selection sync, Gdk log filter#104
d0dg3r merged 1 commit intomainfrom
release/v0.3.3-terminal-clipboard

Conversation

@d0dg3r
Copy link
Copy Markdown
Owner

@d0dg3r d0dg3r commented Apr 6, 2026

Summary

Prepares v0.3.3 with Linux/Wayland terminal clipboard hardening and UX aligned with typical Linux terminals.

Changes

  • Clipboard I/O: wl-paste / wl-copy and xclip via subprocess with timeout; arboard as guarded fallback. Avoids WebKit/GTK Wayland deadlocks from in-process clipboard crates.
  • Paste safety: Sanitize pasted text (controls, replacement char rejection) before feeding xterm.
  • Keys: Ctrl+Shift+C / Ctrl+Insert copy; Ctrl+Shift+V / Shift+Insert paste; Ctrl+C stays SIGINT.
  • Selection: Debounced sync of xterm selection to clipboard + primary so middle-click paste works without an extra copy shortcut.
  • GTK: GLib handler drops only the noisy Gdk "Error writing selection data … Broken pipe" line.

Version / docs

  • App version 0.3.3 in package.json, tauri.conf.json, Cargo.toml.
  • docs/CHANGELOG.md, docs/releases.md, Flatpak metainfo updated.

After merge

  1. GitHub release: Push tag v0.3.3 to run release.yml (full matrix + Arch job if configured there).
  2. Manual build + Arch package: Actions → Manual BuildRun workflow → set Version to 0.3.3 → enable Linux (and optionally other targets). The Linux job builds bundles and runs the Arch makepkg step in Docker; download artifact manual-bundle-linux-arch-<run_id> for .pkg.tar.zst.

Validation

bash .agents/skills/nosuckshell_ops/scripts/validate_project.sh passed locally (tsc, Vitest, cargo test).

- Read/write terminal clipboard via wl-paste/wl-copy and xclip (subprocess + timeout)
- Sanitize paste; Ctrl+Shift+C/V and Shift+Insert; selection syncs to primary (debounced)
- Suppress noisy Gdk broken-pipe selection warning on Wayland
- Bump app version and document in CHANGELOG, releases.md, Flatpak metainfo
Copilot AI review requested due to automatic review settings April 6, 2026 23:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Prepares the desktop app for v0.3.3, focusing on more reliable Linux terminal clipboard/selection behavior (Wayland/X11) and reducing noisy GTK/Gdk logging, plus the usual version/documentation bumps for a release.

Changes:

  • Reworks terminal copy/paste UX: sanitizes pasted text, adds Linux-friendly shortcuts, and syncs selection to clipboard/primary (debounced).
  • Moves Linux clipboard I/O to subprocess calls (wl-paste/wl-copy, xclip) with timeouts and adds a targeted Gdk warning filter.
  • Bumps version references to 0.3.3 and updates release docs/changelog/Flatpak metadata.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
flatpak/dev.nosuckshell.desktop.metainfo.xml Adds 0.3.3 release entry for Flatpak metadata.
docs/releases.md Updates “current release” pointers to 0.3.3.
docs/CHANGELOG.md Adds 0.3.3 changelog entry and release link.
apps/desktop/src/tauri-api.ts Exposes new Tauri command for writing terminal selection to clipboard.
apps/desktop/src/features/terminal-paste-sanitize.ts Introduces paste sanitization helper for terminal input.
apps/desktop/src/features/terminal-paste-sanitize.test.ts Adds Vitest coverage for paste sanitization behavior.
apps/desktop/src/e2e/tauri-core-shim.ts Stubs the new Tauri invoke command for e2e.
apps/desktop/src/components/TerminalPane.tsx Implements copy/paste shortcuts, selection→clipboard sync, and paste sanitization.
apps/desktop/src/components/HelpPanel.tsx Documents the new terminal copy/paste behavior and shortcuts.
apps/desktop/src-tauri/tauri.conf.json Bumps Tauri app version to 0.3.3.
apps/desktop/src-tauri/src/main.rs Implements Linux clipboard subprocess read/write and registers new command + Gdk filter.
apps/desktop/src-tauri/src/gdk_log_suppress.rs Adds GLib log handler to suppress the specific noisy Gdk warning line.
apps/desktop/src-tauri/Cargo.toml Bumps Rust crate version to 0.3.3.
apps/desktop/package.json Bumps desktop package version to 0.3.3.
apps/desktop/package-lock.json Bumps lockfile package versions to 0.3.3.
Files not reviewed (1)
  • apps/desktop/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +222 to +245
) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--no-newline", "--type", "text/plain"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--primary", "--no-newline"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--no-newline"]) {
return sanitize_terminal_clipboard_text(t);
}
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "primary", "-t", "UTF8_STRING", "-o"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "primary", "-o"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "clipboard", "-t", "UTF8_STRING", "-o"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "clipboard", "-o"]) {
return sanitize_terminal_clipboard_text(t);
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

These branches return sanitize_terminal_clipboard_text(t); immediately after a command succeeds. If sanitization returns None (e.g., replacement chars from invalid UTF-8 or only stripped control bytes), the function returns None and skips trying later fallbacks. Consider chaining clipboard_from_command(...).and_then(sanitize_terminal_clipboard_text) and only returning when the sanitized result is Some, otherwise continue to the next source.

Suggested change
) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--no-newline", "--type", "text/plain"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--primary", "--no-newline"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--no-newline"]) {
return sanitize_terminal_clipboard_text(t);
}
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "primary", "-t", "UTF8_STRING", "-o"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "primary", "-o"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "clipboard", "-t", "UTF8_STRING", "-o"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "clipboard", "-o"]) {
return sanitize_terminal_clipboard_text(t);
)
.and_then(sanitize_terminal_clipboard_text)
{
return Some(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--no-newline", "--type", "text/plain"])
.and_then(sanitize_terminal_clipboard_text)
{
return Some(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--primary", "--no-newline"])
.and_then(sanitize_terminal_clipboard_text)
{
return Some(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--no-newline"])
.and_then(sanitize_terminal_clipboard_text)
{
return Some(t);
}
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "primary", "-t", "UTF8_STRING", "-o"])
.and_then(sanitize_terminal_clipboard_text)
{
return Some(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "primary", "-o"])
.and_then(sanitize_terminal_clipboard_text)
{
return Some(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "clipboard", "-t", "UTF8_STRING", "-o"])
.and_then(sanitize_terminal_clipboard_text)
{
return Some(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "clipboard", "-o"])
.and_then(sanitize_terminal_clipboard_text)
{
return Some(t);

Copilot uses AI. Check for mistakes.
Comment on lines +223 to +245
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--no-newline", "--type", "text/plain"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--primary", "--no-newline"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--no-newline"]) {
return sanitize_terminal_clipboard_text(t);
}
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "primary", "-t", "UTF8_STRING", "-o"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "primary", "-o"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "clipboard", "-t", "UTF8_STRING", "-o"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "clipboard", "-o"]) {
return sanitize_terminal_clipboard_text(t);
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

Same issue as above for the xclip fallbacks: return sanitize_terminal_clipboard_text(t); can short-circuit to None after a successful command output, preventing later fallbacks (clipboard selection / arboard) from being attempted. Only return when sanitization yields Some, otherwise keep falling through.

Suggested change
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--no-newline", "--type", "text/plain"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--primary", "--no-newline"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("wl-paste", &["--no-newline"]) {
return sanitize_terminal_clipboard_text(t);
}
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "primary", "-t", "UTF8_STRING", "-o"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "primary", "-o"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "clipboard", "-t", "UTF8_STRING", "-o"]) {
return sanitize_terminal_clipboard_text(t);
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "clipboard", "-o"]) {
return sanitize_terminal_clipboard_text(t);
if let Some(t) = sanitize_terminal_clipboard_text(t) {
return Some(t);
}
}
if let Some(t) = clipboard_from_command("wl-paste", &["--no-newline", "--type", "text/plain"]) {
if let Some(t) = sanitize_terminal_clipboard_text(t) {
return Some(t);
}
}
if let Some(t) = clipboard_from_command("wl-paste", &["--primary", "--no-newline"]) {
if let Some(t) = sanitize_terminal_clipboard_text(t) {
return Some(t);
}
}
if let Some(t) = clipboard_from_command("wl-paste", &["--no-newline"]) {
if let Some(t) = sanitize_terminal_clipboard_text(t) {
return Some(t);
}
}
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "primary", "-t", "UTF8_STRING", "-o"]) {
if let Some(t) = sanitize_terminal_clipboard_text(t) {
return Some(t);
}
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "primary", "-o"]) {
if let Some(t) = sanitize_terminal_clipboard_text(t) {
return Some(t);
}
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "clipboard", "-t", "UTF8_STRING", "-o"]) {
if let Some(t) = sanitize_terminal_clipboard_text(t) {
return Some(t);
}
}
if let Some(t) = clipboard_from_command("xclip", &["-selection", "clipboard", "-o"]) {
if let Some(t) = sanitize_terminal_clipboard_text(t) {
return Some(t);
}

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +18
let out = "";
for (const ch of input) {
if (ch === "\0") continue;
if (ch === "\n" || ch === "\r" || ch === "\t") {
out += ch;
continue;
}
const cp = ch.codePointAt(0)!;
if (cp < 0x20 || cp === 0x7f || (cp >= 0x80 && cp <= 0x9f)) continue;
out += ch;
}
return out;
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

Building out via out += ch in a per-character loop can be unnecessarily slow for large pastes (creates many intermediate strings). Consider accumulating characters into an array and join(""), or otherwise batching writes, to keep sanitization linear-time with less allocation churn.

Suggested change
let out = "";
for (const ch of input) {
if (ch === "\0") continue;
if (ch === "\n" || ch === "\r" || ch === "\t") {
out += ch;
continue;
}
const cp = ch.codePointAt(0)!;
if (cp < 0x20 || cp === 0x7f || (cp >= 0x80 && cp <= 0x9f)) continue;
out += ch;
}
return out;
const out: string[] = [];
for (const ch of input) {
if (ch === "\0") continue;
if (ch === "\n" || ch === "\r" || ch === "\t") {
out.push(ch);
continue;
}
const cp = ch.codePointAt(0)!;
if (cp < 0x20 || cp === 0x7f || (cp >= 0x80 && cp <= 0x9f)) continue;
out.push(ch);
}
return out.join("");

Copilot uses AI. Check for mistakes.
@d0dg3r d0dg3r merged commit 7fe0d8c into main Apr 6, 2026
8 checks passed
@d0dg3r d0dg3r deleted the release/v0.3.3-terminal-clipboard branch April 6, 2026 23:37
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