diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-10 02:26:25 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-10 02:27:41 +0300 |
| commit | 77fcde0a2736c3927d1bc589bd9b4d0173a9df46 (patch) | |
| tree | 209c704342fb52a7ecbb5fe0cdbc291f7650e720 /src/platform/linux.rs | |
| parent | 0facd819b4747c7634040b561509137c2608b024 (diff) | |
fix: support paste in modal inputs
refs #302
Diffstat (limited to 'src/platform/linux.rs')
| -rw-r--r-- | src/platform/linux.rs | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/src/platform/linux.rs b/src/platform/linux.rs index d1ae07f1..5b6a6d3c 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -187,6 +187,15 @@ pub fn write_clipboard(bytes: &[u8]) -> bool { false } +pub fn read_clipboard_text() -> Option<String> { + for command in read_clipboard_text_commands() { + if let Some(text) = read_clipboard_text_with_command(&command) { + return Some(text); + } + } + None +} + pub fn open_url(url: &str) -> std::io::Result<()> { Command::new("xdg-open") .arg(url) @@ -337,6 +346,72 @@ fn clipboard_commands() -> Vec<ClipboardCommand> { commands } +fn read_clipboard_text_commands() -> Vec<ClipboardCommand> { + let mut commands = Vec::new(); + + if std::env::var_os("WAYLAND_DISPLAY").is_some() { + commands.push(ClipboardCommand { + program: "wl-paste", + args: &["--type", "text/plain;charset=utf-8"], + }); + commands.push(ClipboardCommand { + program: "wl-paste", + args: &["--type", "text/plain"], + }); + } + + if std::env::var_os("DISPLAY").is_some() { + commands.push(ClipboardCommand { + program: "xclip", + args: &["-selection", "clipboard", "-out"], + }); + commands.push(ClipboardCommand { + program: "xsel", + args: &["--clipboard", "--output"], + }); + } + + commands +} + +fn read_clipboard_text_with_command(command: &ClipboardCommand) -> Option<String> { + const MAX_CLIPBOARD_TEXT_BYTES: usize = 1024 * 1024; + + let mut child = Command::new(command.program) + .args(command.args) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn() + .ok()?; + + let stdout = child.stdout.take()?; + let read = match read_limited_reader(stdout, MAX_CLIPBOARD_TEXT_BYTES) { + Ok(LimitedRead::Oversized) => { + let _ = child.kill(); + let _ = child.wait(); + return None; + } + Ok(read) => read, + Err(_) => { + let _ = child.kill(); + let _ = child.wait(); + return None; + } + }; + + let status = child.wait().ok()?; + if !status.success() { + return None; + } + + match read { + LimitedRead::Complete(bytes) => String::from_utf8(bytes).ok(), + LimitedRead::Empty => None, + LimitedRead::Oversized => unreachable!("oversized clipboard text is handled before wait"), + } +} + fn run_clipboard_command(command: &ClipboardCommand, bytes: &[u8]) -> bool { let mut child = match Command::new(command.program) .args(command.args) @@ -408,6 +483,44 @@ mod tests { } #[test] + fn read_clipboard_text_commands_include_session_backends() { + let _guard = env_lock().lock().unwrap(); + unsafe { + std::env::set_var("WAYLAND_DISPLAY", "wayland-0"); + std::env::set_var("DISPLAY", ":0"); + } + + let commands = read_clipboard_text_commands(); + assert_eq!(commands[0].program, "wl-paste"); + assert_eq!(commands[1].program, "wl-paste"); + assert_eq!(commands[2].program, "xclip"); + assert_eq!(commands[3].program, "xsel"); + } + + #[test] + fn read_clipboard_text_with_command_reads_utf8() { + let command = ClipboardCommand { + program: "printf", + args: &["feature/linear-302"], + }; + + assert_eq!( + read_clipboard_text_with_command(&command).as_deref(), + Some("feature/linear-302") + ); + } + + #[test] + fn read_clipboard_text_with_command_rejects_oversized_output() { + let command = ClipboardCommand { + program: "sh", + args: &["-c", "yes x | head -c 1048578"], + }; + + assert_eq!(read_clipboard_text_with_command(&command), None); + } + + #[test] fn read_clipboard_image_with_spawned_command_reads_under_limit() { let mut command = Command::new("sh"); command.arg("-c").arg("printf image"); |
