diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index bc322e6a..1a26ca14 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -10,7 +10,8 @@ use zellij_tile_utils::style; use first_line::{ctrl_keys, superkey}; use second_line::{ - fullscreen_panes_to_hide, keybinds, locked_fullscreen_panes_to_hide, text_copied_hint, + fullscreen_panes_to_hide, keybinds, locked_fullscreen_panes_to_hide, system_clipboard_error, + text_copied_hint, }; use tip::utils::get_cached_tip_name; @@ -24,6 +25,7 @@ struct State { tip_name: String, mode_info: ModeInfo, diplay_text_copied_hint: bool, + display_system_clipboard_failure: bool, } register_plugin!(State); @@ -142,6 +144,7 @@ impl ZellijPlugin for State { EventType::TabUpdate, EventType::CopyToClipboard, EventType::InputReceived, + EventType::SystemClipboardFailure, ]); } @@ -156,8 +159,12 @@ impl ZellijPlugin for State { Event::CopyToClipboard => { self.diplay_text_copied_hint = true; } + Event::SystemClipboardFailure => { + self.display_system_clipboard_failure = true; + } Event::InputReceived => { self.diplay_text_copied_hint = false; + self.display_system_clipboard_failure = false; } _ => {} } @@ -188,12 +195,16 @@ impl ZellijPlugin for State { if t.is_fullscreen_active { second_line = if self.diplay_text_copied_hint { text_copied_hint(&self.mode_info.palette) + } else if self.display_system_clipboard_failure { + system_clipboard_error(&self.mode_info.palette) } else { fullscreen_panes_to_hide(&self.mode_info.palette, t.panes_to_hide) } } else { second_line = if self.diplay_text_copied_hint { text_copied_hint(&self.mode_info.palette) + } else if self.display_system_clipboard_failure { + system_clipboard_error(&self.mode_info.palette) } else { keybinds(&self.mode_info, &self.tip_name, cols) } @@ -203,6 +214,8 @@ impl ZellijPlugin for State { if t.is_fullscreen_active { second_line = if self.diplay_text_copied_hint { text_copied_hint(&self.mode_info.palette) + } else if self.display_system_clipboard_failure { + system_clipboard_error(&self.mode_info.palette) } else { locked_fullscreen_panes_to_hide( &self.mode_info.palette, @@ -212,6 +225,8 @@ impl ZellijPlugin for State { } else { second_line = if self.diplay_text_copied_hint { text_copied_hint(&self.mode_info.palette) + } else if self.display_system_clipboard_failure { + system_clipboard_error(&self.mode_info.palette) } else { keybinds(&self.mode_info, &self.tip_name, cols) } @@ -220,6 +235,8 @@ impl ZellijPlugin for State { _ => { second_line = if self.diplay_text_copied_hint { text_copied_hint(&self.mode_info.palette) + } else if self.display_system_clipboard_failure { + system_clipboard_error(&self.mode_info.palette) } else { keybinds(&self.mode_info, &self.tip_name, cols) } diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 6f3c6461..32a44425 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -241,6 +241,18 @@ pub fn text_copied_hint(palette: &Palette) -> LinePart { } } +pub fn system_clipboard_error(palette: &Palette) -> LinePart { + let hint = " Error using the system clipboard."; + let red_color = match palette.red { + PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), + PaletteColor::EightBit(color) => Fixed(color), + }; + LinePart { + part: Style::new().fg(red_color).bold().paint(hint).to_string(), + len: hint.len(), + } +} + pub fn fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> LinePart { let white_color = match palette.white { PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index cfc99518..71ae0805 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -193,6 +193,7 @@ pub(crate) struct Screen { colors: Palette, draw_pane_frames: bool, session_is_mirrored: bool, + copy_command: Option, } impl Screen { @@ -204,6 +205,7 @@ impl Screen { mode_info: ModeInfo, draw_pane_frames: bool, session_is_mirrored: bool, + copy_command: Option, ) -> Self { Screen { bus, @@ -219,6 +221,7 @@ impl Screen { default_mode_info: mode_info, draw_pane_frames, session_is_mirrored, + copy_command, } } @@ -491,6 +494,7 @@ impl Screen { self.connected_clients.clone(), self.session_is_mirrored, client_id, + self.copy_command.clone(), ); tab.apply_layout(layout, new_pids, tab_index, client_id); if self.session_is_mirrored { @@ -692,6 +696,7 @@ pub(crate) fn screen_thread_main( ), draw_pane_frames, session_is_mirrored, + config_options.copy_command, ); loop { let (event, mut err_ctx) = screen diff --git a/zellij-server/src/tab/copy_command.rs b/zellij-server/src/tab/copy_command.rs new file mode 100644 index 00000000..61240950 --- /dev/null +++ b/zellij-server/src/tab/copy_command.rs @@ -0,0 +1,39 @@ +use std::io::prelude::*; +use std::process::{Command, Stdio}; + +pub struct CopyCommand { + command: String, + args: Vec, +} + +impl CopyCommand { + pub fn new(command: String) -> Self { + let mut command_with_args = command.split(' ').map(String::from); + + Self { + command: command_with_args.next().expect("missing command"), + args: command_with_args.collect(), + } + } + pub fn set(&self, value: String) -> bool { + let process = match Command::new(self.command.clone()) + .args(self.args.clone()) + .stdin(Stdio::piped()) + .spawn() + { + Err(why) => { + eprintln!("couldn't spawn {}: {}", self.command, why); + return false; + } + Ok(process) => process, + }; + + match process.stdin.unwrap().write_all(value.as_bytes()) { + Err(why) => { + eprintln!("couldn't write to {} stdin: {}", self.command, why); + false + } + Ok(_) => true, + } + } +} diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 2e98915a..cc83d37c 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -1,9 +1,11 @@ //! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size, //! as well as how they should be resized +mod copy_command; mod pane_grid; mod pane_resizer; +use copy_command::CopyCommand; use zellij_utils::position::{Column, Line}; use zellij_utils::{position::Position, serde, zellij_tile}; @@ -119,6 +121,7 @@ pub(crate) struct Tab { session_is_mirrored: bool, pending_vte_events: HashMap>, selecting_with_mouse: bool, + copy_command: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] @@ -299,6 +302,7 @@ impl Tab { connected_clients_in_app: Rc>>, session_is_mirrored: bool, client_id: ClientId, + copy_command: Option, ) -> Self { let panes = BTreeMap::new(); @@ -335,6 +339,7 @@ impl Tab { connected_clients_in_app, connected_clients, selecting_with_mouse: false, + copy_command, } } @@ -1912,11 +1917,20 @@ impl Tab { fn write_selection_to_clipboard(&self, selection: &str) { let mut output = Output::default(); + let mut system_clipboard_failure = false; output.add_clients(&self.connected_clients); - output.push_str_to_multiple_clients( - &format!("\u{1b}]52;c;{}\u{1b}\\", base64::encode(selection)), - self.connected_clients.iter().copied(), - ); + match self.copy_command.clone() { + Some(copy_command) => { + let system_clipboard = CopyCommand::new(copy_command); + system_clipboard_failure = !system_clipboard.set(selection.to_owned()); + } + None => { + output.push_str_to_multiple_clients( + &format!("\u{1b}]52;c;{}\u{1b}\\", base64::encode(selection)), + self.connected_clients.iter().copied(), + ); + } + } // TODO: ideally we should be sending the Render instruction from the screen self.senders @@ -1926,7 +1940,11 @@ impl Tab { .send_to_plugin(PluginInstruction::Update( None, None, - Event::CopyToClipboard, + if system_clipboard_failure { + Event::SystemClipboardFailure + } else { + Event::CopyToClipboard + }, )) .unwrap(); } diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs index 13b97124..5ac63f6b 100644 --- a/zellij-server/src/tab/unit/tab_tests.rs +++ b/zellij-server/src/tab/unit/tab_tests.rs @@ -96,6 +96,7 @@ fn create_new_tab(size: Size) -> Tab { let mut connected_clients = HashSet::new(); connected_clients.insert(client_id); let connected_clients = Rc::new(RefCell::new(connected_clients)); + let copy_command = None; let mut tab = Tab::new( index, position, @@ -110,6 +111,7 @@ fn create_new_tab(size: Size) -> Tab { connected_clients, session_is_mirrored, client_id, + copy_command, ); tab.apply_layout( LayoutTemplate::default().try_into().unwrap(), diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index ca34645d..1fbc85fc 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -91,6 +91,7 @@ fn create_new_screen(size: Size) -> Screen { let mode_info = ModeInfo::default(); let draw_pane_frames = false; let session_is_mirrored = true; + let copy_command = None; Screen::new( bus, &client_attributes, @@ -98,6 +99,7 @@ fn create_new_screen(size: Size) -> Screen { mode_info, draw_pane_frames, session_is_mirrored, + copy_command, ) } diff --git a/zellij-tile/src/data.rs b/zellij-tile/src/data.rs index 36b44e25..e120748a 100644 --- a/zellij-tile/src/data.rs +++ b/zellij-tile/src/data.rs @@ -76,6 +76,7 @@ pub enum Event { Mouse(Mouse), Timer(f64), CopyToClipboard, + SystemClipboardFailure, InputReceived, Visible(bool), } diff --git a/zellij-utils/src/input/options.rs b/zellij-utils/src/input/options.rs index dcaad306..d8bef694 100644 --- a/zellij-utils/src/input/options.rs +++ b/zellij-utils/src/input/options.rs @@ -74,6 +74,11 @@ pub struct Options { pub on_force_close: Option, #[structopt(long)] pub scroll_buffer_size: Option, + + /// Switch to using a user supplied command for clipboard instead of OSC52 + #[structopt(long)] + #[serde(default)] + pub copy_command: Option, } impl Options { @@ -99,6 +104,7 @@ impl Options { let theme = other.theme.or_else(|| self.theme.clone()); let on_force_close = other.on_force_close.or(self.on_force_close); let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size); + let copy_command = other.copy_command.or_else(|| self.copy_command.clone()); Options { simplified_ui, @@ -111,6 +117,7 @@ impl Options { mirror_session, on_force_close, scroll_buffer_size, + copy_command, } } @@ -140,6 +147,7 @@ impl Options { let theme = other.theme.or_else(|| self.theme.clone()); let on_force_close = other.on_force_close.or(self.on_force_close); let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size); + let copy_command = other.copy_command.or_else(|| self.copy_command.clone()); Options { simplified_ui, @@ -152,6 +160,7 @@ impl Options { mirror_session, on_force_close, scroll_buffer_size, + copy_command, } } @@ -200,6 +209,7 @@ impl From for Options { mirror_session: opts.mirror_session, on_force_close: opts.on_force_close, scroll_buffer_size: opts.scroll_buffer_size, + copy_command: opts.copy_command, } } }