diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-20 02:51:59 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-20 02:51:59 +0300 |
| commit | ed478be9835e984c42bc1e30fe84b5fb01a8c5d4 (patch) | |
| tree | 353b6ea5c7368a9f3b477bc8f1bbcd248e377577 /src/platform | |
| parent | 2b97799b6715fd6c355db9c225029765971915e6 (diff) | |
fix: bridge remote clipboard image paste
refs #205
Diffstat (limited to 'src/platform')
| -rw-r--r-- | src/platform/fallback.rs | 7 | ||||
| -rw-r--r-- | src/platform/linux.rs | 45 | ||||
| -rw-r--r-- | src/platform/macos.rs | 43 | ||||
| -rw-r--r-- | src/platform/mod.rs | 6 |
4 files changed, 98 insertions, 3 deletions
diff --git a/src/platform/fallback.rs b/src/platform/fallback.rs index bf3ac42b..825aaccd 100644 --- a/src/platform/fallback.rs +++ b/src/platform/fallback.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use super::{ForegroundJob, Signal}; +use super::{ClipboardImage, ForegroundJob, Signal}; /// Unsupported platform stub. pub fn foreground_job(_child_pid: u32) -> Option<ForegroundJob> { @@ -36,6 +36,11 @@ pub fn write_clipboard(_bytes: &[u8]) -> bool { } /// Unsupported platform stub. +pub fn read_clipboard_image() -> Option<ClipboardImage> { + None +} + +/// Unsupported platform stub. pub fn show_desktop_notification(_title: &str, _body: Option<&str>) -> std::io::Result<bool> { Ok(false) } diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 6823cca8..4d2ed049 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -4,7 +4,7 @@ use std::{ process::{Command, Stdio}, }; -use super::{ClipboardCommand, ForegroundJob, ForegroundProcess, Signal}; +use super::{ClipboardCommand, ClipboardImage, ForegroundJob, ForegroundProcess, Signal}; /// Collect the foreground terminal job for a given child PID. pub fn foreground_job(child_pid: u32) -> Option<ForegroundJob> { @@ -155,6 +155,34 @@ pub fn write_clipboard(bytes: &[u8]) -> bool { false } +pub fn read_clipboard_image() -> Option<ClipboardImage> { + for (mime, extension) in [ + ("image/png", "png"), + ("image/jpeg", "jpg"), + ("image/jpg", "jpg"), + ("image/gif", "gif"), + ("image/webp", "webp"), + ("image/bmp", "bmp"), + ] { + if std::env::var_os("WAYLAND_DISPLAY").is_some() { + if let Some(bytes) = read_clipboard_image_with_command("wl-paste", &["--type", mime]) { + return Some(ClipboardImage { bytes, extension }); + } + } + + if std::env::var_os("DISPLAY").is_some() { + if let Some(bytes) = read_clipboard_image_with_command( + "xclip", + &["-selection", "clipboard", "-t", mime, "-o"], + ) { + return Some(ClipboardImage { bytes, extension }); + } + } + } + + None +} + /// Show a native desktop notification through libnotify's command-line helper. pub fn show_desktop_notification(title: &str, body: Option<&str>) -> std::io::Result<bool> { show_desktop_notification_with_command(title, body, |program| Command::new(program)) @@ -192,6 +220,21 @@ fn run_notification_command(mut command: Command) -> std::io::Result<bool> { Ok(status.success()) } +fn read_clipboard_image_with_command(program: &str, args: &[&str]) -> Option<Vec<u8>> { + let output = Command::new(program) + .args(args) + .stdin(Stdio::null()) + .stderr(Stdio::null()) + .output() + .ok()?; + + if output.status.success() && !output.stdout.is_empty() { + Some(output.stdout) + } else { + None + } +} + fn clipboard_commands() -> Vec<ClipboardCommand> { let mut commands = Vec::new(); diff --git a/src/platform/macos.rs b/src/platform/macos.rs index f085ee91..d0cf43c8 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::sync::OnceLock; -use super::{ClipboardCommand, ForegroundJob, ForegroundProcess, Signal}; +use super::{ClipboardCommand, ClipboardImage, ForegroundJob, ForegroundProcess, Signal}; const PROC_PGRP_ONLY: u32 = 2; @@ -202,6 +202,47 @@ pub fn write_clipboard(bytes: &[u8]) -> bool { ) } +pub fn read_clipboard_image() -> Option<ClipboardImage> { + let path = std::env::temp_dir().join(format!( + "herdr-clipboard-image-{}-{}.png", + std::process::id(), + unique_timestamp_nanos() + )); + let script = format!( + "set png_data to (the clipboard as «class PNGf»)\nset fp to open for access POSIX file \"{}\" with write permission\nwrite png_data to fp\nclose access fp", + path.display() + ); + + let status = Command::new("osascript") + .arg("-e") + .arg(script) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .ok()?; + + if !status.success() { + let _ = std::fs::remove_file(&path); + return None; + } + + let bytes = std::fs::read(&path).ok(); + let _ = std::fs::remove_file(&path); + let bytes = bytes?; + (!bytes.is_empty()).then_some(ClipboardImage { + bytes, + extension: "png", + }) +} + +fn unique_timestamp_nanos() -> u128 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|duration| duration.as_nanos()) + .unwrap_or(0) +} + /// Show a native macOS notification. /// /// Prefer `terminal-notifier` when it is installed because it can activate the diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 58675d33..00fa8833 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -30,6 +30,12 @@ pub struct ClipboardCommand { pub args: &'static [&'static str], } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ClipboardImage { + pub bytes: Vec<u8>, + pub extension: &'static str, +} + #[cfg(target_os = "linux")] mod linux; #[cfg(target_os = "linux")] |
