From d6fc7b04d14e8eeb5157cace639cc1d157766987 Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Wed, 19 May 2021 18:09:22 +0530 Subject: [PATCH 01/15] add attach and list sessions subcommand to CliArgs --- src/main.rs | 6 +++--- zellij-client/src/lib.rs | 2 +- zellij-utils/src/cli.rs | 29 ++++++++++++++++++++++++++--- zellij-utils/src/input/config.rs | 6 +++--- zellij-utils/src/input/options.rs | 6 +++--- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index a970ce446..eb7f476b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use std::convert::TryFrom; use zellij_client::{os_input_output::get_client_os_input, start_client}; use zellij_server::{os_input_output::get_server_os_input, start_server}; use zellij_utils::{ - cli::{CliArgs, ConfigCli}, + cli::{CliArgs, Command}, consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, input::config::Config, logging::*, @@ -16,8 +16,8 @@ use zellij_utils::{ pub fn main() { let opts = CliArgs::from_args(); - if let Some(ConfigCli::Setup(setup)) = opts.option.clone() { - Setup::from_cli(&setup, &opts).expect("Failed to print to stdout"); + if let Some(Command::Setup(ref setup)) = opts.command { + Setup::from_cli(setup, &opts).expect("Failed to print to stdout"); } let config = match Config::try_from(&opts) { diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 9deb415e6..4ff377063 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -101,7 +101,7 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C let mut command_is_executing = CommandIsExecuting::new(); - let config_options = Options::from_cli(&config.options, opts.option.clone()); + let config_options = Options::from_cli(&config.options, opts.command.clone()); let full_screen_ws = os_input.get_terminal_size_using_fd(0); let client_attributes = ClientAttributes { diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index 4b2fdbbfb..87f929c7e 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -17,7 +17,7 @@ pub struct CliArgs { pub data_dir: Option, /// Run server listening at the specified socket path - #[structopt(long, parse(from_os_str))] + #[structopt(long, parse(from_os_str), hidden = true)] pub server: Option, /// Name of a layout file in the layout directory @@ -37,14 +37,14 @@ pub struct CliArgs { pub config_dir: Option, #[structopt(subcommand)] - pub option: Option, + pub command: Option, #[structopt(short, long)] pub debug: bool, } #[derive(Debug, StructOpt, Clone, Serialize, Deserialize)] -pub enum ConfigCli { +pub enum Command { /// Change the behaviour of zellij #[structopt(name = "options")] Options(Options), @@ -52,4 +52,27 @@ pub enum ConfigCli { /// Setup zellij and check its configuration #[structopt(name = "setup")] Setup(Setup), + + /// Explore existing zellij sessions + #[structopt(flatten)] + Sessions(Sessions), +} + +#[derive(Debug, StructOpt, Clone, Serialize, Deserialize)] +pub enum Sessions { + /// List active sessions + #[structopt(alias = "ls")] + ListSessions, + + /// Attach to session + #[structopt(alias = "a")] + Attach { + /// Name of the session to attach to. + session_name: String, + + /// Force attach- session will detach from the other + /// zellij client (if any) and attach to this. + #[structopt(long, short)] + force: bool, + }, } diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs index a03416890..c4cf0cd72 100644 --- a/zellij-utils/src/input/config.rs +++ b/zellij-utils/src/input/config.rs @@ -7,7 +7,7 @@ use std::path::{Path, PathBuf}; use super::keybinds::{Keybinds, KeybindsFromYaml}; use super::options::Options; -use crate::cli::{CliArgs, ConfigCli}; +use crate::cli::{CliArgs, Command}; use crate::setup; use serde::{Deserialize, Serialize}; @@ -60,7 +60,7 @@ impl TryFrom<&CliArgs> for Config { return Config::new(&path); } - if let Some(ConfigCli::Setup(setup)) = opts.option.clone() { + if let Some(Command::Setup(ref setup)) = opts.command { if setup.clean { return Config::from_default_assets(); } @@ -179,7 +179,7 @@ mod config_test { fn try_from_cli_args_with_option_clean() { use crate::setup::Setup; let mut opts = CliArgs::default(); - opts.option = Some(ConfigCli::Setup(Setup { + opts.command = Some(Command::Setup(Setup { clean: true, ..Setup::default() })); diff --git a/zellij-utils/src/input/options.rs b/zellij-utils/src/input/options.rs index 33625b654..f9724c20f 100644 --- a/zellij-utils/src/input/options.rs +++ b/zellij-utils/src/input/options.rs @@ -1,5 +1,5 @@ //! Handles cli and configuration options -use crate::cli::ConfigCli; +use crate::cli::Command; use serde::{Deserialize, Serialize}; use structopt::StructOpt; @@ -35,8 +35,8 @@ impl Options { Options { simplified_ui } } - pub fn from_cli(&self, other: Option) -> Options { - if let Some(ConfigCli::Options(options)) = other { + pub fn from_cli(&self, other: Option) -> Options { + if let Some(Command::Options(options)) = other { Options::merge(&self, options) } else { self.to_owned() From d231d28d7cdf7df1f44268627f5630edb19b21a0 Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Wed, 19 May 2021 20:01:09 +0530 Subject: [PATCH 02/15] Implement the minimal list-sessions command --- src/main.rs | 43 ++++++++++++++++++++++++++++++++------ zellij-utils/src/consts.rs | 12 +++++++---- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index eb7f476b9..122747b65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,13 @@ mod tests; use std::convert::TryFrom; +use std::os::unix::fs::FileTypeExt; +use std::{fs, io, process}; use zellij_client::{os_input_output::get_client_os_input, start_client}; use zellij_server::{os_input_output::get_server_os_input, start_server}; use zellij_utils::{ - cli::{CliArgs, Command}, - consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, + cli::{CliArgs, Command, Sessions}, + consts::{ZELLIJ_SOCK_DIR, ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, input::config::Config, logging::*, setup::Setup, @@ -16,7 +18,9 @@ use zellij_utils::{ pub fn main() { let opts = CliArgs::from_args(); - if let Some(Command::Setup(ref setup)) = opts.command { + if let Some(Command::Sessions(Sessions::ListSessions)) = opts.command { + list_sessions(); + } else if let Some(Command::Setup(ref setup)) = opts.command { Setup::from_cli(setup, &opts).expect("Failed to print to stdout"); } @@ -24,7 +28,7 @@ pub fn main() { Ok(config) => config, Err(e) => { eprintln!("There was an error in the config file:\n{}", e); - std::process::exit(1); + process::exit(1); } }; atomic_create_dir(&*ZELLIJ_TMP_DIR).unwrap(); @@ -34,7 +38,7 @@ pub fn main() { Ok(server_os_input) => server_os_input, Err(e) => { eprintln!("failed to open terminal:\n{}", e); - std::process::exit(1); + process::exit(1); } }; start_server(Box::new(os_input), path); @@ -43,9 +47,36 @@ pub fn main() { Ok(os_input) => os_input, Err(e) => { eprintln!("failed to open terminal:\n{}", e); - std::process::exit(1); + process::exit(1); } }; start_client(Box::new(os_input), opts, config); } } + +fn list_sessions() { + match fs::read_dir(&*ZELLIJ_SOCK_DIR) { + Ok(files) => { + let mut is_empty = true; + files.for_each(|file| { + let file = file.unwrap(); + if file.file_type().unwrap().is_socket() { + println!("{}", file.file_name().into_string().unwrap()); + is_empty = false; + } + }); + if is_empty { + println!("No active zellij sessions found."); + } + } + Err(err) => { + if let io::ErrorKind::NotFound = err.kind() { + println!("No active zellij sessions found."); + } else { + eprintln!("Error occured: {}", err); + process::exit(1); + } + } + } + process::exit(0); +} diff --git a/zellij-utils/src/consts.rs b/zellij-utils/src/consts.rs index 799e29132..012e36999 100644 --- a/zellij-utils/src/consts.rs +++ b/zellij-utils/src/consts.rs @@ -27,7 +27,7 @@ lazy_static! { pub static ref SESSION_NAME: String = names::Generator::default().next().unwrap(); pub static ref ZELLIJ_PROJ_DIR: ProjectDirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); - pub static ref ZELLIJ_IPC_PIPE: PathBuf = { + pub static ref ZELLIJ_SOCK_DIR: PathBuf = { let mut ipc_dir = env::var("ZELLIJ_SOCKET_DIR").map_or_else( |_| { ZELLIJ_PROJ_DIR @@ -37,11 +37,15 @@ lazy_static! { PathBuf::from, ); ipc_dir.push(VERSION); - fs::create_dir_all(&ipc_dir).unwrap(); - set_permissions(&ipc_dir).unwrap(); - ipc_dir.push(&*SESSION_NAME); ipc_dir }; + pub static ref ZELLIJ_IPC_PIPE: PathBuf = { + let mut sock_dir = ZELLIJ_SOCK_DIR.clone(); + fs::create_dir_all(&sock_dir).unwrap(); + set_permissions(&sock_dir).unwrap(); + sock_dir.push(&*SESSION_NAME); + sock_dir + }; pub static ref ZELLIJ_TMP_DIR: PathBuf = PathBuf::from("/tmp/zellij-".to_string() + &format!("{}", *UID)); pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log"); From 2487256664d4142ee1906f0058cf7e8063cb6e10 Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Wed, 19 May 2021 21:17:21 +0530 Subject: [PATCH 03/15] Mark current session in the output of list-sessions --- src/main.rs | 9 ++++++++- zellij-client/src/lib.rs | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 122747b65..e4651fd1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,10 +58,17 @@ fn list_sessions() { match fs::read_dir(&*ZELLIJ_SOCK_DIR) { Ok(files) => { let mut is_empty = true; + let session_name = std::env::var("ZELLIJ_SESSION_NAME").unwrap_or("".into()); files.for_each(|file| { let file = file.unwrap(); if file.file_type().unwrap().is_socket() { - println!("{}", file.file_name().into_string().unwrap()); + let fname = file.file_name().into_string().unwrap(); + let suffix = if session_name == fname { + " (current)" + } else { + "" + }; + println!("{}{}", fname, suffix); is_empty = false; } }); diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 4ff377063..8f192c388 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -17,7 +17,7 @@ use crate::{ use zellij_utils::cli::CliArgs; use zellij_utils::{ channels::{SenderType, SenderWithContext, SyncChannelWithContext}, - consts::ZELLIJ_IPC_PIPE, + consts::{SESSION_NAME, ZELLIJ_IPC_PIPE}, errors::{ClientContext, ContextType, ErrorInstruction}, input::config::Config, input::options::Options, @@ -95,6 +95,7 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C .write(clear_client_terminal_attributes.as_bytes()) .unwrap(); std::env::set_var(&"ZELLIJ", "0"); + std::env::set_var(&"ZELLIJ_SESSION_NAME", &*SESSION_NAME); #[cfg(not(any(feature = "test", test)))] spawn_server(&*ZELLIJ_IPC_PIPE).unwrap(); From 61aa1045764db63734532486cab0500e5828daad Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Fri, 21 May 2021 01:13:01 +0530 Subject: [PATCH 04/15] Add ability to detach a session --- src/main.rs | 2 +- src/tests/fakes.rs | 1 + zellij-server/src/lib.rs | 61 +++++++++++++++++++++------- zellij-server/src/os_input_output.rs | 6 +++ zellij-server/src/route.rs | 6 +-- zellij-server/src/screen.rs | 23 ++++++++--- zellij-server/src/tab.rs | 11 +++-- zellij-utils/src/errors.rs | 1 + zellij-utils/src/ipc.rs | 1 + 9 files changed, 85 insertions(+), 27 deletions(-) diff --git a/src/main.rs b/src/main.rs index e4651fd1f..82fc14d5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,7 +58,7 @@ fn list_sessions() { match fs::read_dir(&*ZELLIJ_SOCK_DIR) { Ok(files) => { let mut is_empty = true; - let session_name = std::env::var("ZELLIJ_SESSION_NAME").unwrap_or("".into()); + let session_name = std::env::var("ZELLIJ_SESSION_NAME").unwrap_or_else(|_| "".into()); files.for_each(|file| { let file = file.unwrap(); if file.file_type().unwrap().is_socket() { diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs index e9042ed73..c81e226ff 100644 --- a/src/tests/fakes.rs +++ b/src/tests/fakes.rs @@ -299,6 +299,7 @@ impl ServerOsApi for FakeInputOutput { self.send_instructions_to_client.send(msg).unwrap(); } fn add_client_sender(&self) {} + fn remove_client_sender(&self) {} fn update_receiver(&mut self, _stream: LocalSocketStream) {} fn load_palette(&self) -> Palette { default_palette() diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index df0eea65f..a52ba9f49 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -43,6 +43,7 @@ pub(crate) enum ServerInstruction { UnblockInputThread, ClientExit, Error(String), + DetachSession, } impl From for ServerInstruction { @@ -52,6 +53,7 @@ impl From for ServerInstruction { ClientToServerMsg::NewClient(pos, opts, options) => { ServerInstruction::NewClient(pos, opts, options) } + ClientToServerMsg::DetachSession => ServerInstruction::DetachSession, _ => unreachable!(), } } @@ -65,6 +67,7 @@ impl From<&ServerInstruction> for ServerContext { ServerInstruction::UnblockInputThread => ServerContext::UnblockInputThread, ServerInstruction::ClientExit => ServerContext::ClientExit, ServerInstruction::Error(_) => ServerContext::Error, + ServerInstruction::DetachSession => ServerContext::DetachSession, } } } @@ -94,6 +97,13 @@ impl Drop for SessionMetaData { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum SessionState { + Attached, + Detached, + Uninitialized, +} + pub fn start_server(os_input: Box, socket_path: PathBuf) { #[cfg(not(any(feature = "test", test)))] daemonize::Daemonize::new() @@ -107,7 +117,8 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { let (to_server, server_receiver): SyncChannelWithContext = mpsc::sync_channel(50); let to_server = SenderWithContext::new(SenderType::SyncSender(to_server)); - let sessions: Arc>> = Arc::new(RwLock::new(None)); + let session_data: Arc>> = Arc::new(RwLock::new(None)); + let session_state = Arc::new(RwLock::new(SessionState::Uninitialized)); #[cfg(not(any(feature = "test", test)))] std::panic::set_hook({ @@ -122,11 +133,11 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { thread::Builder::new() .name("server_router".to_string()) .spawn({ - let sessions = sessions.clone(); + let session_data = session_data.clone(); let os_input = os_input.clone(); let to_server = to_server.clone(); - move || route_thread_main(sessions, os_input, to_server) + move || route_thread_main(session_data, os_input, to_server) }) .unwrap(); #[cfg(not(any(feature = "test", test)))] @@ -138,7 +149,7 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { }; let os_input = os_input.clone(); - let sessions = sessions.clone(); + let session_data = session_data.clone(); let to_server = to_server.clone(); let socket_path = socket_path.clone(); move || { @@ -150,16 +161,16 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { Ok(stream) => { let mut os_input = os_input.clone(); os_input.update_receiver(stream); - let sessions = sessions.clone(); + let session_data = session_data.clone(); let to_server = to_server.clone(); thread::Builder::new() .name("server_router".to_string()) .spawn({ - let sessions = sessions.clone(); + let session_data = session_data.clone(); let os_input = os_input.clone(); let to_server = to_server.clone(); - move || route_thread_main(sessions, os_input, to_server) + move || route_thread_main(session_data, os_input, to_server) }) .unwrap(); } @@ -176,15 +187,17 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { err_ctx.add_call(ContextType::IPCServer((&instruction).into())); match instruction { ServerInstruction::NewClient(client_attributes, opts, config_options) => { - let session_data = init_session( + let session = init_session( os_input.clone(), opts, config_options, to_server.clone(), client_attributes, + session_state.clone(), ); - *sessions.write().unwrap() = Some(session_data); - sessions + *session_data.write().unwrap() = Some(session); + *session_state.write().unwrap() = SessionState::Attached; + session_data .read() .unwrap() .as_ref() @@ -194,18 +207,29 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { .unwrap(); } ServerInstruction::UnblockInputThread => { - os_input.send_to_client(ServerToClientMsg::UnblockInputThread); + if *session_state.read().unwrap() == SessionState::Attached { + os_input.send_to_client(ServerToClientMsg::UnblockInputThread); + } } ServerInstruction::ClientExit => { - *sessions.write().unwrap() = None; + *session_data.write().unwrap() = None; os_input.send_to_client(ServerToClientMsg::Exit); break; } + ServerInstruction::DetachSession => { + *session_state.write().unwrap() = SessionState::Detached; + os_input.send_to_client(ServerToClientMsg::Exit); + os_input.remove_client_sender(); + } ServerInstruction::Render(output) => { - os_input.send_to_client(ServerToClientMsg::Render(output)) + if *session_state.read().unwrap() == SessionState::Attached { + os_input.send_to_client(ServerToClientMsg::Render(output)); + } } ServerInstruction::Error(backtrace) => { - os_input.send_to_client(ServerToClientMsg::ServerError(backtrace)); + if *session_state.read().unwrap() == SessionState::Attached { + os_input.send_to_client(ServerToClientMsg::ServerError(backtrace)); + } break; } } @@ -220,6 +244,7 @@ fn init_session( config_options: Box, to_server: SenderWithContext, client_attributes: ClientAttributes, + session_state: Arc>, ) -> SessionMetaData { let (to_screen, screen_receiver): ChannelWithContext = mpsc::channel(); let to_screen = SenderWithContext::new(SenderType::Sender(to_screen)); @@ -285,7 +310,13 @@ fn init_session( let max_panes = opts.max_panes; move || { - screen_thread_main(screen_bus, max_panes, client_attributes, config_options); + screen_thread_main( + screen_bus, + max_panes, + client_attributes, + config_options, + session_state, + ); } }) .unwrap(); diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index be51878ce..333b41288 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -156,6 +156,8 @@ pub trait ServerOsApi: Send + Sync { fn send_to_client(&self, msg: ServerToClientMsg); /// Adds a sender to client fn add_client_sender(&self); + /// Removes the sender to client + fn remove_client_sender(&self); /// Update the receiver socket for the client fn update_receiver(&mut self, stream: LocalSocketStream); fn load_palette(&self) -> Palette; @@ -219,6 +221,10 @@ impl ServerOsApi for ServerOsInputOutput { .get_sender(); *self.send_instructions_to_client.lock().unwrap() = Some(sender); } + fn remove_client_sender(&self) { + assert!(self.send_instructions_to_client.lock().unwrap().is_some()); + *self.send_instructions_to_client.lock().unwrap() = None; + } fn update_receiver(&mut self, stream: LocalSocketStream) { self.receive_instructions_from_client = Some(Arc::new(Mutex::new(IpcReceiverWithContext::new(stream)))); diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 0187d9536..408e1e1e2 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -188,16 +188,16 @@ fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn Server } pub(crate) fn route_thread_main( - sessions: Arc>>, + session_data: Arc>>, os_input: Box, to_server: SenderWithContext, ) { loop { let (instruction, err_ctx) = os_input.recv_from_client(); err_ctx.update_thread_ctx(); - let rlocked_sessions = sessions.read().unwrap(); + let rlocked_sessions = session_data.read().unwrap(); match instruction { - ClientToServerMsg::ClientExit => { + ClientToServerMsg::ClientExit | ClientToServerMsg::DetachSession => { to_server.send(instruction.into()).unwrap(); break; } diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 830c42511..24b66199e 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -3,6 +3,7 @@ use std::collections::BTreeMap; use std::os::unix::io::RawFd; use std::str; +use std::sync::{Arc, RwLock}; use zellij_utils::zellij_tile; @@ -13,7 +14,7 @@ use crate::{ thread_bus::Bus, ui::layout::Layout, wasm_vm::PluginInstruction, - ServerInstruction, + ServerInstruction, SessionState, }; use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PluginCapabilities, TabInfo}; use zellij_utils::{ @@ -137,6 +138,7 @@ pub(crate) struct Screen { mode_info: ModeInfo, input_mode: InputMode, colors: Palette, + session_state: Arc>, } impl Screen { @@ -147,6 +149,7 @@ impl Screen { max_panes: Option, mode_info: ModeInfo, input_mode: InputMode, + session_state: Arc>, ) -> Self { Screen { bus, @@ -157,6 +160,7 @@ impl Screen { tabs: BTreeMap::new(), mode_info, input_mode, + session_state, } } @@ -177,6 +181,7 @@ impl Screen { self.mode_info.clone(), self.input_mode, self.colors, + self.session_state.clone(), ); self.active_tab_index = Some(tab_index); self.tabs.insert(tab_index, tab); @@ -261,10 +266,12 @@ impl Screen { .unwrap(); if self.tabs.is_empty() { self.active_tab_index = None; - self.bus - .senders - .send_to_server(ServerInstruction::Render(None)) - .unwrap(); + if *self.session_state.read().unwrap() == SessionState::Attached { + self.bus + .senders + .send_to_server(ServerInstruction::Render(None)) + .unwrap(); + } } else { for t in self.tabs.values_mut() { if t.position > active_tab.position { @@ -286,6 +293,9 @@ impl Screen { /// Renders this [`Screen`], which amounts to rendering its active [`Tab`]. pub fn render(&mut self) { + if *self.session_state.read().unwrap() != SessionState::Attached { + return; + } if let Some(active_tab) = self.get_active_tab_mut() { if active_tab.get_active_pane().is_some() { active_tab.render(); @@ -333,6 +343,7 @@ impl Screen { self.mode_info.clone(), self.input_mode, self.colors, + self.session_state.clone(), ); tab.apply_layout(layout, new_pids); self.active_tab_index = Some(tab_index); @@ -390,6 +401,7 @@ pub(crate) fn screen_thread_main( max_panes: Option, client_attributes: ClientAttributes, config_options: Box, + session_state: Arc>, ) { let capabilities = config_options.simplified_ui; @@ -405,6 +417,7 @@ pub(crate) fn screen_thread_main( ..ModeInfo::default() }, InputMode::Normal, + session_state, ); loop { let (event, mut err_ctx) = screen diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index e6953bfa2..68d01ec09 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -10,11 +10,11 @@ use crate::{ thread_bus::ThreadSenders, ui::{boundaries::Boundaries, layout::Layout, pane_resizer::PaneResizer}, wasm_vm::PluginInstruction, - ServerInstruction, + ServerInstruction, SessionState, }; use serde::{Deserialize, Serialize}; use std::os::unix::io::RawFd; -use std::sync::mpsc::channel; +use std::sync::{mpsc::channel, Arc, RwLock}; use std::time::Instant; use std::{ cmp::Reverse, @@ -74,6 +74,7 @@ pub(crate) struct Tab { pub senders: ThreadSenders, synchronize_is_active: bool, should_clear_display_before_rendering: bool, + session_state: Arc>, pub mode_info: ModeInfo, pub input_mode: InputMode, pub colors: Palette, @@ -242,6 +243,7 @@ impl Tab { mode_info: ModeInfo, input_mode: InputMode, colors: Palette, + session_state: Arc>, ) -> Self { let panes = if let Some(PaneId::Terminal(pid)) = pane_id { let new_terminal = TerminalPane::new(pid, *full_screen_ws, colors); @@ -273,6 +275,7 @@ impl Tab { mode_info, input_mode, colors, + session_state, } } @@ -722,7 +725,9 @@ impl Tab { self.panes.iter().any(|(_, p)| p.contains_widechar()) } pub fn render(&mut self) { - if self.active_terminal.is_none() { + if self.active_terminal.is_none() + || *self.session_state.read().unwrap() != SessionState::Attached + { // we might not have an active terminal if we closed the last pane // in that case, we should not render as the app is exiting return; diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 94a894c46..d7ca19fb1 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -260,4 +260,5 @@ pub enum ServerContext { UnblockInputThread, ClientExit, Error, + DetachSession, } diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index 725bf16b0..6e40fe1fc 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -58,6 +58,7 @@ pub enum ClientToServerMsg { TerminalResize(PositionAndSize), NewClient(ClientAttributes, Box, Box), Action(Action), + DetachSession, } // Types of messages sent from the server to the client From b8acf190710e97092def6ef3adfe44e992ca140c Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Fri, 21 May 2021 01:32:58 +0530 Subject: [PATCH 05/15] Use Action enum for Quit and detach instead of separate messages under ClientToServerMsg --- zellij-client/src/input_handler.rs | 4 +++- zellij-client/src/lib.rs | 6 ++---- zellij-server/src/lib.rs | 2 -- zellij-server/src/route.rs | 26 +++++++++++++++++++------- zellij-utils/src/input/actions.rs | 2 ++ zellij-utils/src/ipc.rs | 2 -- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index 4e2cff94c..9fa001cda 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -132,7 +132,9 @@ impl InputHandler { let mut should_break = false; match action { - Action::Quit => { + Action::Quit | Action::Detach => { + self.os_input + .send_to_server(ClientToServerMsg::Action(action)); self.exit(); should_break = true; } diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 8f192c388..0eff09af0 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -19,8 +19,7 @@ use zellij_utils::{ channels::{SenderType, SenderWithContext, SyncChannelWithContext}, consts::{SESSION_NAME, ZELLIJ_IPC_PIPE}, errors::{ClientContext, ContextType, ErrorInstruction}, - input::config::Config, - input::options::Options, + input::{actions::Action, config::Config, options::Options}, ipc::{ClientAttributes, ClientToServerMsg, ServerToClientMsg}, }; @@ -226,7 +225,7 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C match client_instruction { ClientInstruction::Exit => break, ClientInstruction::Error(backtrace) => { - let _ = os_input.send_to_server(ClientToServerMsg::ClientExit); + let _ = os_input.send_to_server(ClientToServerMsg::Action(Action::Quit)); handle_error(backtrace); } ClientInstruction::ServerError(backtrace) => { @@ -248,7 +247,6 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C } } - let _ = os_input.send_to_server(ClientToServerMsg::ClientExit); router_thread.join().unwrap(); // cleanup(); diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index a52ba9f49..586bd3e6e 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -49,11 +49,9 @@ pub(crate) enum ServerInstruction { impl From for ServerInstruction { fn from(instruction: ClientToServerMsg) -> Self { match instruction { - ClientToServerMsg::ClientExit => ServerInstruction::ClientExit, ClientToServerMsg::NewClient(pos, opts, options) => { ServerInstruction::NewClient(pos, opts, options) } - ClientToServerMsg::DetachSession => ServerInstruction::DetachSession, _ => unreachable!(), } } diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 408e1e1e2..6804fdf78 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -15,7 +15,13 @@ use zellij_utils::{ ipc::ClientToServerMsg, }; -fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn ServerOsApi) { +fn route_action( + action: Action, + session: &SessionMetaData, + os_input: &dyn ServerOsApi, + to_server: &SenderWithContext, +) -> bool { + let mut should_break = false; match action { Action::Write(val) => { session @@ -182,9 +188,17 @@ fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn Server .send_to_screen(ScreenInstruction::UpdateTabName(c)) .unwrap(); } + Action::Quit => { + to_server.send(ServerInstruction::ClientExit).unwrap(); + should_break = true; + } + Action::Detach => { + to_server.send(ServerInstruction::DetachSession).unwrap(); + should_break = true; + } Action::NoOp => {} - Action::Quit => panic!("Received unexpected action"), } + should_break } pub(crate) fn route_thread_main( @@ -197,13 +211,11 @@ pub(crate) fn route_thread_main( err_ctx.update_thread_ctx(); let rlocked_sessions = session_data.read().unwrap(); match instruction { - ClientToServerMsg::ClientExit | ClientToServerMsg::DetachSession => { - to_server.send(instruction.into()).unwrap(); - break; - } ClientToServerMsg::Action(action) => { if let Some(rlocked_sessions) = rlocked_sessions.as_ref() { - route_action(action, rlocked_sessions, &*os_input); + if route_action(action, rlocked_sessions, &*os_input, &to_server) { + break; + } } } ClientToServerMsg::TerminalResize(new_size) => { diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 5ad771da6..4e0ce0cb0 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -65,4 +65,6 @@ pub enum Action { CloseTab, GoToTab(u32), TabNameInput(Vec), + /// Detach session and exit + Detach, } diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index 6e40fe1fc..23b9ff619 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -54,11 +54,9 @@ pub enum ClientToServerMsg { DetachSession(SessionId), // Disconnect from the session we're connected to DisconnectFromSession,*/ - ClientExit, TerminalResize(PositionAndSize), NewClient(ClientAttributes, Box, Box), Action(Action), - DetachSession, } // Types of messages sent from the server to the client From ac082a1c930a356253f5cb3b685aabe28f87cba6 Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Fri, 21 May 2021 02:22:01 +0530 Subject: [PATCH 06/15] add keybinds for session mode and detach --- assets/config/default.yaml | 27 ++++++++++++++++++++ default-plugins/status-bar/src/first_line.rs | 24 +++++++++++++++++ zellij-tile/src/data.rs | 3 +++ zellij-utils/src/input/mod.rs | 3 +++ 4 files changed, 57 insertions(+) diff --git a/assets/config/default.yaml b/assets/config/default.yaml index 4582e79f6..e649f2ae2 100644 --- a/assets/config/default.yaml +++ b/assets/config/default.yaml @@ -12,6 +12,8 @@ keybinds: key: [Ctrl: 't',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's',] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'b',] - action: [Quit,] key: [Ctrl: 'q',] - action: [NewPane: ] @@ -42,6 +44,8 @@ keybinds: key: [Ctrl: 'r', Char: "\n", Char: ' ',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'b',] - action: [Quit] key: [Ctrl: 'q'] - action: [Resize: Left,] @@ -77,6 +81,8 @@ keybinds: key: [Ctrl: 'p', Char: "\n", Char: ' ',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'b',] - action: [Quit,] key: [Ctrl: 'q',] - action: [MoveFocus: Left,] @@ -114,6 +120,8 @@ keybinds: key: [Ctrl: 't', Char: "\n", Char: ' ',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'b',] - action: [SwitchToMode: RenameTab, TabNameInput: [0],] key: [Char: 'r'] - action: [Quit,] @@ -168,6 +176,8 @@ keybinds: key: [Ctrl: 'g',] - action: [SwitchToMode: Pane,] key: [Ctrl: 'p',] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'b',] - action: [Quit,] key: [Ctrl: 'q',] - action: [ScrollDown,] @@ -213,3 +223,20 @@ keybinds: key: [ Alt: '[',] - action: [FocusNextPane,] key: [ Alt: ']',] + session: + - action: [SwitchToMode: Locked,] + key: [Ctrl: 'g'] + - action: [SwitchToMode: Resize,] + key: [Ctrl: 'r',] + - action: [SwitchToMode: Pane,] + key: [Ctrl: 'p',] + - action: [SwitchToMode: Tab,] + key: [Ctrl: 't',] + - action: [SwitchToMode: Normal,] + key: [Ctrl: 'b', Char: "\n", Char: ' ',] + - action: [SwitchToMode: Scroll,] + key: [Ctrl: 's'] + - action: [Quit,] + key: [Ctrl: 'q',] + - action: [Detach,] + key: [Char: 'd',] diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 37175e3fa..b6a01fd9c 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -22,6 +22,7 @@ enum CtrlKeyAction { Resize, Scroll, Quit, + Session, } enum CtrlKeyMode { @@ -39,6 +40,7 @@ impl CtrlKeyShortcut { CtrlKeyAction::Resize => String::from("RESIZE"), CtrlKeyAction::Scroll => String::from("SCROLL"), CtrlKeyAction::Quit => String::from("QUIT"), + CtrlKeyAction::Session => String::from("SESSION"), } } pub fn shortened_text(&self) -> String { @@ -49,6 +51,7 @@ impl CtrlKeyShortcut { CtrlKeyAction::Resize => String::from("esize"), CtrlKeyAction::Scroll => String::from("croll"), CtrlKeyAction::Quit => String::from("uit"), + CtrlKeyAction::Session => String::from("sess"), } } pub fn letter_shortcut(&self) -> char { @@ -59,6 +62,7 @@ impl CtrlKeyShortcut { CtrlKeyAction::Resize => 'r', CtrlKeyAction::Scroll => 's', CtrlKeyAction::Quit => 'q', + CtrlKeyAction::Session => 'b', } } } @@ -297,6 +301,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Scroll), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Quit), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), ], colored_elements, separator, @@ -310,6 +315,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), ], colored_elements, separator, @@ -323,6 +329,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), ], colored_elements, separator, @@ -336,6 +343,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), ], colored_elements, separator, @@ -349,6 +357,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Scroll), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), ], colored_elements, separator, @@ -362,6 +371,21 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), + ], + colored_elements, + separator, + ), + InputMode::Session => key_indicators( + max_len, + &[ + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Pane), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), + CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Session), ], colored_elements, separator, diff --git a/zellij-tile/src/data.rs b/zellij-tile/src/data.rs index c1b9c30ef..cb4b2aa01 100644 --- a/zellij-tile/src/data.rs +++ b/zellij-tile/src/data.rs @@ -59,6 +59,9 @@ pub enum InputMode { Scroll, #[serde(alias = "renametab")] RenameTab, + /// `Session` mode allows detaching sessions + #[serde(alias = "session")] + Session, } impl Default for InputMode { diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index 2747205b6..068e22bc4 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -45,6 +45,9 @@ pub fn get_mode_info( InputMode::RenameTab => { keybinds.push(("Enter".to_string(), "when done".to_string())); } + InputMode::Session => { + keybinds.push(("d".to_string(), "Detach".to_string())); + } } ModeInfo { mode, From fa0a7e05c384f0da1a6fe3dd240181a6ff528b58 Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Sat, 22 May 2021 15:45:47 +0530 Subject: [PATCH 07/15] Add ability to attach to sessions --- Cargo.lock | 3 +- src/main.rs | 15 ++++- src/tests/fakes.rs | 1 + src/tests/mod.rs | 2 +- zellij-client/Cargo.toml | 1 + zellij-client/src/input_handler.rs | 4 +- zellij-client/src/lib.rs | 93 +++++++++++++++++----------- zellij-server/src/lib.rs | 63 +++++++++++++++---- zellij-server/src/os_input_output.rs | 25 +++++++- zellij-server/src/route.rs | 21 ++++++- zellij-server/src/screen.rs | 1 + zellij-utils/Cargo.toml | 2 +- zellij-utils/src/consts.rs | 5 +- zellij-utils/src/errors.rs | 1 + zellij-utils/src/ipc.rs | 35 +++++++++-- 15 files changed, 208 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 48aa2bf2c..a637785b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2299,6 +2299,7 @@ dependencies = [ name = "zellij-client" version = "0.12.0" dependencies = [ + "names", "termbg", "zellij-utils", ] @@ -2346,8 +2347,8 @@ dependencies = [ "interprocess", "lazy_static", "libc", - "names", "nix", + "once_cell", "serde", "serde_yaml", "signal-hook 0.3.8", diff --git a/src/main.rs b/src/main.rs index 82fc14d5e..319ad307d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,7 +50,20 @@ pub fn main() { process::exit(1); } }; - start_client(Box::new(os_input), opts, config); + if let Some(Command::Sessions(Sessions::Attach { + session_name, + force, + })) = opts.command.clone() + { + start_client( + Box::new(os_input), + opts, + config, + Some((session_name, force)), + ); + } else { + start_client(Box::new(os_input), opts, config, None); + } } } diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs index c81e226ff..cb41821a3 100644 --- a/src/tests/fakes.rs +++ b/src/tests/fakes.rs @@ -300,6 +300,7 @@ impl ServerOsApi for FakeInputOutput { } fn add_client_sender(&self) {} fn remove_client_sender(&self) {} + fn send_to_temp_client(&self, _msg: ServerToClientMsg) {} fn update_receiver(&mut self, _stream: LocalSocketStream) {} fn load_palette(&self) -> Palette { default_palette() diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 3e548db7c..f8a48a39f 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -21,6 +21,6 @@ pub fn start( start_server(server_os_input, PathBuf::from("")); }) .unwrap(); - start_client(client_os_input, opts, config); + start_client(client_os_input, opts, config, None); let _ = server_thread.join(); } diff --git a/zellij-client/Cargo.toml b/zellij-client/Cargo.toml index f9d0db086..76a25c1c4 100644 --- a/zellij-client/Cargo.toml +++ b/zellij-client/Cargo.toml @@ -9,6 +9,7 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +names = "0.11.0" termbg = "0.2.3" zellij-utils = { path = "../zellij-utils/", version = "0.12.0" } diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index 9fa001cda..70f6e9824 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -7,7 +7,7 @@ use zellij_utils::{ channels::{SenderWithContext, OPENCALLS}, errors::ContextType, input::{actions::Action, cast_termion_key, config::Config, keybinds::Keybinds}, - ipc::ClientToServerMsg, + ipc::{ClientToServerMsg, ExitReason}, }; use termion::input::TermReadEventsAndRaw; @@ -169,7 +169,7 @@ impl InputHandler { /// same as quitting Zellij). fn exit(&mut self) { self.send_client_instructions - .send(ClientInstruction::Exit) + .send(ClientInstruction::Exit(ExitReason::Normal)) .unwrap(); } } diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 0eff09af0..7042bed72 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -20,26 +20,24 @@ use zellij_utils::{ consts::{SESSION_NAME, ZELLIJ_IPC_PIPE}, errors::{ClientContext, ContextType, ErrorInstruction}, input::{actions::Action, config::Config, options::Options}, - ipc::{ClientAttributes, ClientToServerMsg, ServerToClientMsg}, + ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, }; /// Instructions related to the client-side application #[derive(Debug, Clone)] pub(crate) enum ClientInstruction { Error(String), - Render(Option), + Render(String), UnblockInputThread, - Exit, - ServerError(String), + Exit(ExitReason), } impl From for ClientInstruction { fn from(instruction: ServerToClientMsg) -> Self { match instruction { - ServerToClientMsg::Exit => ClientInstruction::Exit, + ServerToClientMsg::Exit(e) => ClientInstruction::Exit(e), ServerToClientMsg::Render(buffer) => ClientInstruction::Render(buffer), ServerToClientMsg::UnblockInputThread => ClientInstruction::UnblockInputThread, - ServerToClientMsg::ServerError(backtrace) => ClientInstruction::ServerError(backtrace), } } } @@ -47,9 +45,8 @@ impl From for ClientInstruction { impl From<&ClientInstruction> for ClientContext { fn from(client_instruction: &ClientInstruction) -> Self { match *client_instruction { - ClientInstruction::Exit => ClientContext::Exit, + ClientInstruction::Exit(_) => ClientContext::Exit, ClientInstruction::Error(_) => ClientContext::Error, - ClientInstruction::ServerError(_) => ClientContext::ServerError, ClientInstruction::Render(_) => ClientContext::Render, ClientInstruction::UnblockInputThread => ClientContext::UnblockInputThread, } @@ -79,7 +76,12 @@ fn spawn_server(socket_path: &Path) -> io::Result<()> { } } -pub fn start_client(mut os_input: Box, opts: CliArgs, config: Config) { +pub fn start_client( + mut os_input: Box, + opts: CliArgs, + config: Config, + attach_to: Option<(String, bool)>, +) { let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l"; let take_snapshot = "\u{1b}[?1049h"; let bracketed_paste = "\u{1b}[?2004h"; @@ -94,12 +96,6 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C .write(clear_client_terminal_attributes.as_bytes()) .unwrap(); std::env::set_var(&"ZELLIJ", "0"); - std::env::set_var(&"ZELLIJ_SESSION_NAME", &*SESSION_NAME); - - #[cfg(not(any(feature = "test", test)))] - spawn_server(&*ZELLIJ_IPC_PIPE).unwrap(); - - let mut command_is_executing = CommandIsExecuting::new(); let config_options = Options::from_cli(&config.options, opts.command.clone()); @@ -108,12 +104,34 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C position_and_size: full_screen_ws, palette, }; + + #[cfg(not(any(feature = "test", test)))] + let first_msg = if let Some((name, force)) = attach_to { + SESSION_NAME.set(name).unwrap(); + std::env::set_var(&"ZELLIJ_SESSION_NAME", SESSION_NAME.get().unwrap()); + + ClientToServerMsg::AttachClient(client_attributes, force) + } else { + SESSION_NAME + .set(names::Generator::default().next().unwrap()) + .unwrap(); + std::env::set_var(&"ZELLIJ_SESSION_NAME", SESSION_NAME.get().unwrap()); + + spawn_server(&*ZELLIJ_IPC_PIPE).unwrap(); + + ClientToServerMsg::NewClient(client_attributes, Box::new(opts), Box::new(config_options)) + }; + #[cfg(any(feature = "test", test))] + let first_msg = { + let _ = SESSION_NAME.set("".into()); + ClientToServerMsg::NewClient(client_attributes, Box::new(opts), Box::new(config_options)) + }; + os_input.connect_to_server(&*ZELLIJ_IPC_PIPE); - os_input.send_to_server(ClientToServerMsg::NewClient( - client_attributes, - Box::new(opts), - Box::new(config_options), - )); + os_input.send_to_server(first_msg); + + let mut command_is_executing = CommandIsExecuting::new(); + os_input.set_raw_mode(0); let _ = os_input .get_stdout_writer() @@ -170,7 +188,7 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C let send_client_instructions = send_client_instructions.clone(); move || { send_client_instructions - .send(ClientInstruction::Exit) + .send(ClientInstruction::Exit(ExitReason::Normal)) .unwrap() } }), @@ -187,11 +205,8 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C move || loop { let (instruction, err_ctx) = os_input.recv_from_server(); err_ctx.update_thread_ctx(); - match instruction { - ServerToClientMsg::Exit | ServerToClientMsg::ServerError(_) => { - should_break = true; - } - _ => {} + if let ServerToClientMsg::Exit(_) = instruction { + should_break = true; } send_client_instructions.send(instruction.into()).unwrap(); if should_break { @@ -216,6 +231,8 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C std::process::exit(1); }; + let exit_msg: String; + loop { let (client_instruction, mut err_ctx) = receive_client_instructions .recv() @@ -223,21 +240,25 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C err_ctx.add_call(ContextType::Client((&client_instruction).into())); match client_instruction { - ClientInstruction::Exit => break, + ClientInstruction::Exit(reason) => { + match reason { + ExitReason::Error(_) => handle_error(format!("{}", reason)), + ExitReason::ForceDetached => { + os_input.send_to_server(ClientToServerMsg::ClientDetached); + } + _ => {} + } + exit_msg = format!("{}", reason); + break; + } ClientInstruction::Error(backtrace) => { let _ = os_input.send_to_server(ClientToServerMsg::Action(Action::Quit)); handle_error(backtrace); } - ClientInstruction::ServerError(backtrace) => { - handle_error(backtrace); - } ClientInstruction::Render(output) => { - if output.is_none() { - break; - } let mut stdout = os_input.get_stdout_writer(); stdout - .write_all(&output.unwrap().as_bytes()) + .write_all(&output.as_bytes()) .expect("cannot write to stdout"); stdout.flush().expect("could not flush"); } @@ -255,8 +276,8 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C let restore_snapshot = "\u{1b}[?1049l"; let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); let goodbye_message = format!( - "{}\n{}{}{}Bye from Zellij!\n", - goto_start_of_last_line, restore_snapshot, reset_style, show_cursor + "{}\n{}{}{}{}\n", + goto_start_of_last_line, restore_snapshot, reset_style, show_cursor, exit_msg ); os_input.unset_raw_mode(0); diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 586bd3e6e..c1f471c76 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -15,7 +15,7 @@ use std::sync::{Arc, RwLock}; use std::thread; use std::{path::PathBuf, sync::mpsc}; use wasmer::Store; -use zellij_tile::data::PluginCapabilities; +use zellij_tile::data::{Event, InputMode, PluginCapabilities}; use crate::{ os_input_output::ServerOsApi, @@ -30,8 +30,8 @@ use zellij_utils::{ channels::{ChannelWithContext, SenderType, SenderWithContext, SyncChannelWithContext}, cli::CliArgs, errors::{ContextType, ErrorInstruction, ServerContext}, - input::options::Options, - ipc::{ClientAttributes, ClientToServerMsg, ServerToClientMsg}, + input::{get_mode_info, options::Options}, + ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg}, setup::{get_default_data_dir, install::populate_data_dir}, }; @@ -44,13 +44,17 @@ pub(crate) enum ServerInstruction { ClientExit, Error(String), DetachSession, + AttachClient(ClientAttributes, bool), } impl From for ServerInstruction { fn from(instruction: ClientToServerMsg) -> Self { match instruction { - ClientToServerMsg::NewClient(pos, opts, options) => { - ServerInstruction::NewClient(pos, opts, options) + ClientToServerMsg::NewClient(attrs, opts, options) => { + ServerInstruction::NewClient(attrs, opts, options) + } + ClientToServerMsg::AttachClient(attrs, force) => { + ServerInstruction::AttachClient(attrs, force) } _ => unreachable!(), } @@ -66,6 +70,7 @@ impl From<&ServerInstruction> for ServerContext { ServerInstruction::ClientExit => ServerContext::ClientExit, ServerInstruction::Error(_) => ServerContext::Error, ServerInstruction::DetachSession => ServerContext::DetachSession, + ServerInstruction::AttachClient(..) => ServerContext::AttachClient, } } } @@ -134,8 +139,9 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { let session_data = session_data.clone(); let os_input = os_input.clone(); let to_server = to_server.clone(); + let session_state = session_state.clone(); - move || route_thread_main(session_data, os_input, to_server) + move || route_thread_main(session_data, session_state, os_input, to_server) }) .unwrap(); #[cfg(not(any(feature = "test", test)))] @@ -148,6 +154,7 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { let os_input = os_input.clone(); let session_data = session_data.clone(); + let session_state = session_state.clone(); let to_server = to_server.clone(); let socket_path = socket_path.clone(); move || { @@ -160,6 +167,7 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { let mut os_input = os_input.clone(); os_input.update_receiver(stream); let session_data = session_data.clone(); + let session_state = session_state.clone(); let to_server = to_server.clone(); thread::Builder::new() .name("server_router".to_string()) @@ -168,7 +176,14 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { let os_input = os_input.clone(); let to_server = to_server.clone(); - move || route_thread_main(session_data, os_input, to_server) + move || { + route_thread_main( + session_data, + session_state, + os_input, + to_server, + ) + } }) .unwrap(); } @@ -204,6 +219,28 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { .send_to_pty(PtyInstruction::NewTab) .unwrap(); } + ServerInstruction::AttachClient(attrs, _) => { + *session_state.write().unwrap() = SessionState::Attached; + let rlock = session_data.read().unwrap(); + let session_data = rlock.as_ref().unwrap(); + session_data + .senders + .send_to_screen(ScreenInstruction::TerminalResize(attrs.position_and_size)) + .unwrap(); + let mode_info = + get_mode_info(InputMode::Normal, attrs.palette, session_data.capabilities); + session_data + .senders + .send_to_screen(ScreenInstruction::ChangeMode(mode_info.clone())) + .unwrap(); + session_data + .senders + .send_to_plugin(PluginInstruction::Update( + None, + Event::ModeUpdate(mode_info), + )) + .unwrap(); + } ServerInstruction::UnblockInputThread => { if *session_state.read().unwrap() == SessionState::Attached { os_input.send_to_client(ServerToClientMsg::UnblockInputThread); @@ -211,22 +248,26 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { } ServerInstruction::ClientExit => { *session_data.write().unwrap() = None; - os_input.send_to_client(ServerToClientMsg::Exit); + os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Normal)); break; } ServerInstruction::DetachSession => { *session_state.write().unwrap() = SessionState::Detached; - os_input.send_to_client(ServerToClientMsg::Exit); + os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Normal)); os_input.remove_client_sender(); } ServerInstruction::Render(output) => { if *session_state.read().unwrap() == SessionState::Attached { - os_input.send_to_client(ServerToClientMsg::Render(output)); + os_input.send_to_client( + output.map_or(ServerToClientMsg::Exit(ExitReason::Normal), |op| { + ServerToClientMsg::Render(op) + }), + ); } } ServerInstruction::Error(backtrace) => { if *session_state.read().unwrap() == SessionState::Attached { - os_input.send_to_client(ServerToClientMsg::ServerError(backtrace)); + os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Error(backtrace))); } break; } diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index 333b41288..51903c9a5 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -17,7 +17,10 @@ use signal_hook::consts::*; use zellij_tile::data::Palette; use zellij_utils::{ errors::ErrorContext, - ipc::{ClientToServerMsg, IpcReceiverWithContext, IpcSenderWithContext, ServerToClientMsg}, + ipc::{ + ClientToServerMsg, ExitReason, IpcReceiverWithContext, IpcSenderWithContext, + ServerToClientMsg, + }, shared::default_palette, }; @@ -156,6 +159,7 @@ pub trait ServerOsApi: Send + Sync { fn send_to_client(&self, msg: ServerToClientMsg); /// Adds a sender to client fn add_client_sender(&self); + fn send_to_temp_client(&self, msg: ServerToClientMsg); /// Removes the sender to client fn remove_client_sender(&self); /// Update the receiver socket for the client @@ -211,7 +215,6 @@ impl ServerOsApi for ServerOsInputOutput { .send(msg); } fn add_client_sender(&self) { - assert!(self.send_instructions_to_client.lock().unwrap().is_none()); let sender = self .receive_instructions_from_client .as_ref() @@ -219,7 +222,23 @@ impl ServerOsApi for ServerOsInputOutput { .lock() .unwrap() .get_sender(); - *self.send_instructions_to_client.lock().unwrap() = Some(sender); + let old_sender = self + .send_instructions_to_client + .lock() + .unwrap() + .replace(sender); + if let Some(mut sender) = old_sender { + sender.send(ServerToClientMsg::Exit(ExitReason::ForceDetached)); + } + } + fn send_to_temp_client(&self, msg: ServerToClientMsg) { + self.receive_instructions_from_client + .as_ref() + .unwrap() + .lock() + .unwrap() + .get_sender() + .send(msg); } fn remove_client_sender(&self) { assert!(self.send_instructions_to_client.lock().unwrap().is_some()); diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 6804fdf78..c8f78caae 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -4,7 +4,7 @@ use zellij_utils::zellij_tile::data::Event; use crate::{ os_input_output::ServerOsApi, pty::PtyInstruction, screen::ScreenInstruction, - wasm_vm::PluginInstruction, ServerInstruction, SessionMetaData, + wasm_vm::PluginInstruction, ServerInstruction, SessionMetaData, SessionState, }; use zellij_utils::{ channels::SenderWithContext, @@ -12,7 +12,7 @@ use zellij_utils::{ actions::{Action, Direction}, get_mode_info, }, - ipc::ClientToServerMsg, + ipc::{ClientToServerMsg, ExitReason, ServerToClientMsg}, }; fn route_action( @@ -203,6 +203,7 @@ fn route_action( pub(crate) fn route_thread_main( session_data: Arc>>, + session_state: Arc>, os_input: Box, to_server: SenderWithContext, ) { @@ -210,6 +211,7 @@ pub(crate) fn route_thread_main( let (instruction, err_ctx) = os_input.recv_from_client(); err_ctx.update_thread_ctx(); let rlocked_sessions = session_data.read().unwrap(); + match instruction { ClientToServerMsg::Action(action) => { if let Some(rlocked_sessions) = rlocked_sessions.as_ref() { @@ -227,9 +229,24 @@ pub(crate) fn route_thread_main( .unwrap(); } ClientToServerMsg::NewClient(..) => { + if *session_state.read().unwrap() != SessionState::Uninitialized { + os_input.send_to_temp_client(ServerToClientMsg::Exit(ExitReason::Error( + "Cannot add new client".into(), + ))); + break; + } os_input.add_client_sender(); to_server.send(instruction.into()).unwrap(); } + ClientToServerMsg::AttachClient(_, force) => { + if *session_state.read().unwrap() == SessionState::Attached && !force { + os_input.send_to_temp_client(ServerToClientMsg::Exit(ExitReason::CannotAttach)); + break; + } + os_input.add_client_sender(); + to_server.send(instruction.into()).unwrap(); + } + ClientToServerMsg::ClientDetached => break, } } } diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 24b66199e..068037eea 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -386,6 +386,7 @@ impl Screen { self.update_tabs(); } pub fn change_mode(&mut self, mode_info: ModeInfo) { + self.colors = mode_info.palette; self.mode_info = mode_info; for tab in self.tabs.values_mut() { tab.mode_info = self.mode_info.clone(); diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml index b1f48d10b..2fcccd7d7 100644 --- a/zellij-utils/Cargo.toml +++ b/zellij-utils/Cargo.toml @@ -16,8 +16,8 @@ directories-next = "2.0" interprocess = "1.1.1" lazy_static = "1.4.0" libc = "0.2" -names = "0.11.0" nix = "0.19.1" +once_cell = "1.7.2" serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.8" signal-hook = "0.3" diff --git a/zellij-utils/src/consts.rs b/zellij-utils/src/consts.rs index 012e36999..f22dd4893 100644 --- a/zellij-utils/src/consts.rs +++ b/zellij-utils/src/consts.rs @@ -4,6 +4,7 @@ use crate::shared::set_permissions; use directories_next::ProjectDirs; use lazy_static::lazy_static; use nix::unistd::Uid; +use once_cell::sync::OnceCell; use std::path::PathBuf; use std::{env, fs}; @@ -24,7 +25,7 @@ const fn system_default_data_dir() -> &'static str { lazy_static! { static ref UID: Uid = Uid::current(); - pub static ref SESSION_NAME: String = names::Generator::default().next().unwrap(); + pub static ref SESSION_NAME: OnceCell = OnceCell::new(); pub static ref ZELLIJ_PROJ_DIR: ProjectDirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); pub static ref ZELLIJ_SOCK_DIR: PathBuf = { @@ -43,7 +44,7 @@ lazy_static! { let mut sock_dir = ZELLIJ_SOCK_DIR.clone(); fs::create_dir_all(&sock_dir).unwrap(); set_permissions(&sock_dir).unwrap(); - sock_dir.push(&*SESSION_NAME); + sock_dir.push(SESSION_NAME.get().unwrap()); sock_dir }; pub static ref ZELLIJ_TMP_DIR: PathBuf = diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index d7ca19fb1..b47ddb9a2 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -261,4 +261,5 @@ pub enum ServerContext { ClientExit, Error, DetachSession, + AttachClient, } diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index 23b9ff619..41feef1c0 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -9,6 +9,7 @@ use crate::{ use interprocess::local_socket::LocalSocketStream; use nix::unistd::dup; use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Error, Formatter}; use std::io::{self, Write}; use std::marker::PhantomData; use std::os::unix::io::{AsRawFd, FromRawFd}; @@ -34,7 +35,7 @@ pub enum ClientType { Writer, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub struct ClientAttributes { pub position_and_size: PositionAndSize, pub palette: Palette, @@ -56,7 +57,9 @@ pub enum ClientToServerMsg { DisconnectFromSession,*/ TerminalResize(PositionAndSize), NewClient(ClientAttributes, Box, Box), + AttachClient(ClientAttributes, bool), Action(Action), + ClientDetached, } // Types of messages sent from the server to the client @@ -66,10 +69,34 @@ pub enum ServerToClientMsg { SessionInfo(Session), // A list of sessions SessionList(HashSet),*/ - Render(Option), + Render(String), UnblockInputThread, - Exit, - ServerError(String), + Exit(ExitReason), +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum ExitReason { + Normal, + ForceDetached, + CannotAttach, + Error(String), +} + +impl Display for ExitReason { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + match self { + Self::Normal => write!(f, "Bye from Zellij!"), + Self::ForceDetached => write!( + f, + "Session was detach from this client (possibly because another client connected)" + ), + Self::CannotAttach => write!( + f, + "Session attached to another client. Use --force flag to force connect." + ), + Self::Error(e) => write!(f, "Error occured in server:\n{}", e), + } + } } /// Sends messages on a stream socket, along with an [`ErrorContext`]. From dbc446ab556c44aa62985728c26572011997ce98 Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Sat, 22 May 2021 16:59:12 +0530 Subject: [PATCH 08/15] fix regression and tests --- zellij-server/src/lib.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index c1f471c76..e598d2384 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -258,11 +258,12 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { } ServerInstruction::Render(output) => { if *session_state.read().unwrap() == SessionState::Attached { - os_input.send_to_client( - output.map_or(ServerToClientMsg::Exit(ExitReason::Normal), |op| { - ServerToClientMsg::Render(op) - }), - ); + if let Some(op) = output { + os_input.send_to_client(ServerToClientMsg::Render(op)); + } else { + os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Normal)); + break; + } } } ServerInstruction::Error(backtrace) => { From 62a2d9cff2780262d76938d2f3364464b559e0ec Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Sat, 22 May 2021 20:26:43 +0530 Subject: [PATCH 09/15] join router threads on exit --- zellij-client/src/lib.rs | 10 +++--- zellij-server/src/lib.rs | 68 ++++++++++++++++++++++---------------- zellij-server/src/route.rs | 14 ++++---- zellij-utils/src/ipc.rs | 2 +- 4 files changed, 52 insertions(+), 42 deletions(-) diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 7042bed72..45b60d44a 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -241,12 +241,10 @@ pub fn start_client( err_ctx.add_call(ContextType::Client((&client_instruction).into())); match client_instruction { ClientInstruction::Exit(reason) => { - match reason { - ExitReason::Error(_) => handle_error(format!("{}", reason)), - ExitReason::ForceDetached => { - os_input.send_to_server(ClientToServerMsg::ClientDetached); - } - _ => {} + os_input.send_to_server(ClientToServerMsg::ClientExited); + + if let ExitReason::Error(_) = reason { + handle_error(format!("{}", reason)); } exit_msg = format!("{}", reason); break; diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index e598d2384..7a5aed1ec 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -11,7 +11,7 @@ mod wasm_vm; use zellij_utils::zellij_tile; -use std::sync::{Arc, RwLock}; +use std::sync::{Arc, Mutex, RwLock}; use std::thread; use std::{path::PathBuf, sync::mpsc}; use wasmer::Store; @@ -132,18 +132,22 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { }) }); - #[cfg(any(feature = "test", test))] - thread::Builder::new() - .name("server_router".to_string()) - .spawn({ - let session_data = session_data.clone(); - let os_input = os_input.clone(); - let to_server = to_server.clone(); - let session_state = session_state.clone(); + let thread_handles = Arc::new(Mutex::new(Vec::new())); - move || route_thread_main(session_data, session_state, os_input, to_server) - }) - .unwrap(); + #[cfg(any(feature = "test", test))] + thread_handles.lock().unwrap().push( + thread::Builder::new() + .name("server_router".to_string()) + .spawn({ + let session_data = session_data.clone(); + let os_input = os_input.clone(); + let to_server = to_server.clone(); + let session_state = session_state.clone(); + + move || route_thread_main(session_data, session_state, os_input, to_server) + }) + .unwrap(), + ); #[cfg(not(any(feature = "test", test)))] let _ = thread::Builder::new() .name("server_listener".to_string()) @@ -157,6 +161,7 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { let session_state = session_state.clone(); let to_server = to_server.clone(); let socket_path = socket_path.clone(); + let thread_handles = thread_handles.clone(); move || { drop(std::fs::remove_file(&socket_path)); let listener = LocalSocketListener::bind(&*socket_path).unwrap(); @@ -169,23 +174,25 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { let session_data = session_data.clone(); let session_state = session_state.clone(); let to_server = to_server.clone(); - thread::Builder::new() - .name("server_router".to_string()) - .spawn({ - let session_data = session_data.clone(); - let os_input = os_input.clone(); - let to_server = to_server.clone(); + thread_handles.lock().unwrap().push( + thread::Builder::new() + .name("server_router".to_string()) + .spawn({ + let session_data = session_data.clone(); + let os_input = os_input.clone(); + let to_server = to_server.clone(); - move || { - route_thread_main( - session_data, - session_state, - os_input, - to_server, - ) - } - }) - .unwrap(); + move || { + route_thread_main( + session_data, + session_state, + os_input, + to_server, + ) + } + }) + .unwrap(), + ); } Err(err) => { panic!("err {:?}", err); @@ -274,6 +281,11 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { } } } + thread_handles + .lock() + .unwrap() + .drain(..) + .for_each(|h| drop(h.join())); #[cfg(not(any(feature = "test", test)))] drop(std::fs::remove_file(&socket_path)); } diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index c8f78caae..8dcd98ffa 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -233,20 +233,20 @@ pub(crate) fn route_thread_main( os_input.send_to_temp_client(ServerToClientMsg::Exit(ExitReason::Error( "Cannot add new client".into(), ))); - break; + } else { + os_input.add_client_sender(); + to_server.send(instruction.into()).unwrap(); } - os_input.add_client_sender(); - to_server.send(instruction.into()).unwrap(); } ClientToServerMsg::AttachClient(_, force) => { if *session_state.read().unwrap() == SessionState::Attached && !force { os_input.send_to_temp_client(ServerToClientMsg::Exit(ExitReason::CannotAttach)); - break; + } else { + os_input.add_client_sender(); + to_server.send(instruction.into()).unwrap(); } - os_input.add_client_sender(); - to_server.send(instruction.into()).unwrap(); } - ClientToServerMsg::ClientDetached => break, + ClientToServerMsg::ClientExited => break, } } } diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index 41feef1c0..a160b782f 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -59,7 +59,7 @@ pub enum ClientToServerMsg { NewClient(ClientAttributes, Box, Box), AttachClient(ClientAttributes, bool), Action(Action), - ClientDetached, + ClientExited, } // Types of messages sent from the server to the client From 1162d40ea00aec38907c7079e53c62a02c2c5eda Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Sat, 22 May 2021 21:16:44 +0530 Subject: [PATCH 10/15] check if session exists before attaching --- src/list_sessions.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 42 ++++----------------------- 2 files changed, 74 insertions(+), 37 deletions(-) create mode 100644 src/list_sessions.rs diff --git a/src/list_sessions.rs b/src/list_sessions.rs new file mode 100644 index 000000000..41437e1a7 --- /dev/null +++ b/src/list_sessions.rs @@ -0,0 +1,69 @@ +use std::os::unix::fs::FileTypeExt; +use std::{fs, io, process}; +use zellij_utils::consts::ZELLIJ_SOCK_DIR; + +fn get_sessions() -> Result, io::ErrorKind> { + match fs::read_dir(&*ZELLIJ_SOCK_DIR) { + Ok(files) => { + let mut sessions = Vec::new(); + files.for_each(|file| { + let file = file.unwrap(); + if file.file_type().unwrap().is_socket() { + sessions.push(file.file_name().into_string().unwrap()); + } + }); + Ok(sessions) + } + Err(err) => { + if let io::ErrorKind::NotFound = err.kind() { + Ok(Vec::with_capacity(0)) + } else { + Err(err.kind()) + } + } + } +} + +pub(crate) fn list_sessions() { + let exit_code = match get_sessions() { + Ok(sessions) => { + if sessions.is_empty() { + println!("No active zellij sessions found."); + } else { + let curr_session = + std::env::var("ZELLIJ_SESSION_NAME").unwrap_or_else(|_| "".into()); + sessions.iter().for_each(|session| { + let suffix = if curr_session == *session { + " (current)" + } else { + "" + }; + println!("{}{}", session, suffix); + }) + } + 0 + } + Err(e) => { + eprintln!("Error occured: {:?}", e); + 1 + } + }; + process::exit(exit_code); +} + +pub(crate) fn assert_session(name: &str) { + let exit_code = match get_sessions() { + Ok(sessions) => { + if sessions.iter().any(|s| s == name) { + return; + } + println!("No session named {:?} found.", name); + 0 + } + Err(e) => { + eprintln!("Error occured: {:?}", e); + 1 + } + }; + process::exit(exit_code); +} diff --git a/src/main.rs b/src/main.rs index 319ad307d..5a6d5ed18 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,15 @@ +mod list_sessions; #[cfg(test)] mod tests; +use list_sessions::{assert_session, list_sessions}; use std::convert::TryFrom; -use std::os::unix::fs::FileTypeExt; -use std::{fs, io, process}; +use std::process; use zellij_client::{os_input_output::get_client_os_input, start_client}; use zellij_server::{os_input_output::get_server_os_input, start_server}; use zellij_utils::{ cli::{CliArgs, Command, Sessions}, - consts::{ZELLIJ_SOCK_DIR, ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, + consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, input::config::Config, logging::*, setup::Setup, @@ -55,6 +56,7 @@ pub fn main() { force, })) = opts.command.clone() { + assert_session(&session_name); start_client( Box::new(os_input), opts, @@ -66,37 +68,3 @@ pub fn main() { } } } - -fn list_sessions() { - match fs::read_dir(&*ZELLIJ_SOCK_DIR) { - Ok(files) => { - let mut is_empty = true; - let session_name = std::env::var("ZELLIJ_SESSION_NAME").unwrap_or_else(|_| "".into()); - files.for_each(|file| { - let file = file.unwrap(); - if file.file_type().unwrap().is_socket() { - let fname = file.file_name().into_string().unwrap(); - let suffix = if session_name == fname { - " (current)" - } else { - "" - }; - println!("{}{}", fname, suffix); - is_empty = false; - } - }); - if is_empty { - println!("No active zellij sessions found."); - } - } - Err(err) => { - if let io::ErrorKind::NotFound = err.kind() { - println!("No active zellij sessions found."); - } else { - eprintln!("Error occured: {}", err); - process::exit(1); - } - } - } - process::exit(0); -} From 0621ba8f349a28d0f746f4c8349658025e5f64f5 Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Sat, 22 May 2021 22:12:43 +0530 Subject: [PATCH 11/15] Allow user to specify session name --- Cargo.lock | 2 +- Cargo.toml | 1 + src/list_sessions.rs | 17 +++++++++++++++++ src/main.rs | 18 ++++++++++++++---- src/tests/mod.rs | 4 ++-- zellij-client/Cargo.toml | 1 - zellij-client/src/lib.rs | 35 +++++++++++++++++++++++------------ zellij-utils/src/cli.rs | 4 ++++ 8 files changed, 62 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a637785b5..e32a829b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2290,6 +2290,7 @@ name = "zellij" version = "0.12.0" dependencies = [ "insta", + "names", "zellij-client", "zellij-server", "zellij-utils", @@ -2299,7 +2300,6 @@ dependencies = [ name = "zellij-client" version = "0.12.0" dependencies = [ - "names", "termbg", "zellij-utils", ] diff --git a/Cargo.toml b/Cargo.toml index 9e7e31ab4..5f7afaaa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +names = "0.11.0" zellij-client = { path = "zellij-client/", version = "0.12.0" } zellij-server = { path = "zellij-server/", version = "0.12.0" } zellij-utils = { path = "zellij-utils/", version = "0.12.0" } diff --git a/src/list_sessions.rs b/src/list_sessions.rs index 41437e1a7..ee72a34d0 100644 --- a/src/list_sessions.rs +++ b/src/list_sessions.rs @@ -67,3 +67,20 @@ pub(crate) fn assert_session(name: &str) { }; process::exit(exit_code); } + +pub(crate) fn assert_session_ne(name: &str) { + let exit_code = match get_sessions() { + Ok(sessions) => { + if sessions.iter().all(|s| s != name) { + return; + } + println!("Session with name {:?} aleady exists. Use attach command to connect to it or specify a different name.", name); + 0 + } + Err(e) => { + eprintln!("Error occured: {:?}", e); + 1 + } + }; + process::exit(exit_code); +} diff --git a/src/main.rs b/src/main.rs index 5a6d5ed18..a91ce508c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,10 +2,10 @@ mod list_sessions; #[cfg(test)] mod tests; -use list_sessions::{assert_session, list_sessions}; +use list_sessions::{assert_session, assert_session_ne, list_sessions}; use std::convert::TryFrom; use std::process; -use zellij_client::{os_input_output::get_client_os_input, start_client}; +use zellij_client::{os_input_output::get_client_os_input, start_client, ClientInfo}; use zellij_server::{os_input_output::get_server_os_input, start_server}; use zellij_utils::{ cli::{CliArgs, Command, Sessions}, @@ -61,10 +61,20 @@ pub fn main() { Box::new(os_input), opts, config, - Some((session_name, force)), + ClientInfo::Attach(session_name, force), ); } else { - start_client(Box::new(os_input), opts, config, None); + let session_name = opts + .session + .clone() + .unwrap_or_else(|| names::Generator::default().next().unwrap()); + assert_session_ne(&session_name); + start_client( + Box::new(os_input), + opts, + config, + ClientInfo::New(session_name), + ); } } } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index f8a48a39f..e075ed49a 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -5,7 +5,7 @@ pub mod tty_inputs; pub mod utils; use std::path::PathBuf; -use zellij_client::{os_input_output::ClientOsApi, start_client}; +use zellij_client::{os_input_output::ClientOsApi, start_client, ClientInfo}; use zellij_server::{os_input_output::ServerOsApi, start_server}; use zellij_utils::{cli::CliArgs, input::config::Config}; @@ -21,6 +21,6 @@ pub fn start( start_server(server_os_input, PathBuf::from("")); }) .unwrap(); - start_client(client_os_input, opts, config, None); + start_client(client_os_input, opts, config, ClientInfo::New("".into())); let _ = server_thread.join(); } diff --git a/zellij-client/Cargo.toml b/zellij-client/Cargo.toml index 76a25c1c4..f9d0db086 100644 --- a/zellij-client/Cargo.toml +++ b/zellij-client/Cargo.toml @@ -9,7 +9,6 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -names = "0.11.0" termbg = "0.2.3" zellij-utils = { path = "../zellij-utils/", version = "0.12.0" } diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 45b60d44a..f3b0f11d3 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -76,11 +76,17 @@ fn spawn_server(socket_path: &Path) -> io::Result<()> { } } +#[derive(Debug, Clone)] +pub enum ClientInfo { + Attach(String, bool), + New(String), +} + pub fn start_client( mut os_input: Box, opts: CliArgs, config: Config, - attach_to: Option<(String, bool)>, + info: ClientInfo, ) { let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l"; let take_snapshot = "\u{1b}[?1049h"; @@ -106,20 +112,25 @@ pub fn start_client( }; #[cfg(not(any(feature = "test", test)))] - let first_msg = if let Some((name, force)) = attach_to { - SESSION_NAME.set(name).unwrap(); - std::env::set_var(&"ZELLIJ_SESSION_NAME", SESSION_NAME.get().unwrap()); + let first_msg = match info { + ClientInfo::Attach(name, force) => { + SESSION_NAME.set(name).unwrap(); + std::env::set_var(&"ZELLIJ_SESSION_NAME", SESSION_NAME.get().unwrap()); - ClientToServerMsg::AttachClient(client_attributes, force) - } else { - SESSION_NAME - .set(names::Generator::default().next().unwrap()) - .unwrap(); - std::env::set_var(&"ZELLIJ_SESSION_NAME", SESSION_NAME.get().unwrap()); + ClientToServerMsg::AttachClient(client_attributes, force) + } + ClientInfo::New(name) => { + SESSION_NAME.set(name).unwrap(); + std::env::set_var(&"ZELLIJ_SESSION_NAME", SESSION_NAME.get().unwrap()); - spawn_server(&*ZELLIJ_IPC_PIPE).unwrap(); + spawn_server(&*ZELLIJ_IPC_PIPE).unwrap(); - ClientToServerMsg::NewClient(client_attributes, Box::new(opts), Box::new(config_options)) + ClientToServerMsg::NewClient( + client_attributes, + Box::new(opts), + Box::new(config_options), + ) + } }; #[cfg(any(feature = "test", test))] let first_msg = { diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index 87f929c7e..bf7851c9f 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -20,6 +20,10 @@ pub struct CliArgs { #[structopt(long, parse(from_os_str), hidden = true)] pub server: Option, + /// Specify name of a new session + #[structopt(long, short)] + pub session: Option, + /// Name of a layout file in the layout directory #[structopt(short, long, parse(from_os_str))] pub layout: Option, From cba7c07cd66c15be82e32c3e6766feac88dce3ff Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Sat, 22 May 2021 22:34:00 +0530 Subject: [PATCH 12/15] rename list_sessions file to sessions --- src/main.rs | 4 ++-- src/{list_sessions.rs => sessions.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{list_sessions.rs => sessions.rs} (100%) diff --git a/src/main.rs b/src/main.rs index a91ce508c..67b833988 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ -mod list_sessions; +mod sessions; #[cfg(test)] mod tests; -use list_sessions::{assert_session, assert_session_ne, list_sessions}; +use sessions::{assert_session, assert_session_ne, list_sessions}; use std::convert::TryFrom; use std::process; use zellij_client::{os_input_output::get_client_os_input, start_client, ClientInfo}; diff --git a/src/list_sessions.rs b/src/sessions.rs similarity index 100% rename from src/list_sessions.rs rename to src/sessions.rs From c60abe6ad634d9889a95c0043bfb0e05105a1e29 Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Tue, 25 May 2021 13:11:37 +0530 Subject: [PATCH 13/15] CHange keybind for Session mode and add comments for clarity --- assets/config/default.yaml | 12 ++++++------ default-plugins/status-bar/src/first_line.rs | 16 ++++++++-------- zellij-server/src/lib.rs | 3 +++ zellij-server/src/os_input_output.rs | 5 +++++ zellij-server/src/tab.rs | 1 + 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/assets/config/default.yaml b/assets/config/default.yaml index e649f2ae2..42bc16886 100644 --- a/assets/config/default.yaml +++ b/assets/config/default.yaml @@ -13,7 +13,7 @@ keybinds: - action: [SwitchToMode: Scroll,] key: [Ctrl: 's',] - action: [SwitchToMode: Session,] - key: [Ctrl: 'b',] + key: [Ctrl: 'o',] - action: [Quit,] key: [Ctrl: 'q',] - action: [NewPane: ] @@ -45,7 +45,7 @@ keybinds: - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] - action: [SwitchToMode: Session,] - key: [Ctrl: 'b',] + key: [Ctrl: 'o',] - action: [Quit] key: [Ctrl: 'q'] - action: [Resize: Left,] @@ -82,7 +82,7 @@ keybinds: - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] - action: [SwitchToMode: Session,] - key: [Ctrl: 'b',] + key: [Ctrl: 'o',] - action: [Quit,] key: [Ctrl: 'q',] - action: [MoveFocus: Left,] @@ -121,7 +121,7 @@ keybinds: - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] - action: [SwitchToMode: Session,] - key: [Ctrl: 'b',] + key: [Ctrl: 'o',] - action: [SwitchToMode: RenameTab, TabNameInput: [0],] key: [Char: 'r'] - action: [Quit,] @@ -177,7 +177,7 @@ keybinds: - action: [SwitchToMode: Pane,] key: [Ctrl: 'p',] - action: [SwitchToMode: Session,] - key: [Ctrl: 'b',] + key: [Ctrl: 'o',] - action: [Quit,] key: [Ctrl: 'q',] - action: [ScrollDown,] @@ -233,7 +233,7 @@ keybinds: - action: [SwitchToMode: Tab,] key: [Ctrl: 't',] - action: [SwitchToMode: Normal,] - key: [Ctrl: 'b', Char: "\n", Char: ' ',] + key: [Ctrl: 'o', Char: "\n", Char: ' ',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] - action: [Quit,] diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index b6a01fd9c..a8bc19cdc 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -62,7 +62,7 @@ impl CtrlKeyShortcut { CtrlKeyAction::Resize => 'r', CtrlKeyAction::Scroll => 's', CtrlKeyAction::Quit => 'q', - CtrlKeyAction::Session => 'b', + CtrlKeyAction::Session => 'o', } } } @@ -300,8 +300,8 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Scroll), - CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Quit), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), + CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Quit), ], colored_elements, separator, @@ -314,8 +314,8 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, separator, @@ -328,8 +328,8 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, separator, @@ -342,8 +342,8 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, separator, @@ -356,8 +356,8 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Scroll), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, separator, @@ -370,8 +370,8 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, separator, @@ -384,8 +384,8 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), - CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Session), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, separator, diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 7a5aed1ec..aeb13cb56 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -265,6 +265,9 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { } ServerInstruction::Render(output) => { if *session_state.read().unwrap() == SessionState::Attached { + // Here output is of type Option sent by screen thread. + // If `Some(_)`- unwrap it and forward it to the client to render. + // If `None`- Send an exit instruction. This is the case when the user closes last Tab/Pane. if let Some(op) = output { os_input.send_to_client(ServerToClientMsg::Render(op)); } else { diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index 51903c9a5..bef3445ff 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -159,6 +159,11 @@ pub trait ServerOsApi: Send + Sync { fn send_to_client(&self, msg: ServerToClientMsg); /// Adds a sender to client fn add_client_sender(&self); + /// Send to the temporary client + // A temporary client is the one that hasn't been registered as a client yet. + // Only the corresponding router thread has access to send messages to it. + // This can be the case when the client cannot attach to the session, + // so it tries to connect and then exits, hence temporary. fn send_to_temp_client(&self, msg: ServerToClientMsg); /// Removes the sender to client fn remove_client_sender(&self); diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index 68d01ec09..4a0db153d 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -730,6 +730,7 @@ impl Tab { { // we might not have an active terminal if we closed the last pane // in that case, we should not render as the app is exiting + // or if this session is not attached to a client, we do not have to render return; } // if any pane contain widechar, all pane in the same row will messup. We should render them every time From 151deb323578d599b9358db072b52ca712d950c6 Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Tue, 25 May 2021 13:20:30 +0530 Subject: [PATCH 14/15] remove shortened_text mode from status-bar --- default-plugins/status-bar/src/first_line.rs | 46 -------------------- zellij-server/src/lib.rs | 2 +- 2 files changed, 1 insertion(+), 47 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index a8bc19cdc..7a678cdc4 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -43,17 +43,6 @@ impl CtrlKeyShortcut { CtrlKeyAction::Session => String::from("SESSION"), } } - pub fn shortened_text(&self) -> String { - match self.action { - CtrlKeyAction::Lock => String::from("LOCK"), - CtrlKeyAction::Pane => String::from("ane"), - CtrlKeyAction::Tab => String::from("ab"), - CtrlKeyAction::Resize => String::from("esize"), - CtrlKeyAction::Scroll => String::from("croll"), - CtrlKeyAction::Quit => String::from("uit"), - CtrlKeyAction::Session => String::from("sess"), - } - } pub fn letter_shortcut(&self) -> char { match self.action { CtrlKeyAction::Lock => 'g', @@ -197,32 +186,6 @@ fn full_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements, separator: &st } } -fn shortened_ctrl_key( - key: &CtrlKeyShortcut, - palette: ColoredElements, - separator: &str, -) -> LinePart { - let shortened_text = key.shortened_text(); - let letter_shortcut = key.letter_shortcut(); - let shortened_text = match key.action { - CtrlKeyAction::Lock => format!(" {}", shortened_text), - _ => shortened_text, - }; - match key.mode { - CtrlKeyMode::Unselected => { - unselected_mode_shortcut(letter_shortcut, &shortened_text, palette, separator) - } - CtrlKeyMode::Selected => { - selected_mode_shortcut(letter_shortcut, &shortened_text, palette, separator) - } - CtrlKeyMode::Disabled => disabled_mode_shortcut( - &format!(" <{}>{}", letter_shortcut, shortened_text), - palette, - separator, - ), - } -} - fn single_letter_ctrl_key( key: &CtrlKeyShortcut, palette: ColoredElements, @@ -258,15 +221,6 @@ fn key_indicators( return line_part; } line_part = LinePart::default(); - for ctrl_key in keys { - let key = shortened_ctrl_key(ctrl_key, palette, separator); - line_part.part = format!("{}{}", line_part.part, key.part); - line_part.len += key.len; - } - if line_part.len < max_len { - return line_part; - } - line_part = LinePart::default(); for ctrl_key in keys { let key = single_letter_ctrl_key(ctrl_key, palette, separator); line_part.part = format!("{}{}", line_part.part, key.part); diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index aeb13cb56..40fb87f7d 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -265,7 +265,7 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { } ServerInstruction::Render(output) => { if *session_state.read().unwrap() == SessionState::Attached { - // Here output is of type Option sent by screen thread. + // Here output is of the type Option sent by screen thread. // If `Some(_)`- unwrap it and forward it to the client to render. // If `None`- Send an exit instruction. This is the case when the user closes last Tab/Pane. if let Some(op) = output { From df6d6cb3a70cbea9d088c7af47f3540896b98089 Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Tue, 25 May 2021 16:26:31 +0530 Subject: [PATCH 15/15] check for residual socket files and clean them --- src/sessions.rs | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/sessions.rs b/src/sessions.rs index ee72a34d0..fd834e408 100644 --- a/src/sessions.rs +++ b/src/sessions.rs @@ -1,6 +1,10 @@ use std::os::unix::fs::FileTypeExt; use std::{fs, io, process}; -use zellij_utils::consts::ZELLIJ_SOCK_DIR; +use zellij_utils::{ + consts::ZELLIJ_SOCK_DIR, + interprocess::local_socket::LocalSocketStream, + ipc::{ClientToServerMsg, IpcSenderWithContext}, +}; fn get_sessions() -> Result, io::ErrorKind> { match fs::read_dir(&*ZELLIJ_SOCK_DIR) { @@ -8,8 +12,9 @@ fn get_sessions() -> Result, io::ErrorKind> { let mut sessions = Vec::new(); files.for_each(|file| { let file = file.unwrap(); - if file.file_type().unwrap().is_socket() { - sessions.push(file.file_name().into_string().unwrap()); + let file_name = file.file_name().into_string().unwrap(); + if file.file_type().unwrap().is_socket() && assert_socket(&file_name) { + sessions.push(file_name); } }); Ok(sessions) @@ -84,3 +89,21 @@ pub(crate) fn assert_session_ne(name: &str) { }; process::exit(exit_code); } + +fn assert_socket(name: &str) -> bool { + let path = &*ZELLIJ_SOCK_DIR.join(name); + match LocalSocketStream::connect(path) { + Ok(stream) => { + IpcSenderWithContext::new(stream).send(ClientToServerMsg::ClientExited); + true + } + Err(e) => { + if e.kind() == io::ErrorKind::ConnectionRefused { + drop(fs::remove_file(path)); + false + } else { + true + } + } + } +}