mirror of
https://github.com/zellij-org/zellij.git
synced 2024-11-26 22:15:19 +03:00
Add ability to detach a session
This commit is contained in:
parent
2487256664
commit
61aa104576
@ -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() {
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
|
@ -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))));
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -260,4 +260,5 @@ pub enum ServerContext {
|
||||
UnblockInputThread,
|
||||
ClientExit,
|
||||
Error,
|
||||
DetachSession,
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user