From 61aa1045764db63734532486cab0500e5828daad Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Fri, 21 May 2021 01:13:01 +0530 Subject: Add ability to detach a session --- 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 +++++-- 5 files changed, 81 insertions(+), 26 deletions(-) (limited to 'zellij-server/src') 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; -- cgit v1.2.3