aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-03 22:19:47 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-03 22:19:47 +0300
commit6f148588dde3b330618981c344c12f400d74da7d (patch)
tree49e296f55a781bb3e4f2759311c667f2115b3a61
parent6ea68f9af2569ea87b7d243c5bbe4f3bc4c8fa44 (diff)
fix: scope empty image paste bridge to remote
refs #986
-rw-r--r--docs/next/CHANGELOG.md1
-rw-r--r--docs/next/website/src/content/docs/configuration.mdx2
-rw-r--r--src/client/mod.rs33
3 files changed, 30 insertions, 6 deletions
diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md
index db771584..310e1da1 100644
--- a/docs/next/CHANGELOG.md
+++ b/docs/next/CHANGELOG.md
@@ -15,6 +15,7 @@
- Bumped the client/server protocol version to 15 for socket API placement mutation event and response compatibility.
### Fixed
+- Unix local Herdr clients no longer treat empty bracketed paste as a clipboard-image bridge; `herdr --remote` keeps using it for local-desktop image paste over SSH. (#986)
- Plain PageUp/PageDown now reach primary-screen pager apps such as `less -X` and Git diff when they enter application cursor mode, while shell transcripts still use Herdr pane scrollback. (#953)
- `prefix+e` scrollback editor panes now open on Windows without trying to run `/bin/sh`; Windows uses `VISUAL`, then `EDITOR`, then `notepad.exe` as the fallback editor. (#914)
- `herdr pane split --current` now resolves to the calling Herdr pane instead of the UI-focused pane when run inside a pane. (#902)
diff --git a/docs/next/website/src/content/docs/configuration.mdx b/docs/next/website/src/content/docs/configuration.mdx
index f7bcb616..0305a670 100644
--- a/docs/next/website/src/content/docs/configuration.mdx
+++ b/docs/next/website/src/content/docs/configuration.mdx
@@ -198,7 +198,7 @@ next_tab = ["prefix+n", "ctrl+alt+]"]
`last_pane` switches back to the last focused pane across workspaces and tabs. It is unset by default because the tmux-style pane binding `prefix+l` is already used for pane-right focus.
-`remote_image_paste` is only active in `herdr --remote`. It is the local-client shortcut that sends a local clipboard image to the remote pane. Set it to an empty string to disable the raw-key shortcut; terminal paste-image signals still work when the outer terminal sends them.
+`remote_image_paste` is only active in `herdr --remote`. It is the local-client shortcut that sends a local clipboard image to the remote pane. Set it to an empty string to disable the raw-key shortcut; remote terminal paste-image signals still work when the outer terminal sends them.
Key strings accept plain keys, modifier combinations such as `ctrl+a`, `shift+n`, `alt+1`, `cmd+k`, and special keys such as `enter`, `tab`, `esc`, `left`, `right`, `up`, and `down`. Named punctuation such as `minus`, `comma`, `ampersand`, `plus`, and `backtick` is also accepted. Plain direct printable keys such as `n` are unsafe because they intercept typing; use `prefix+n` unless you intentionally want a direct binding. The `navigate_workspace_*` and `navigate_pane_*` fields are navigate-mode-only and may use plain keys such as `j` or `k`; they must not use `prefix+`, `esc`, `enter`, `tab`, `shift+tab`, `left`, `right`, or unmodified `1` through `9`. Left and right arrows are permanent aliases for pane-left and pane-right navigation. These navigate-mode shortcuts are independent from general action bindings such as `focus_pane_down = "prefix+j"`; when both use the same key, the navigate-mode shortcut wins while navigate mode is open. Alt, Cmd/Super, and punctuation with modifiers depend on your terminal and tmux settings.
diff --git a/src/client/mod.rs b/src/client/mod.rs
index ceed9826..6a70c79c 100644
--- a/src/client/mod.rs
+++ b/src/client/mod.rs
@@ -1278,6 +1278,8 @@ async fn run_client_loop(
#[cfg(windows)]
let _ = config.mouse_scroll_lines;
let draw_host_cursor = attach_escape.is_none() && should_draw_host_cursor(config.host_cursor);
+ #[cfg(unix)]
+ let is_remote_client = is_remote_client_process();
let mut state = ClientState {
blit_encoder: render_ansi::BlitEncoder::new(),
@@ -1409,7 +1411,11 @@ async fn run_client_loop(
}
data
};
- if should_bridge_clipboard_image_paste(&data, state.remote_image_paste_key) {
+ if should_bridge_clipboard_image_paste(
+ &data,
+ is_remote_client,
+ state.remote_image_paste_key,
+ ) {
if let Some(image) = crate::platform::read_clipboard_image() {
if image.bytes.len() > MAX_CLIPBOARD_IMAGE_PAYLOAD {
warn!(
@@ -1761,10 +1767,11 @@ fn sound_from_notify_message(message: &str) -> Option<crate::sound::Sound> {
#[cfg(unix)]
fn should_bridge_clipboard_image_paste(
data: &[u8],
+ is_remote_client: bool,
remote_image_paste_key: Option<(crossterm::event::KeyCode, crossterm::event::KeyModifiers)>,
) -> bool {
if data == b"\x1b[200~\x1b[201~" {
- return true;
+ return is_remote_client;
}
let Some(remote_image_paste_key) = remote_image_paste_key else {
@@ -2080,21 +2087,37 @@ mod tests {
#[test]
fn clipboard_image_paste_bridge_triggers_on_configured_key_and_empty_paste() {
let ctrl_v = crate::config::parse_key_combo("ctrl+v").unwrap();
- assert!(should_bridge_clipboard_image_paste(&[0x16], Some(ctrl_v)));
+ assert!(should_bridge_clipboard_image_paste(
+ &[0x16],
+ true,
+ Some(ctrl_v)
+ ));
assert!(should_bridge_clipboard_image_paste(
b"\x1b[118;5u",
+ true,
Some(ctrl_v)
));
assert!(should_bridge_clipboard_image_paste(
b"\x1b[200~\x1b[201~",
+ true,
None
));
assert!(!should_bridge_clipboard_image_paste(
+ b"\x1b[200~\x1b[201~",
+ false,
+ Some(ctrl_v)
+ ));
+ assert!(!should_bridge_clipboard_image_paste(
b"\x1b[200~text\x1b[201~",
+ true,
+ Some(ctrl_v)
+ ));
+ assert!(!should_bridge_clipboard_image_paste(&[0x16], true, None));
+ assert!(!should_bridge_clipboard_image_paste(
+ b"v",
+ true,
Some(ctrl_v)
));
- assert!(!should_bridge_clipboard_image_paste(&[0x16], None));
- assert!(!should_bridge_clipboard_image_paste(b"v", Some(ctrl_v)));
}
#[test]