From e68bc649d63d51178d224be3af7109ca324480f4 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Fri, 12 Apr 2024 15:39:36 +0200 Subject: feat(cli): allow starting a session detached (#3257) * feat(cli): allow starting a session detached * fix tests --- src/commands.rs | 26 ++++++++-- zellij-client/src/cli_client.rs | 4 +- zellij-client/src/lib.rs | 69 +++++++++++++++++++++++++ zellij-client/src/os_input_output.rs | 4 +- zellij-server/src/os_input_output.rs | 13 +++-- zellij-server/src/unit/os_input_output_tests.rs | 2 +- zellij-utils/src/cli.rs | 4 ++ 7 files changed, 109 insertions(+), 13 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index d37a422a0..e0dbbdc2e 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -407,6 +407,7 @@ pub(crate) fn start_client(opts: CliArgs) { let mut config_options = config_options.clone(); let mut opts = opts.clone(); let mut is_a_reconnect = false; + let mut should_create_detached = false; if let Some(reconnect_to_session) = &reconnect_to_session { // this is integration code to make session reconnects work with this existing, @@ -417,6 +418,7 @@ pub(crate) fn start_client(opts: CliArgs) { opts.command = Some(Command::Sessions(Sessions::Attach { session_name: reconnect_to_session.name.clone(), create: true, + background: false, force_run_commands: false, index: None, options: None, @@ -476,6 +478,7 @@ pub(crate) fn start_client(opts: CliArgs) { if let Some(Command::Sessions(Sessions::Attach { session_name, create, + background, force_run_commands, index, options, @@ -487,9 +490,14 @@ pub(crate) fn start_client(opts: CliArgs) { }, None => config_options, }; + should_create_detached = background; let client = if let Some(idx) = index { - attach_with_session_index(config_options.clone(), idx, create) + attach_with_session_index( + config_options.clone(), + idx, + create || should_create_detached, + ) } else { let session_exists = session_name .as_ref() @@ -497,7 +505,10 @@ pub(crate) fn start_client(opts: CliArgs) { .unwrap_or(false); let resurrection_layout = session_name.as_ref().and_then(|s| resurrection_layout(&s)); - if create && !session_exists && resurrection_layout.is_none() { + if (create || should_create_detached) + && !session_exists + && resurrection_layout.is_none() + { session_name.clone().map(start_client_plan); } match (session_name.as_ref(), resurrection_layout) { @@ -507,7 +518,11 @@ pub(crate) fn start_client(opts: CliArgs) { } ClientInfo::Resurrect(session_name.clone(), resurrection_layout) }, - _ => attach_with_session_name(session_name, config_options.clone(), create), + _ => attach_with_session_name( + session_name, + config_options.clone(), + create || should_create_detached, + ), } }; @@ -541,6 +556,7 @@ pub(crate) fn start_client(opts: CliArgs) { tab_position_to_focus, pane_id_to_focus, is_a_reconnect, + should_create_detached, ); } else { if let Some(session_name) = opts.session.clone() { @@ -555,6 +571,7 @@ pub(crate) fn start_client(opts: CliArgs) { None, None, is_a_reconnect, + should_create_detached, ); } else { if let Some(session_name) = config_options.session_name.as_ref() { @@ -595,6 +612,7 @@ pub(crate) fn start_client(opts: CliArgs) { None, None, is_a_reconnect, + should_create_detached, ); }, _ => { @@ -609,6 +627,7 @@ pub(crate) fn start_client(opts: CliArgs) { None, None, is_a_reconnect, + should_create_detached, ); }, } @@ -632,6 +651,7 @@ pub(crate) fn start_client(opts: CliArgs) { None, None, is_a_reconnect, + should_create_detached, ); } } diff --git a/zellij-client/src/cli_client.rs b/zellij-client/src/cli_client.rs index 6d25e21b9..30e406031 100644 --- a/zellij-client/src/cli_client.rs +++ b/zellij-client/src/cli_client.rs @@ -138,8 +138,8 @@ fn pipe_client( let mut buffer = String::new(); let _ = stdin.read_line(&mut buffer); if buffer.is_empty() { - // TODO: consider notifying the relevant plugin that the pipe has ended with a - // specialized message + let msg = create_msg(None); + os_input.send_to_server(msg); break; } else { // we've got data! send it down the pipe (most common) diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 2d74e0dd4..5e2f80603 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -29,6 +29,7 @@ use zellij_utils::{ errors::{ClientContext, ContextType, ErrorInstruction}, input::{config::Config, options::Options}, ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, + pane_size::Size, termwiz::input::InputEvent, }; use zellij_utils::{cli::CliArgs, input::layout::Layout}; @@ -168,7 +169,12 @@ pub fn start_client( tab_position_to_focus: Option, pane_id_to_focus: Option<(u32, bool)>, // (pane_id, is_plugin) is_a_reconnect: bool, + start_detached_and_exit: bool, ) -> Option { + if start_detached_and_exit { + start_server_detached(os_input, opts, config, config_options, info, layout); + return None; + } info!("Starting Zellij client!"); let mut reconnect_to_session = None; @@ -541,6 +547,69 @@ pub fn start_client( reconnect_to_session } +pub fn start_server_detached( + mut os_input: Box, + opts: CliArgs, + config: Config, + config_options: Options, + info: ClientInfo, + layout: Option, +) { + envs::set_zellij("0".to_string()); + config.env.set_vars(); + + let palette = config + .theme_config(&config_options) + .unwrap_or_else(|| os_input.load_palette()); + + let client_attributes = ClientAttributes { + size: Size { rows: 50, cols: 50 }, // just so size is not 0, it doesn't matter because we + // immediately detach + style: Style { + colors: palette, + rounded_corners: config.ui.pane_frames.rounded_corners, + hide_session_name: config.ui.pane_frames.hide_session_name, + }, + keybinds: config.keybinds.clone(), + }; + + let create_ipc_pipe = || -> std::path::PathBuf { + let mut sock_dir = ZELLIJ_SOCK_DIR.clone(); + std::fs::create_dir_all(&sock_dir).unwrap(); + set_permissions(&sock_dir, 0o700).unwrap(); + sock_dir.push(envs::get_session_name().unwrap()); + sock_dir + }; + + let (first_msg, ipc_pipe) = match info { + ClientInfo::New(name) | ClientInfo::Resurrect(name, _) => { + envs::set_session_name(name.clone()); + os_input.update_session_name(name); + let ipc_pipe = create_ipc_pipe(); + + spawn_server(&*ipc_pipe, opts.debug).unwrap(); + + ( + ClientToServerMsg::NewClient( + client_attributes, + Box::new(opts), + Box::new(config_options.clone()), + Box::new(layout.unwrap()), + Box::new(config.plugins.clone()), + ), + ipc_pipe, + ) + }, + _ => { + eprintln!("Session already exists"); + std::process::exit(1); + }, + }; + + os_input.connect_to_server(&*ipc_pipe); + os_input.send_to_server(first_msg); +} + #[cfg(test)] #[path = "./unit/stdin_tests.rs"] mod stdin_tests; diff --git a/zellij-client/src/os_input_output.rs b/zellij-client/src/os_input_output.rs index 1bfd6666a..47dcafb09 100644 --- a/zellij-client/src/os_input_output.rs +++ b/zellij-client/src/os_input_output.rs @@ -317,8 +317,8 @@ impl Clone for Box { } pub fn get_client_os_input() -> Result { - let current_termios = termios::tcgetattr(0)?; - let orig_termios = Some(Arc::new(Mutex::new(current_termios))); + let current_termios = termios::tcgetattr(0).ok(); + let orig_termios = current_termios.map(|termios| Arc::new(Mutex::new(termios))); let reading_from_stdin = Arc::new(Mutex::new(None)); Ok(ClientOsInputOutput { orig_termios, diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index a2d9e36a8..640b6fbc9 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -221,7 +221,7 @@ fn handle_openpty( fn handle_terminal( cmd: RunCommand, failover_cmd: Option, - orig_termios: termios::Termios, + orig_termios: Option, quit_cb: Box, RunCommand) + Send>, terminal_id: u32, ) -> Result<(RawFd, RawFd)> { @@ -229,7 +229,7 @@ fn handle_terminal( // Create a pipe to allow the child the communicate the shell's pid to its // parent. - match openpty(None, Some(&orig_termios)) { + match openpty(None, &orig_termios) { Ok(open_pty_res) => handle_openpty(open_pty_res, cmd, quit_cb, terminal_id), Err(e) => match failover_cmd { Some(failover_cmd) => { @@ -279,7 +279,7 @@ fn separate_command_arguments(command: &mut PathBuf, args: &mut Vec) { /// set. fn spawn_terminal( terminal_action: TerminalAction, - orig_termios: termios::Termios, + orig_termios: Option, quit_cb: Box, RunCommand) + Send>, // u32 is the exit_status default_editor: Option, terminal_id: u32, @@ -418,7 +418,7 @@ impl ClientSender { #[derive(Clone)] pub struct ServerOsInputOutput { - orig_termios: Arc>, + orig_termios: Arc>>, client_senders: Arc>>, terminal_id_to_raw_fd: Arc>>>, // A value of None means the // terminal_id exists but is @@ -876,7 +876,10 @@ impl Clone for Box { } pub fn get_server_os_input() -> Result { - let current_termios = termios::tcgetattr(0)?; + let current_termios = termios::tcgetattr(0).ok(); + if current_termios.is_none() { + log::warn!("Starting a server without a controlling terminal, using the default termios configuration."); + } let orig_termios = Arc::new(Mutex::new(current_termios)); Ok(ServerOsInputOutput { orig_termios, diff --git a/zellij-server/src/unit/os_input_output_tests.rs b/zellij-server/src/unit/os_input_output_tests.rs index 7e6510835..31892525d 100644 --- a/zellij-server/src/unit/os_input_output_tests.rs +++ b/zellij-server/src/unit/os_input_output_tests.rs @@ -36,7 +36,7 @@ fn get_cwd() { termios::tcgetattr(test_terminal.slave()).expect("Could not configure the termios"); let server = ServerOsInputOutput { - orig_termios: Arc::new(Mutex::new(test_termios)), + orig_termios: Arc::new(Mutex::new(Some(test_termios))), client_senders: Arc::default(), terminal_id_to_raw_fd: Arc::default(), cached_resizes: Arc::default(), diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index 0388e5999..ba1898439 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -125,6 +125,10 @@ pub enum Sessions { #[clap(short, long, value_parser)] create: bool, + /// Create a detached session in the background if one does not exist + #[clap(short, long, value_parser)] + background: bool, + /// Number of the session index in the active sessions ordered creation date. #[clap(long, value_parser)] index: Option, -- cgit v1.2.3