summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKunal Mohan <kunalmohan99@gmail.com>2021-05-21 01:13:01 +0530
committerKunal Mohan <kunalmohan99@gmail.com>2021-05-22 22:19:50 +0530
commit61aa1045764db63734532486cab0500e5828daad (patch)
treedfebca9004a61d2e3bd2d4852a9dd6dc568875a3
parent2487256664d4142ee1906f0058cf7e8063cb6e10 (diff)
Add ability to detach a session
-rw-r--r--src/main.rs2
-rw-r--r--src/tests/fakes.rs1
-rw-r--r--zellij-server/src/lib.rs61
-rw-r--r--zellij-server/src/os_input_output.rs6
-rw-r--r--zellij-server/src/route.rs6
-rw-r--r--zellij-server/src/screen.rs23
-rw-r--r--zellij-server/src/tab.rs11
-rw-r--r--zellij-utils/src/errors.rs1
-rw-r--r--zellij-utils/src/ipc.rs1
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<ClientToServerMsg> for ServerInstruction {
@@ -52,6 +53,7 @@ impl From<ClientToServerMsg> 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<dyn ServerOsApi>, socket_path: PathBuf) {
#[cfg(not(any(feature = "test", test)))]
daemonize::Daemonize::new()
@@ -107,7 +117,8 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
let (to_server, server_receiver): SyncChannelWithContext<ServerInstruction> =
mpsc::sync_channel(50);
let to_server = SenderWithContext::new(SenderType::SyncSender(to_server));
- let sessions: Arc<RwLock<Option<SessionMetaData>>> = Arc::new(RwLock::new(None));
+ let session_data: Arc<RwLock<Option<SessionMetaData>>> = 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<Options>,
to_server: SenderWithContext<ServerInstruction>,
client_attributes: ClientAttributes,
+ session_state: Arc<RwLock<SessionState>>,
) -> SessionMetaData {
let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = 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<RwLock<Option<SessionMetaData>>>,
+ session_data: Arc<RwLock<Option<SessionMetaData>>>,
os_input: Box<dyn ServerOsApi>,
to_server: SenderWithContext<ServerInstruction>,
) {
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<RwLock<SessionState>>,
}
impl Screen {
@@ -147,6 +149,7 @@ impl Screen {
max_panes: Option<usize>,
mode_info: ModeInfo,
input_mode: InputMode,
+ session_state: Arc<RwLock<SessionState>>,
) -> 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<usize>,
client_attributes: ClientAttributes,
config_options: Box<Options>,
+ session_state: Arc<RwLock<SessionState>>,
) {
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<RwLock<SessionState>>,
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<RwLock<SessionState>>,
) -> 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<CliArgs>, Box<Options>),
Action(Action),
+ DetachSession,
}
// Types of messages sent from the server to the client