feat(cli): allow starting a session detached (#3257)

* feat(cli): allow starting a session detached

* fix tests
This commit is contained in:
Aram Drevekenin 2024-04-12 15:39:36 +02:00 committed by GitHub
parent a0f48c6731
commit e68bc649d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 109 additions and 13 deletions

View File

@ -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,
);
}
}

View File

@ -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)

View File

@ -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<usize>,
pane_id_to_focus: Option<(u32, bool)>, // (pane_id, is_plugin)
is_a_reconnect: bool,
start_detached_and_exit: bool,
) -> Option<ConnectToSession> {
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<dyn ClientOsApi>,
opts: CliArgs,
config: Config,
config_options: Options,
info: ClientInfo,
layout: Option<Layout>,
) {
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;

View File

@ -317,8 +317,8 @@ impl Clone for Box<dyn ClientOsApi> {
}
pub fn get_client_os_input() -> Result<ClientOsInputOutput, nix::Error> {
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,

View File

@ -221,7 +221,7 @@ fn handle_openpty(
fn handle_terminal(
cmd: RunCommand,
failover_cmd: Option<RunCommand>,
orig_termios: termios::Termios,
orig_termios: Option<termios::Termios>,
quit_cb: Box<dyn Fn(PaneId, Option<i32>, 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<String>) {
/// set.
fn spawn_terminal(
terminal_action: TerminalAction,
orig_termios: termios::Termios,
orig_termios: Option<termios::Termios>,
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit_status
default_editor: Option<PathBuf>,
terminal_id: u32,
@ -418,7 +418,7 @@ impl ClientSender {
#[derive(Clone)]
pub struct ServerOsInputOutput {
orig_termios: Arc<Mutex<termios::Termios>>,
orig_termios: Arc<Mutex<Option<termios::Termios>>>,
client_senders: Arc<Mutex<HashMap<ClientId, ClientSender>>>,
terminal_id_to_raw_fd: Arc<Mutex<BTreeMap<u32, Option<RawFd>>>>, // A value of None means the
// terminal_id exists but is
@ -876,7 +876,10 @@ impl Clone for Box<dyn ServerOsApi> {
}
pub fn get_server_os_input() -> Result<ServerOsInputOutput, nix::Error> {
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,

View File

@ -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(),

View File

@ -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<usize>,