aboutsummaryrefslogtreecommitdiff
path: root/src/platform
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-20 02:51:59 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-20 02:51:59 +0300
commited478be9835e984c42bc1e30fe84b5fb01a8c5d4 (patch)
tree353b6ea5c7368a9f3b477bc8f1bbcd248e377577 /src/platform
parent2b97799b6715fd6c355db9c225029765971915e6 (diff)
fix: bridge remote clipboard image paste
refs #205
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/fallback.rs7
-rw-r--r--src/platform/linux.rs45
-rw-r--r--src/platform/macos.rs43
-rw-r--r--src/platform/mod.rs6
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")]