diff options
-rw-r--r-- | Cargo.lock | 3 | ||||
-rw-r--r-- | src/main.rs | 15 | ||||
-rw-r--r-- | src/tests/fakes.rs | 1 | ||||
-rw-r--r-- | src/tests/mod.rs | 2 | ||||
-rw-r--r-- | zellij-client/Cargo.toml | 1 | ||||
-rw-r--r-- | zellij-client/src/input_handler.rs | 4 | ||||
-rw-r--r-- | zellij-client/src/lib.rs | 93 | ||||
-rw-r--r-- | zellij-server/src/lib.rs | 63 | ||||
-rw-r--r-- | zellij-server/src/os_input_output.rs | 25 | ||||
-rw-r--r-- | zellij-server/src/route.rs | 21 | ||||
-rw-r--r-- | zellij-server/src/screen.rs | 1 | ||||
-rw-r--r-- | zellij-utils/Cargo.toml | 2 | ||||
-rw-r--r-- | zellij-utils/src/consts.rs | 5 | ||||
-rw-r--r-- | zellij-utils/src/errors.rs | 1 | ||||
-rw-r--r-- | 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<String>), + Render(String), UnblockInputThread, - Exit, - ServerError(String), + Exit(ExitReason), } impl From<ServerToClientMsg> 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<ServerToClientMsg> 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<dyn ClientOsApi>, opts: CliArgs, config: Config) { +pub fn start_client( + mut os_input: Box<dyn ClientOsApi>, + 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<dyn ClientOsApi>, 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<dyn ClientOsApi>, 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<dyn ClientOsApi>, 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<dyn ClientOsApi>, 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<dyn ClientOsApi>, 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<dyn ClientOsApi>, 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<dyn ClientOsApi>, 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<ClientToServerMsg> 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<RwLock<Option<SessionMetaData>>>, + session_state: Arc<RwLock<SessionState>>, os_input: Box<dyn ServerOsApi>, to_server: SenderWithContext<ServerInstruction>, ) { @@ -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" |