Add ability to detach a session

This commit is contained in:
Kunal Mohan 2021-05-21 01:13:01 +05:30
parent 2487256664
commit 61aa104576
9 changed files with 85 additions and 27 deletions

View file

@ -58,7 +58,7 @@ fn list_sessions() {
match fs::read_dir(&*ZELLIJ_SOCK_DIR) { match fs::read_dir(&*ZELLIJ_SOCK_DIR) {
Ok(files) => { Ok(files) => {
let mut is_empty = true; 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| { files.for_each(|file| {
let file = file.unwrap(); let file = file.unwrap();
if file.file_type().unwrap().is_socket() { if file.file_type().unwrap().is_socket() {

View file

@ -299,6 +299,7 @@ impl ServerOsApi for FakeInputOutput {
self.send_instructions_to_client.send(msg).unwrap(); self.send_instructions_to_client.send(msg).unwrap();
} }
fn add_client_sender(&self) {} fn add_client_sender(&self) {}
fn remove_client_sender(&self) {}
fn update_receiver(&mut self, _stream: LocalSocketStream) {} fn update_receiver(&mut self, _stream: LocalSocketStream) {}
fn load_palette(&self) -> Palette { fn load_palette(&self) -> Palette {
default_palette() default_palette()

View file

@ -43,6 +43,7 @@ pub(crate) enum ServerInstruction {
UnblockInputThread, UnblockInputThread,
ClientExit, ClientExit,
Error(String), Error(String),
DetachSession,
} }
impl From<ClientToServerMsg> for ServerInstruction { impl From<ClientToServerMsg> for ServerInstruction {
@ -52,6 +53,7 @@ impl From<ClientToServerMsg> for ServerInstruction {
ClientToServerMsg::NewClient(pos, opts, options) => { ClientToServerMsg::NewClient(pos, opts, options) => {
ServerInstruction::NewClient(pos, opts, options) ServerInstruction::NewClient(pos, opts, options)
} }
ClientToServerMsg::DetachSession => ServerInstruction::DetachSession,
_ => unreachable!(), _ => unreachable!(),
} }
} }
@ -65,6 +67,7 @@ impl From<&ServerInstruction> for ServerContext {
ServerInstruction::UnblockInputThread => ServerContext::UnblockInputThread, ServerInstruction::UnblockInputThread => ServerContext::UnblockInputThread,
ServerInstruction::ClientExit => ServerContext::ClientExit, ServerInstruction::ClientExit => ServerContext::ClientExit,
ServerInstruction::Error(_) => ServerContext::Error, 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) { pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
#[cfg(not(any(feature = "test", test)))] #[cfg(not(any(feature = "test", test)))]
daemonize::Daemonize::new() 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> = let (to_server, server_receiver): SyncChannelWithContext<ServerInstruction> =
mpsc::sync_channel(50); mpsc::sync_channel(50);
let to_server = SenderWithContext::new(SenderType::SyncSender(to_server)); 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)))] #[cfg(not(any(feature = "test", test)))]
std::panic::set_hook({ std::panic::set_hook({
@ -122,11 +133,11 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
thread::Builder::new() thread::Builder::new()
.name("server_router".to_string()) .name("server_router".to_string())
.spawn({ .spawn({
let sessions = sessions.clone(); let session_data = session_data.clone();
let os_input = os_input.clone(); let os_input = os_input.clone();
let to_server = to_server.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(); .unwrap();
#[cfg(not(any(feature = "test", test)))] #[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 os_input = os_input.clone();
let sessions = sessions.clone(); let session_data = session_data.clone();
let to_server = to_server.clone(); let to_server = to_server.clone();
let socket_path = socket_path.clone(); let socket_path = socket_path.clone();
move || { move || {
@ -150,16 +161,16 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
Ok(stream) => { Ok(stream) => {
let mut os_input = os_input.clone(); let mut os_input = os_input.clone();
os_input.update_receiver(stream); os_input.update_receiver(stream);
let sessions = sessions.clone(); let session_data = session_data.clone();
let to_server = to_server.clone(); let to_server = to_server.clone();
thread::Builder::new() thread::Builder::new()
.name("server_router".to_string()) .name("server_router".to_string())
.spawn({ .spawn({
let sessions = sessions.clone(); let session_data = session_data.clone();
let os_input = os_input.clone(); let os_input = os_input.clone();
let to_server = to_server.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(); .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())); err_ctx.add_call(ContextType::IPCServer((&instruction).into()));
match instruction { match instruction {
ServerInstruction::NewClient(client_attributes, opts, config_options) => { ServerInstruction::NewClient(client_attributes, opts, config_options) => {
let session_data = init_session( let session = init_session(
os_input.clone(), os_input.clone(),
opts, opts,
config_options, config_options,
to_server.clone(), to_server.clone(),
client_attributes, client_attributes,
session_state.clone(),
); );
*sessions.write().unwrap() = Some(session_data); *session_data.write().unwrap() = Some(session);
sessions *session_state.write().unwrap() = SessionState::Attached;
session_data
.read() .read()
.unwrap() .unwrap()
.as_ref() .as_ref()
@ -194,18 +207,29 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap(); .unwrap();
} }
ServerInstruction::UnblockInputThread => { ServerInstruction::UnblockInputThread => {
if *session_state.read().unwrap() == SessionState::Attached {
os_input.send_to_client(ServerToClientMsg::UnblockInputThread); os_input.send_to_client(ServerToClientMsg::UnblockInputThread);
} }
}
ServerInstruction::ClientExit => { ServerInstruction::ClientExit => {
*sessions.write().unwrap() = None; *session_data.write().unwrap() = None;
os_input.send_to_client(ServerToClientMsg::Exit); os_input.send_to_client(ServerToClientMsg::Exit);
break; break;
} }
ServerInstruction::DetachSession => {
*session_state.write().unwrap() = SessionState::Detached;
os_input.send_to_client(ServerToClientMsg::Exit);
os_input.remove_client_sender();
}
ServerInstruction::Render(output) => { 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) => { ServerInstruction::Error(backtrace) => {
if *session_state.read().unwrap() == SessionState::Attached {
os_input.send_to_client(ServerToClientMsg::ServerError(backtrace)); os_input.send_to_client(ServerToClientMsg::ServerError(backtrace));
}
break; break;
} }
} }
@ -220,6 +244,7 @@ fn init_session(
config_options: Box<Options>, config_options: Box<Options>,
to_server: SenderWithContext<ServerInstruction>, to_server: SenderWithContext<ServerInstruction>,
client_attributes: ClientAttributes, client_attributes: ClientAttributes,
session_state: Arc<RwLock<SessionState>>,
) -> SessionMetaData { ) -> SessionMetaData {
let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = mpsc::channel(); let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = mpsc::channel();
let to_screen = SenderWithContext::new(SenderType::Sender(to_screen)); let to_screen = SenderWithContext::new(SenderType::Sender(to_screen));
@ -285,7 +310,13 @@ fn init_session(
let max_panes = opts.max_panes; let max_panes = opts.max_panes;
move || { 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(); .unwrap();

View file

@ -156,6 +156,8 @@ pub trait ServerOsApi: Send + Sync {
fn send_to_client(&self, msg: ServerToClientMsg); fn send_to_client(&self, msg: ServerToClientMsg);
/// Adds a sender to client /// Adds a sender to client
fn add_client_sender(&self); fn add_client_sender(&self);
/// Removes the sender to client
fn remove_client_sender(&self);
/// Update the receiver socket for the client /// Update the receiver socket for the client
fn update_receiver(&mut self, stream: LocalSocketStream); fn update_receiver(&mut self, stream: LocalSocketStream);
fn load_palette(&self) -> Palette; fn load_palette(&self) -> Palette;
@ -219,6 +221,10 @@ impl ServerOsApi for ServerOsInputOutput {
.get_sender(); .get_sender();
*self.send_instructions_to_client.lock().unwrap() = Some(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) { fn update_receiver(&mut self, stream: LocalSocketStream) {
self.receive_instructions_from_client = self.receive_instructions_from_client =
Some(Arc::new(Mutex::new(IpcReceiverWithContext::new(stream)))); Some(Arc::new(Mutex::new(IpcReceiverWithContext::new(stream))));

View file

@ -188,16 +188,16 @@ fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn Server
} }
pub(crate) fn route_thread_main( pub(crate) fn route_thread_main(
sessions: Arc<RwLock<Option<SessionMetaData>>>, session_data: Arc<RwLock<Option<SessionMetaData>>>,
os_input: Box<dyn ServerOsApi>, os_input: Box<dyn ServerOsApi>,
to_server: SenderWithContext<ServerInstruction>, to_server: SenderWithContext<ServerInstruction>,
) { ) {
loop { loop {
let (instruction, err_ctx) = os_input.recv_from_client(); let (instruction, err_ctx) = os_input.recv_from_client();
err_ctx.update_thread_ctx(); err_ctx.update_thread_ctx();
let rlocked_sessions = sessions.read().unwrap(); let rlocked_sessions = session_data.read().unwrap();
match instruction { match instruction {
ClientToServerMsg::ClientExit => { ClientToServerMsg::ClientExit | ClientToServerMsg::DetachSession => {
to_server.send(instruction.into()).unwrap(); to_server.send(instruction.into()).unwrap();
break; break;
} }

View file

@ -3,6 +3,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::os::unix::io::RawFd; use std::os::unix::io::RawFd;
use std::str; use std::str;
use std::sync::{Arc, RwLock};
use zellij_utils::zellij_tile; use zellij_utils::zellij_tile;
@ -13,7 +14,7 @@ use crate::{
thread_bus::Bus, thread_bus::Bus,
ui::layout::Layout, ui::layout::Layout,
wasm_vm::PluginInstruction, wasm_vm::PluginInstruction,
ServerInstruction, ServerInstruction, SessionState,
}; };
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PluginCapabilities, TabInfo}; use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PluginCapabilities, TabInfo};
use zellij_utils::{ use zellij_utils::{
@ -137,6 +138,7 @@ pub(crate) struct Screen {
mode_info: ModeInfo, mode_info: ModeInfo,
input_mode: InputMode, input_mode: InputMode,
colors: Palette, colors: Palette,
session_state: Arc<RwLock<SessionState>>,
} }
impl Screen { impl Screen {
@ -147,6 +149,7 @@ impl Screen {
max_panes: Option<usize>, max_panes: Option<usize>,
mode_info: ModeInfo, mode_info: ModeInfo,
input_mode: InputMode, input_mode: InputMode,
session_state: Arc<RwLock<SessionState>>,
) -> Self { ) -> Self {
Screen { Screen {
bus, bus,
@ -157,6 +160,7 @@ impl Screen {
tabs: BTreeMap::new(), tabs: BTreeMap::new(),
mode_info, mode_info,
input_mode, input_mode,
session_state,
} }
} }
@ -177,6 +181,7 @@ impl Screen {
self.mode_info.clone(), self.mode_info.clone(),
self.input_mode, self.input_mode,
self.colors, self.colors,
self.session_state.clone(),
); );
self.active_tab_index = Some(tab_index); self.active_tab_index = Some(tab_index);
self.tabs.insert(tab_index, tab); self.tabs.insert(tab_index, tab);
@ -261,10 +266,12 @@ impl Screen {
.unwrap(); .unwrap();
if self.tabs.is_empty() { if self.tabs.is_empty() {
self.active_tab_index = None; self.active_tab_index = None;
if *self.session_state.read().unwrap() == SessionState::Attached {
self.bus self.bus
.senders .senders
.send_to_server(ServerInstruction::Render(None)) .send_to_server(ServerInstruction::Render(None))
.unwrap(); .unwrap();
}
} else { } else {
for t in self.tabs.values_mut() { for t in self.tabs.values_mut() {
if t.position > active_tab.position { if t.position > active_tab.position {
@ -286,6 +293,9 @@ impl Screen {
/// Renders this [`Screen`], which amounts to rendering its active [`Tab`]. /// Renders this [`Screen`], which amounts to rendering its active [`Tab`].
pub fn render(&mut self) { 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 let Some(active_tab) = self.get_active_tab_mut() {
if active_tab.get_active_pane().is_some() { if active_tab.get_active_pane().is_some() {
active_tab.render(); active_tab.render();
@ -333,6 +343,7 @@ impl Screen {
self.mode_info.clone(), self.mode_info.clone(),
self.input_mode, self.input_mode,
self.colors, self.colors,
self.session_state.clone(),
); );
tab.apply_layout(layout, new_pids); tab.apply_layout(layout, new_pids);
self.active_tab_index = Some(tab_index); self.active_tab_index = Some(tab_index);
@ -390,6 +401,7 @@ pub(crate) fn screen_thread_main(
max_panes: Option<usize>, max_panes: Option<usize>,
client_attributes: ClientAttributes, client_attributes: ClientAttributes,
config_options: Box<Options>, config_options: Box<Options>,
session_state: Arc<RwLock<SessionState>>,
) { ) {
let capabilities = config_options.simplified_ui; let capabilities = config_options.simplified_ui;
@ -405,6 +417,7 @@ pub(crate) fn screen_thread_main(
..ModeInfo::default() ..ModeInfo::default()
}, },
InputMode::Normal, InputMode::Normal,
session_state,
); );
loop { loop {
let (event, mut err_ctx) = screen let (event, mut err_ctx) = screen

View file

@ -10,11 +10,11 @@ use crate::{
thread_bus::ThreadSenders, thread_bus::ThreadSenders,
ui::{boundaries::Boundaries, layout::Layout, pane_resizer::PaneResizer}, ui::{boundaries::Boundaries, layout::Layout, pane_resizer::PaneResizer},
wasm_vm::PluginInstruction, wasm_vm::PluginInstruction,
ServerInstruction, ServerInstruction, SessionState,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::os::unix::io::RawFd; use std::os::unix::io::RawFd;
use std::sync::mpsc::channel; use std::sync::{mpsc::channel, Arc, RwLock};
use std::time::Instant; use std::time::Instant;
use std::{ use std::{
cmp::Reverse, cmp::Reverse,
@ -74,6 +74,7 @@ pub(crate) struct Tab {
pub senders: ThreadSenders, pub senders: ThreadSenders,
synchronize_is_active: bool, synchronize_is_active: bool,
should_clear_display_before_rendering: bool, should_clear_display_before_rendering: bool,
session_state: Arc<RwLock<SessionState>>,
pub mode_info: ModeInfo, pub mode_info: ModeInfo,
pub input_mode: InputMode, pub input_mode: InputMode,
pub colors: Palette, pub colors: Palette,
@ -242,6 +243,7 @@ impl Tab {
mode_info: ModeInfo, mode_info: ModeInfo,
input_mode: InputMode, input_mode: InputMode,
colors: Palette, colors: Palette,
session_state: Arc<RwLock<SessionState>>,
) -> Self { ) -> Self {
let panes = if let Some(PaneId::Terminal(pid)) = pane_id { let panes = if let Some(PaneId::Terminal(pid)) = pane_id {
let new_terminal = TerminalPane::new(pid, *full_screen_ws, colors); let new_terminal = TerminalPane::new(pid, *full_screen_ws, colors);
@ -273,6 +275,7 @@ impl Tab {
mode_info, mode_info,
input_mode, input_mode,
colors, colors,
session_state,
} }
} }
@ -722,7 +725,9 @@ impl Tab {
self.panes.iter().any(|(_, p)| p.contains_widechar()) self.panes.iter().any(|(_, p)| p.contains_widechar())
} }
pub fn render(&mut self) { 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 // 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 // in that case, we should not render as the app is exiting
return; return;

View file

@ -260,4 +260,5 @@ pub enum ServerContext {
UnblockInputThread, UnblockInputThread,
ClientExit, ClientExit,
Error, Error,
DetachSession,
} }

View file

@ -58,6 +58,7 @@ pub enum ClientToServerMsg {
TerminalResize(PositionAndSize), TerminalResize(PositionAndSize),
NewClient(ClientAttributes, Box<CliArgs>, Box<Options>), NewClient(ClientAttributes, Box<CliArgs>, Box<Options>),
Action(Action), Action(Action),
DetachSession,
} }
// Types of messages sent from the server to the client // Types of messages sent from the server to the client