Add ability to attach to sessions
This commit is contained in:
parent
ac082a1c93
commit
fa0a7e05c3
15 changed files with 208 additions and 64 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -2299,6 +2299,7 @@ dependencies = [
|
||||||
name = "zellij-client"
|
name = "zellij-client"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"names",
|
||||||
"termbg",
|
"termbg",
|
||||||
"zellij-utils",
|
"zellij-utils",
|
||||||
]
|
]
|
||||||
|
|
@ -2346,8 +2347,8 @@ dependencies = [
|
||||||
"interprocess",
|
"interprocess",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"names",
|
|
||||||
"nix",
|
"nix",
|
||||||
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"signal-hook 0.3.8",
|
"signal-hook 0.3.8",
|
||||||
|
|
|
||||||
15
src/main.rs
15
src/main.rs
|
|
@ -50,7 +50,20 @@ pub fn main() {
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
start_client(Box::new(os_input), opts, config);
|
if let Some(Command::Sessions(Sessions::Attach {
|
||||||
|
session_name,
|
||||||
|
force,
|
||||||
|
})) = opts.command.clone()
|
||||||
|
{
|
||||||
|
start_client(
|
||||||
|
Box::new(os_input),
|
||||||
|
opts,
|
||||||
|
config,
|
||||||
|
Some((session_name, force)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
start_client(Box::new(os_input), opts, config, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -300,6 +300,7 @@ impl ServerOsApi for FakeInputOutput {
|
||||||
}
|
}
|
||||||
fn add_client_sender(&self) {}
|
fn add_client_sender(&self) {}
|
||||||
fn remove_client_sender(&self) {}
|
fn remove_client_sender(&self) {}
|
||||||
|
fn send_to_temp_client(&self, _msg: ServerToClientMsg) {}
|
||||||
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()
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,6 @@ pub fn start(
|
||||||
start_server(server_os_input, PathBuf::from(""));
|
start_server(server_os_input, PathBuf::from(""));
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
start_client(client_os_input, opts, config);
|
start_client(client_os_input, opts, config, None);
|
||||||
let _ = server_thread.join();
|
let _ = server_thread.join();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ license = "MIT"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
names = "0.11.0"
|
||||||
termbg = "0.2.3"
|
termbg = "0.2.3"
|
||||||
zellij-utils = { path = "../zellij-utils/", version = "0.12.0" }
|
zellij-utils = { path = "../zellij-utils/", version = "0.12.0" }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use zellij_utils::{
|
||||||
channels::{SenderWithContext, OPENCALLS},
|
channels::{SenderWithContext, OPENCALLS},
|
||||||
errors::ContextType,
|
errors::ContextType,
|
||||||
input::{actions::Action, cast_termion_key, config::Config, keybinds::Keybinds},
|
input::{actions::Action, cast_termion_key, config::Config, keybinds::Keybinds},
|
||||||
ipc::ClientToServerMsg,
|
ipc::{ClientToServerMsg, ExitReason},
|
||||||
};
|
};
|
||||||
|
|
||||||
use termion::input::TermReadEventsAndRaw;
|
use termion::input::TermReadEventsAndRaw;
|
||||||
|
|
@ -169,7 +169,7 @@ impl InputHandler {
|
||||||
/// same as quitting Zellij).
|
/// same as quitting Zellij).
|
||||||
fn exit(&mut self) {
|
fn exit(&mut self) {
|
||||||
self.send_client_instructions
|
self.send_client_instructions
|
||||||
.send(ClientInstruction::Exit)
|
.send(ClientInstruction::Exit(ExitReason::Normal))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,26 +20,24 @@ use zellij_utils::{
|
||||||
consts::{SESSION_NAME, ZELLIJ_IPC_PIPE},
|
consts::{SESSION_NAME, ZELLIJ_IPC_PIPE},
|
||||||
errors::{ClientContext, ContextType, ErrorInstruction},
|
errors::{ClientContext, ContextType, ErrorInstruction},
|
||||||
input::{actions::Action, config::Config, options::Options},
|
input::{actions::Action, config::Config, options::Options},
|
||||||
ipc::{ClientAttributes, ClientToServerMsg, ServerToClientMsg},
|
ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Instructions related to the client-side application
|
/// Instructions related to the client-side application
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) enum ClientInstruction {
|
pub(crate) enum ClientInstruction {
|
||||||
Error(String),
|
Error(String),
|
||||||
Render(Option<String>),
|
Render(String),
|
||||||
UnblockInputThread,
|
UnblockInputThread,
|
||||||
Exit,
|
Exit(ExitReason),
|
||||||
ServerError(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ServerToClientMsg> for ClientInstruction {
|
impl From<ServerToClientMsg> for ClientInstruction {
|
||||||
fn from(instruction: ServerToClientMsg) -> Self {
|
fn from(instruction: ServerToClientMsg) -> Self {
|
||||||
match instruction {
|
match instruction {
|
||||||
ServerToClientMsg::Exit => ClientInstruction::Exit,
|
ServerToClientMsg::Exit(e) => ClientInstruction::Exit(e),
|
||||||
ServerToClientMsg::Render(buffer) => ClientInstruction::Render(buffer),
|
ServerToClientMsg::Render(buffer) => ClientInstruction::Render(buffer),
|
||||||
ServerToClientMsg::UnblockInputThread => ClientInstruction::UnblockInputThread,
|
ServerToClientMsg::UnblockInputThread => ClientInstruction::UnblockInputThread,
|
||||||
ServerToClientMsg::ServerError(backtrace) => ClientInstruction::ServerError(backtrace),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -47,9 +45,8 @@ impl From<ServerToClientMsg> for ClientInstruction {
|
||||||
impl From<&ClientInstruction> for ClientContext {
|
impl From<&ClientInstruction> for ClientContext {
|
||||||
fn from(client_instruction: &ClientInstruction) -> Self {
|
fn from(client_instruction: &ClientInstruction) -> Self {
|
||||||
match *client_instruction {
|
match *client_instruction {
|
||||||
ClientInstruction::Exit => ClientContext::Exit,
|
ClientInstruction::Exit(_) => ClientContext::Exit,
|
||||||
ClientInstruction::Error(_) => ClientContext::Error,
|
ClientInstruction::Error(_) => ClientContext::Error,
|
||||||
ClientInstruction::ServerError(_) => ClientContext::ServerError,
|
|
||||||
ClientInstruction::Render(_) => ClientContext::Render,
|
ClientInstruction::Render(_) => ClientContext::Render,
|
||||||
ClientInstruction::UnblockInputThread => ClientContext::UnblockInputThread,
|
ClientInstruction::UnblockInputThread => ClientContext::UnblockInputThread,
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +76,12 @@ fn spawn_server(socket_path: &Path) -> io::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: Config) {
|
pub fn start_client(
|
||||||
|
mut os_input: Box<dyn ClientOsApi>,
|
||||||
|
opts: CliArgs,
|
||||||
|
config: Config,
|
||||||
|
attach_to: Option<(String, bool)>,
|
||||||
|
) {
|
||||||
let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l";
|
let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l";
|
||||||
let take_snapshot = "\u{1b}[?1049h";
|
let take_snapshot = "\u{1b}[?1049h";
|
||||||
let bracketed_paste = "\u{1b}[?2004h";
|
let bracketed_paste = "\u{1b}[?2004h";
|
||||||
|
|
@ -94,12 +96,6 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
|
||||||
.write(clear_client_terminal_attributes.as_bytes())
|
.write(clear_client_terminal_attributes.as_bytes())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
std::env::set_var(&"ZELLIJ", "0");
|
std::env::set_var(&"ZELLIJ", "0");
|
||||||
std::env::set_var(&"ZELLIJ_SESSION_NAME", &*SESSION_NAME);
|
|
||||||
|
|
||||||
#[cfg(not(any(feature = "test", test)))]
|
|
||||||
spawn_server(&*ZELLIJ_IPC_PIPE).unwrap();
|
|
||||||
|
|
||||||
let mut command_is_executing = CommandIsExecuting::new();
|
|
||||||
|
|
||||||
let config_options = Options::from_cli(&config.options, opts.command.clone());
|
let config_options = Options::from_cli(&config.options, opts.command.clone());
|
||||||
|
|
||||||
|
|
@ -108,12 +104,34 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
|
||||||
position_and_size: full_screen_ws,
|
position_and_size: full_screen_ws,
|
||||||
palette,
|
palette,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "test", test)))]
|
||||||
|
let first_msg = if let Some((name, force)) = attach_to {
|
||||||
|
SESSION_NAME.set(name).unwrap();
|
||||||
|
std::env::set_var(&"ZELLIJ_SESSION_NAME", SESSION_NAME.get().unwrap());
|
||||||
|
|
||||||
|
ClientToServerMsg::AttachClient(client_attributes, force)
|
||||||
|
} else {
|
||||||
|
SESSION_NAME
|
||||||
|
.set(names::Generator::default().next().unwrap())
|
||||||
|
.unwrap();
|
||||||
|
std::env::set_var(&"ZELLIJ_SESSION_NAME", SESSION_NAME.get().unwrap());
|
||||||
|
|
||||||
|
spawn_server(&*ZELLIJ_IPC_PIPE).unwrap();
|
||||||
|
|
||||||
|
ClientToServerMsg::NewClient(client_attributes, Box::new(opts), Box::new(config_options))
|
||||||
|
};
|
||||||
|
#[cfg(any(feature = "test", test))]
|
||||||
|
let first_msg = {
|
||||||
|
let _ = SESSION_NAME.set("".into());
|
||||||
|
ClientToServerMsg::NewClient(client_attributes, Box::new(opts), Box::new(config_options))
|
||||||
|
};
|
||||||
|
|
||||||
os_input.connect_to_server(&*ZELLIJ_IPC_PIPE);
|
os_input.connect_to_server(&*ZELLIJ_IPC_PIPE);
|
||||||
os_input.send_to_server(ClientToServerMsg::NewClient(
|
os_input.send_to_server(first_msg);
|
||||||
client_attributes,
|
|
||||||
Box::new(opts),
|
let mut command_is_executing = CommandIsExecuting::new();
|
||||||
Box::new(config_options),
|
|
||||||
));
|
|
||||||
os_input.set_raw_mode(0);
|
os_input.set_raw_mode(0);
|
||||||
let _ = os_input
|
let _ = os_input
|
||||||
.get_stdout_writer()
|
.get_stdout_writer()
|
||||||
|
|
@ -170,7 +188,7 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
|
||||||
let send_client_instructions = send_client_instructions.clone();
|
let send_client_instructions = send_client_instructions.clone();
|
||||||
move || {
|
move || {
|
||||||
send_client_instructions
|
send_client_instructions
|
||||||
.send(ClientInstruction::Exit)
|
.send(ClientInstruction::Exit(ExitReason::Normal))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
@ -187,11 +205,8 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
|
||||||
move || loop {
|
move || loop {
|
||||||
let (instruction, err_ctx) = os_input.recv_from_server();
|
let (instruction, err_ctx) = os_input.recv_from_server();
|
||||||
err_ctx.update_thread_ctx();
|
err_ctx.update_thread_ctx();
|
||||||
match instruction {
|
if let ServerToClientMsg::Exit(_) = instruction {
|
||||||
ServerToClientMsg::Exit | ServerToClientMsg::ServerError(_) => {
|
should_break = true;
|
||||||
should_break = true;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
send_client_instructions.send(instruction.into()).unwrap();
|
send_client_instructions.send(instruction.into()).unwrap();
|
||||||
if should_break {
|
if should_break {
|
||||||
|
|
@ -216,6 +231,8 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let exit_msg: String;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (client_instruction, mut err_ctx) = receive_client_instructions
|
let (client_instruction, mut err_ctx) = receive_client_instructions
|
||||||
.recv()
|
.recv()
|
||||||
|
|
@ -223,21 +240,25 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
|
||||||
|
|
||||||
err_ctx.add_call(ContextType::Client((&client_instruction).into()));
|
err_ctx.add_call(ContextType::Client((&client_instruction).into()));
|
||||||
match client_instruction {
|
match client_instruction {
|
||||||
ClientInstruction::Exit => break,
|
ClientInstruction::Exit(reason) => {
|
||||||
|
match reason {
|
||||||
|
ExitReason::Error(_) => handle_error(format!("{}", reason)),
|
||||||
|
ExitReason::ForceDetached => {
|
||||||
|
os_input.send_to_server(ClientToServerMsg::ClientDetached);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
exit_msg = format!("{}", reason);
|
||||||
|
break;
|
||||||
|
}
|
||||||
ClientInstruction::Error(backtrace) => {
|
ClientInstruction::Error(backtrace) => {
|
||||||
let _ = os_input.send_to_server(ClientToServerMsg::Action(Action::Quit));
|
let _ = os_input.send_to_server(ClientToServerMsg::Action(Action::Quit));
|
||||||
handle_error(backtrace);
|
handle_error(backtrace);
|
||||||
}
|
}
|
||||||
ClientInstruction::ServerError(backtrace) => {
|
|
||||||
handle_error(backtrace);
|
|
||||||
}
|
|
||||||
ClientInstruction::Render(output) => {
|
ClientInstruction::Render(output) => {
|
||||||
if output.is_none() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let mut stdout = os_input.get_stdout_writer();
|
let mut stdout = os_input.get_stdout_writer();
|
||||||
stdout
|
stdout
|
||||||
.write_all(&output.unwrap().as_bytes())
|
.write_all(&output.as_bytes())
|
||||||
.expect("cannot write to stdout");
|
.expect("cannot write to stdout");
|
||||||
stdout.flush().expect("could not flush");
|
stdout.flush().expect("could not flush");
|
||||||
}
|
}
|
||||||
|
|
@ -255,8 +276,8 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
|
||||||
let restore_snapshot = "\u{1b}[?1049l";
|
let restore_snapshot = "\u{1b}[?1049l";
|
||||||
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
|
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
|
||||||
let goodbye_message = format!(
|
let goodbye_message = format!(
|
||||||
"{}\n{}{}{}Bye from Zellij!\n",
|
"{}\n{}{}{}{}\n",
|
||||||
goto_start_of_last_line, restore_snapshot, reset_style, show_cursor
|
goto_start_of_last_line, restore_snapshot, reset_style, show_cursor, exit_msg
|
||||||
);
|
);
|
||||||
|
|
||||||
os_input.unset_raw_mode(0);
|
os_input.unset_raw_mode(0);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use std::sync::{Arc, RwLock};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::{path::PathBuf, sync::mpsc};
|
use std::{path::PathBuf, sync::mpsc};
|
||||||
use wasmer::Store;
|
use wasmer::Store;
|
||||||
use zellij_tile::data::PluginCapabilities;
|
use zellij_tile::data::{Event, InputMode, PluginCapabilities};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
os_input_output::ServerOsApi,
|
os_input_output::ServerOsApi,
|
||||||
|
|
@ -30,8 +30,8 @@ use zellij_utils::{
|
||||||
channels::{ChannelWithContext, SenderType, SenderWithContext, SyncChannelWithContext},
|
channels::{ChannelWithContext, SenderType, SenderWithContext, SyncChannelWithContext},
|
||||||
cli::CliArgs,
|
cli::CliArgs,
|
||||||
errors::{ContextType, ErrorInstruction, ServerContext},
|
errors::{ContextType, ErrorInstruction, ServerContext},
|
||||||
input::options::Options,
|
input::{get_mode_info, options::Options},
|
||||||
ipc::{ClientAttributes, ClientToServerMsg, ServerToClientMsg},
|
ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
|
||||||
setup::{get_default_data_dir, install::populate_data_dir},
|
setup::{get_default_data_dir, install::populate_data_dir},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -44,13 +44,17 @@ pub(crate) enum ServerInstruction {
|
||||||
ClientExit,
|
ClientExit,
|
||||||
Error(String),
|
Error(String),
|
||||||
DetachSession,
|
DetachSession,
|
||||||
|
AttachClient(ClientAttributes, bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ClientToServerMsg> for ServerInstruction {
|
impl From<ClientToServerMsg> for ServerInstruction {
|
||||||
fn from(instruction: ClientToServerMsg) -> Self {
|
fn from(instruction: ClientToServerMsg) -> Self {
|
||||||
match instruction {
|
match instruction {
|
||||||
ClientToServerMsg::NewClient(pos, opts, options) => {
|
ClientToServerMsg::NewClient(attrs, opts, options) => {
|
||||||
ServerInstruction::NewClient(pos, opts, options)
|
ServerInstruction::NewClient(attrs, opts, options)
|
||||||
|
}
|
||||||
|
ClientToServerMsg::AttachClient(attrs, force) => {
|
||||||
|
ServerInstruction::AttachClient(attrs, force)
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
@ -66,6 +70,7 @@ impl From<&ServerInstruction> for ServerContext {
|
||||||
ServerInstruction::ClientExit => ServerContext::ClientExit,
|
ServerInstruction::ClientExit => ServerContext::ClientExit,
|
||||||
ServerInstruction::Error(_) => ServerContext::Error,
|
ServerInstruction::Error(_) => ServerContext::Error,
|
||||||
ServerInstruction::DetachSession => ServerContext::DetachSession,
|
ServerInstruction::DetachSession => ServerContext::DetachSession,
|
||||||
|
ServerInstruction::AttachClient(..) => ServerContext::AttachClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,8 +139,9 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||||
let session_data = session_data.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();
|
||||||
|
let session_state = session_state.clone();
|
||||||
|
|
||||||
move || route_thread_main(session_data, os_input, to_server)
|
move || route_thread_main(session_data, session_state, os_input, to_server)
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
#[cfg(not(any(feature = "test", test)))]
|
#[cfg(not(any(feature = "test", test)))]
|
||||||
|
|
@ -148,6 +154,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 session_data = session_data.clone();
|
let session_data = session_data.clone();
|
||||||
|
let session_state = session_state.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 || {
|
||||||
|
|
@ -160,6 +167,7 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||||
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 session_data = session_data.clone();
|
let session_data = session_data.clone();
|
||||||
|
let session_state = session_state.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())
|
||||||
|
|
@ -168,7 +176,14 @@ 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 to_server = to_server.clone();
|
let to_server = to_server.clone();
|
||||||
|
|
||||||
move || route_thread_main(session_data, os_input, to_server)
|
move || {
|
||||||
|
route_thread_main(
|
||||||
|
session_data,
|
||||||
|
session_state,
|
||||||
|
os_input,
|
||||||
|
to_server,
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
@ -204,6 +219,28 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||||
.send_to_pty(PtyInstruction::NewTab)
|
.send_to_pty(PtyInstruction::NewTab)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
ServerInstruction::AttachClient(attrs, _) => {
|
||||||
|
*session_state.write().unwrap() = SessionState::Attached;
|
||||||
|
let rlock = session_data.read().unwrap();
|
||||||
|
let session_data = rlock.as_ref().unwrap();
|
||||||
|
session_data
|
||||||
|
.senders
|
||||||
|
.send_to_screen(ScreenInstruction::TerminalResize(attrs.position_and_size))
|
||||||
|
.unwrap();
|
||||||
|
let mode_info =
|
||||||
|
get_mode_info(InputMode::Normal, attrs.palette, session_data.capabilities);
|
||||||
|
session_data
|
||||||
|
.senders
|
||||||
|
.send_to_screen(ScreenInstruction::ChangeMode(mode_info.clone()))
|
||||||
|
.unwrap();
|
||||||
|
session_data
|
||||||
|
.senders
|
||||||
|
.send_to_plugin(PluginInstruction::Update(
|
||||||
|
None,
|
||||||
|
Event::ModeUpdate(mode_info),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
ServerInstruction::UnblockInputThread => {
|
ServerInstruction::UnblockInputThread => {
|
||||||
if *session_state.read().unwrap() == SessionState::Attached {
|
if *session_state.read().unwrap() == SessionState::Attached {
|
||||||
os_input.send_to_client(ServerToClientMsg::UnblockInputThread);
|
os_input.send_to_client(ServerToClientMsg::UnblockInputThread);
|
||||||
|
|
@ -211,22 +248,26 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||||
}
|
}
|
||||||
ServerInstruction::ClientExit => {
|
ServerInstruction::ClientExit => {
|
||||||
*session_data.write().unwrap() = None;
|
*session_data.write().unwrap() = None;
|
||||||
os_input.send_to_client(ServerToClientMsg::Exit);
|
os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Normal));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ServerInstruction::DetachSession => {
|
ServerInstruction::DetachSession => {
|
||||||
*session_state.write().unwrap() = SessionState::Detached;
|
*session_state.write().unwrap() = SessionState::Detached;
|
||||||
os_input.send_to_client(ServerToClientMsg::Exit);
|
os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Normal));
|
||||||
os_input.remove_client_sender();
|
os_input.remove_client_sender();
|
||||||
}
|
}
|
||||||
ServerInstruction::Render(output) => {
|
ServerInstruction::Render(output) => {
|
||||||
if *session_state.read().unwrap() == SessionState::Attached {
|
if *session_state.read().unwrap() == SessionState::Attached {
|
||||||
os_input.send_to_client(ServerToClientMsg::Render(output));
|
os_input.send_to_client(
|
||||||
|
output.map_or(ServerToClientMsg::Exit(ExitReason::Normal), |op| {
|
||||||
|
ServerToClientMsg::Render(op)
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ServerInstruction::Error(backtrace) => {
|
ServerInstruction::Error(backtrace) => {
|
||||||
if *session_state.read().unwrap() == SessionState::Attached {
|
if *session_state.read().unwrap() == SessionState::Attached {
|
||||||
os_input.send_to_client(ServerToClientMsg::ServerError(backtrace));
|
os_input.send_to_client(ServerToClientMsg::Exit(ExitReason::Error(backtrace)));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,10 @@ use signal_hook::consts::*;
|
||||||
use zellij_tile::data::Palette;
|
use zellij_tile::data::Palette;
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
errors::ErrorContext,
|
errors::ErrorContext,
|
||||||
ipc::{ClientToServerMsg, IpcReceiverWithContext, IpcSenderWithContext, ServerToClientMsg},
|
ipc::{
|
||||||
|
ClientToServerMsg, ExitReason, IpcReceiverWithContext, IpcSenderWithContext,
|
||||||
|
ServerToClientMsg,
|
||||||
|
},
|
||||||
shared::default_palette,
|
shared::default_palette,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -156,6 +159,7 @@ 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);
|
||||||
|
fn send_to_temp_client(&self, msg: ServerToClientMsg);
|
||||||
/// Removes the sender to client
|
/// Removes the sender to client
|
||||||
fn remove_client_sender(&self);
|
fn remove_client_sender(&self);
|
||||||
/// Update the receiver socket for the client
|
/// Update the receiver socket for the client
|
||||||
|
|
@ -211,7 +215,6 @@ impl ServerOsApi for ServerOsInputOutput {
|
||||||
.send(msg);
|
.send(msg);
|
||||||
}
|
}
|
||||||
fn add_client_sender(&self) {
|
fn add_client_sender(&self) {
|
||||||
assert!(self.send_instructions_to_client.lock().unwrap().is_none());
|
|
||||||
let sender = self
|
let sender = self
|
||||||
.receive_instructions_from_client
|
.receive_instructions_from_client
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -219,7 +222,23 @@ impl ServerOsApi for ServerOsInputOutput {
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get_sender();
|
.get_sender();
|
||||||
*self.send_instructions_to_client.lock().unwrap() = Some(sender);
|
let old_sender = self
|
||||||
|
.send_instructions_to_client
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.replace(sender);
|
||||||
|
if let Some(mut sender) = old_sender {
|
||||||
|
sender.send(ServerToClientMsg::Exit(ExitReason::ForceDetached));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn send_to_temp_client(&self, msg: ServerToClientMsg) {
|
||||||
|
self.receive_instructions_from_client
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get_sender()
|
||||||
|
.send(msg);
|
||||||
}
|
}
|
||||||
fn remove_client_sender(&self) {
|
fn remove_client_sender(&self) {
|
||||||
assert!(self.send_instructions_to_client.lock().unwrap().is_some());
|
assert!(self.send_instructions_to_client.lock().unwrap().is_some());
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use zellij_utils::zellij_tile::data::Event;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
os_input_output::ServerOsApi, pty::PtyInstruction, screen::ScreenInstruction,
|
os_input_output::ServerOsApi, pty::PtyInstruction, screen::ScreenInstruction,
|
||||||
wasm_vm::PluginInstruction, ServerInstruction, SessionMetaData,
|
wasm_vm::PluginInstruction, ServerInstruction, SessionMetaData, SessionState,
|
||||||
};
|
};
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
channels::SenderWithContext,
|
channels::SenderWithContext,
|
||||||
|
|
@ -12,7 +12,7 @@ use zellij_utils::{
|
||||||
actions::{Action, Direction},
|
actions::{Action, Direction},
|
||||||
get_mode_info,
|
get_mode_info,
|
||||||
},
|
},
|
||||||
ipc::ClientToServerMsg,
|
ipc::{ClientToServerMsg, ExitReason, ServerToClientMsg},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn route_action(
|
fn route_action(
|
||||||
|
|
@ -203,6 +203,7 @@ fn route_action(
|
||||||
|
|
||||||
pub(crate) fn route_thread_main(
|
pub(crate) fn route_thread_main(
|
||||||
session_data: Arc<RwLock<Option<SessionMetaData>>>,
|
session_data: Arc<RwLock<Option<SessionMetaData>>>,
|
||||||
|
session_state: Arc<RwLock<SessionState>>,
|
||||||
os_input: Box<dyn ServerOsApi>,
|
os_input: Box<dyn ServerOsApi>,
|
||||||
to_server: SenderWithContext<ServerInstruction>,
|
to_server: SenderWithContext<ServerInstruction>,
|
||||||
) {
|
) {
|
||||||
|
|
@ -210,6 +211,7 @@ pub(crate) fn route_thread_main(
|
||||||
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 = session_data.read().unwrap();
|
let rlocked_sessions = session_data.read().unwrap();
|
||||||
|
|
||||||
match instruction {
|
match instruction {
|
||||||
ClientToServerMsg::Action(action) => {
|
ClientToServerMsg::Action(action) => {
|
||||||
if let Some(rlocked_sessions) = rlocked_sessions.as_ref() {
|
if let Some(rlocked_sessions) = rlocked_sessions.as_ref() {
|
||||||
|
|
@ -227,9 +229,24 @@ pub(crate) fn route_thread_main(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
ClientToServerMsg::NewClient(..) => {
|
ClientToServerMsg::NewClient(..) => {
|
||||||
|
if *session_state.read().unwrap() != SessionState::Uninitialized {
|
||||||
|
os_input.send_to_temp_client(ServerToClientMsg::Exit(ExitReason::Error(
|
||||||
|
"Cannot add new client".into(),
|
||||||
|
)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
os_input.add_client_sender();
|
os_input.add_client_sender();
|
||||||
to_server.send(instruction.into()).unwrap();
|
to_server.send(instruction.into()).unwrap();
|
||||||
}
|
}
|
||||||
|
ClientToServerMsg::AttachClient(_, force) => {
|
||||||
|
if *session_state.read().unwrap() == SessionState::Attached && !force {
|
||||||
|
os_input.send_to_temp_client(ServerToClientMsg::Exit(ExitReason::CannotAttach));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
os_input.add_client_sender();
|
||||||
|
to_server.send(instruction.into()).unwrap();
|
||||||
|
}
|
||||||
|
ClientToServerMsg::ClientDetached => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -386,6 +386,7 @@ impl Screen {
|
||||||
self.update_tabs();
|
self.update_tabs();
|
||||||
}
|
}
|
||||||
pub fn change_mode(&mut self, mode_info: ModeInfo) {
|
pub fn change_mode(&mut self, mode_info: ModeInfo) {
|
||||||
|
self.colors = mode_info.palette;
|
||||||
self.mode_info = mode_info;
|
self.mode_info = mode_info;
|
||||||
for tab in self.tabs.values_mut() {
|
for tab in self.tabs.values_mut() {
|
||||||
tab.mode_info = self.mode_info.clone();
|
tab.mode_info = self.mode_info.clone();
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ directories-next = "2.0"
|
||||||
interprocess = "1.1.1"
|
interprocess = "1.1.1"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
names = "0.11.0"
|
|
||||||
nix = "0.19.1"
|
nix = "0.19.1"
|
||||||
|
once_cell = "1.7.2"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8"
|
||||||
signal-hook = "0.3"
|
signal-hook = "0.3"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use crate::shared::set_permissions;
|
||||||
use directories_next::ProjectDirs;
|
use directories_next::ProjectDirs;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use nix::unistd::Uid;
|
use nix::unistd::Uid;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ const fn system_default_data_dir() -> &'static str {
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref UID: Uid = Uid::current();
|
static ref UID: Uid = Uid::current();
|
||||||
pub static ref SESSION_NAME: String = names::Generator::default().next().unwrap();
|
pub static ref SESSION_NAME: OnceCell<String> = OnceCell::new();
|
||||||
pub static ref ZELLIJ_PROJ_DIR: ProjectDirs =
|
pub static ref ZELLIJ_PROJ_DIR: ProjectDirs =
|
||||||
ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
|
ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
|
||||||
pub static ref ZELLIJ_SOCK_DIR: PathBuf = {
|
pub static ref ZELLIJ_SOCK_DIR: PathBuf = {
|
||||||
|
|
@ -43,7 +44,7 @@ lazy_static! {
|
||||||
let mut sock_dir = ZELLIJ_SOCK_DIR.clone();
|
let mut sock_dir = ZELLIJ_SOCK_DIR.clone();
|
||||||
fs::create_dir_all(&sock_dir).unwrap();
|
fs::create_dir_all(&sock_dir).unwrap();
|
||||||
set_permissions(&sock_dir).unwrap();
|
set_permissions(&sock_dir).unwrap();
|
||||||
sock_dir.push(&*SESSION_NAME);
|
sock_dir.push(SESSION_NAME.get().unwrap());
|
||||||
sock_dir
|
sock_dir
|
||||||
};
|
};
|
||||||
pub static ref ZELLIJ_TMP_DIR: PathBuf =
|
pub static ref ZELLIJ_TMP_DIR: PathBuf =
|
||||||
|
|
|
||||||
|
|
@ -261,4 +261,5 @@ pub enum ServerContext {
|
||||||
ClientExit,
|
ClientExit,
|
||||||
Error,
|
Error,
|
||||||
DetachSession,
|
DetachSession,
|
||||||
|
AttachClient,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use crate::{
|
||||||
use interprocess::local_socket::LocalSocketStream;
|
use interprocess::local_socket::LocalSocketStream;
|
||||||
use nix::unistd::dup;
|
use nix::unistd::dup;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::{Display, Error, Formatter};
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||||
|
|
@ -34,7 +35,7 @@ pub enum ClientType {
|
||||||
Writer,
|
Writer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||||
pub struct ClientAttributes {
|
pub struct ClientAttributes {
|
||||||
pub position_and_size: PositionAndSize,
|
pub position_and_size: PositionAndSize,
|
||||||
pub palette: Palette,
|
pub palette: Palette,
|
||||||
|
|
@ -56,7 +57,9 @@ pub enum ClientToServerMsg {
|
||||||
DisconnectFromSession,*/
|
DisconnectFromSession,*/
|
||||||
TerminalResize(PositionAndSize),
|
TerminalResize(PositionAndSize),
|
||||||
NewClient(ClientAttributes, Box<CliArgs>, Box<Options>),
|
NewClient(ClientAttributes, Box<CliArgs>, Box<Options>),
|
||||||
|
AttachClient(ClientAttributes, bool),
|
||||||
Action(Action),
|
Action(Action),
|
||||||
|
ClientDetached,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Types of messages sent from the server to the client
|
// Types of messages sent from the server to the client
|
||||||
|
|
@ -66,10 +69,34 @@ pub enum ServerToClientMsg {
|
||||||
SessionInfo(Session),
|
SessionInfo(Session),
|
||||||
// A list of sessions
|
// A list of sessions
|
||||||
SessionList(HashSet<Session>),*/
|
SessionList(HashSet<Session>),*/
|
||||||
Render(Option<String>),
|
Render(String),
|
||||||
UnblockInputThread,
|
UnblockInputThread,
|
||||||
Exit,
|
Exit(ExitReason),
|
||||||
ServerError(String),
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub enum ExitReason {
|
||||||
|
Normal,
|
||||||
|
ForceDetached,
|
||||||
|
CannotAttach,
|
||||||
|
Error(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ExitReason {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||||
|
match self {
|
||||||
|
Self::Normal => write!(f, "Bye from Zellij!"),
|
||||||
|
Self::ForceDetached => write!(
|
||||||
|
f,
|
||||||
|
"Session was detach from this client (possibly because another client connected)"
|
||||||
|
),
|
||||||
|
Self::CannotAttach => write!(
|
||||||
|
f,
|
||||||
|
"Session attached to another client. Use --force flag to force connect."
|
||||||
|
),
|
||||||
|
Self::Error(e) => write!(f, "Error occured in server:\n{}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends messages on a stream socket, along with an [`ErrorContext`].
|
/// Sends messages on a stream socket, along with an [`ErrorContext`].
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue