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) {
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() {

View File

@ -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()

View File

@ -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();

View File

@ -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))));

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

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

View File

@ -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