aboutsummaryrefslogtreecommitdiff
path: root/src/platform/linux.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-10 02:26:25 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-10 02:27:41 +0300
commit77fcde0a2736c3927d1bc589bd9b4d0173a9df46 (patch)
tree209c704342fb52a7ecbb5fe0cdbc291f7650e720 /src/platform/linux.rs
parent0facd819b4747c7634040b561509137c2608b024 (diff)
fix: support paste in modal inputs
refs #302
Diffstat (limited to 'src/platform/linux.rs')
-rw-r--r--src/platform/linux.rs113
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");