1pub mod os_input_output;
2pub mod output;
3pub mod panes;
4pub mod tab;
5
6mod background_jobs;
7mod logging_pipe;
8mod pane_groups;
9mod plugins;
10mod pty;
11mod pty_writer;
12mod route;
13mod screen;
14mod session_layout_metadata;
15mod terminal_bytes;
16mod thread_bus;
17mod ui;
18
19pub use daemonize;
20
21use background_jobs::{background_jobs_main, BackgroundJob};
22use log::info;
23use nix::sys::stat::{umask, Mode};
24use pty_writer::{pty_writer_main, PtyWriteInstruction};
25use std::collections::{BTreeMap, HashMap, HashSet};
26use std::{
27 net::{IpAddr, Ipv4Addr},
28 path::PathBuf,
29 sync::{Arc, RwLock},
30 thread,
31};
32use zellij_utils::envs;
33use zellij_utils::pane_size::Size;
34
35use wasmtime::{Config as WasmtimeConfig, Engine, Strategy};
36
37use crate::{
38 os_input_output::ServerOsApi,
39 plugins::{plugin_thread_main, PluginInstruction},
40 pty::{get_default_shell, pty_thread_main, Pty, PtyInstruction},
41 screen::{screen_thread_main, ScreenInstruction},
42 thread_bus::{Bus, ThreadSenders},
43};
44use route::route_thread_main;
45use zellij_utils::{
46 channels::{self, ChannelWithContext, SenderWithContext},
47 cli::CliArgs,
48 consts::{
49 DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE, ZELLIJ_SEEN_RELEASE_NOTES_CACHE_FILE,
50 },
51 data::{ConnectToSession, Event, InputMode, KeyWithModifier, PluginCapabilities, WebSharing},
52 errors::{prelude::*, ContextType, ErrorInstruction, FatalError, ServerContext},
53 home::{default_layout_dir, get_default_data_dir},
54 input::{
55 actions::Action,
56 command::{RunCommand, TerminalAction},
57 config::Config,
58 get_mode_info,
59 keybinds::Keybinds,
60 layout::{FloatingPaneLayout, Layout, PluginAlias, Run, RunPluginOrAlias},
61 options::Options,
62 plugins::PluginAliases,
63 },
64 ipc::{ClientAttributes, ExitReason, ServerToClientMsg},
65 shared::{default_palette, web_server_base_url},
66};
67
68pub type ClientId = u16;
69
70#[derive(Debug, Clone)]
72pub enum ServerInstruction {
73 NewClient(
74 ClientAttributes,
75 Box<CliArgs>,
76 Box<Config>, Box<Options>, Box<Layout>,
79 Box<PluginAliases>,
80 bool, bool, bool, ClientId,
84 ),
85 Render(Option<HashMap<ClientId, String>>),
86 UnblockInputThread,
87 ClientExit(ClientId),
88 RemoveClient(ClientId),
89 Error(String),
90 KillSession,
91 DetachSession(Vec<ClientId>),
92 AttachClient(
93 ClientAttributes,
94 Config, Options, Option<usize>, Option<(u32, bool)>, bool, ClientId,
100 ),
101 ConnStatus(ClientId),
102 Log(Vec<String>, ClientId),
103 LogError(Vec<String>, ClientId),
104 SwitchSession(ConnectToSession, ClientId),
105 UnblockCliPipeInput(String), CliPipeOutput(String, String), AssociatePipeWithClient {
108 pipe_id: String,
109 client_id: ClientId,
110 },
111 DisconnectAllClientsExcept(ClientId),
112 ChangeMode(ClientId, InputMode),
113 ChangeModeForAllClients(InputMode),
114 Reconfigure {
115 client_id: ClientId,
116 config: String,
117 write_config_to_disk: bool,
118 },
119 ConfigWrittenToDisk(ClientId, Config),
120 FailedToWriteConfigToDisk(ClientId, Option<PathBuf>), RebindKeys {
122 client_id: ClientId,
123 keys_to_rebind: Vec<(InputMode, KeyWithModifier, Vec<Action>)>,
124 keys_to_unbind: Vec<(InputMode, KeyWithModifier)>,
125 write_config_to_disk: bool,
126 },
127 StartWebServer(ClientId),
128 ShareCurrentSession(ClientId),
129 StopSharingCurrentSession(ClientId),
130 SendWebClientsForbidden(ClientId),
131 WebServerStarted(String), FailedToStartWebServer(String),
133}
134
135impl From<&ServerInstruction> for ServerContext {
136 fn from(server_instruction: &ServerInstruction) -> Self {
137 match *server_instruction {
138 ServerInstruction::NewClient(..) => ServerContext::NewClient,
139 ServerInstruction::Render(..) => ServerContext::Render,
140 ServerInstruction::UnblockInputThread => ServerContext::UnblockInputThread,
141 ServerInstruction::ClientExit(..) => ServerContext::ClientExit,
142 ServerInstruction::RemoveClient(..) => ServerContext::RemoveClient,
143 ServerInstruction::Error(_) => ServerContext::Error,
144 ServerInstruction::KillSession => ServerContext::KillSession,
145 ServerInstruction::DetachSession(..) => ServerContext::DetachSession,
146 ServerInstruction::AttachClient(..) => ServerContext::AttachClient,
147 ServerInstruction::ConnStatus(..) => ServerContext::ConnStatus,
148 ServerInstruction::Log(..) => ServerContext::Log,
149 ServerInstruction::LogError(..) => ServerContext::LogError,
150 ServerInstruction::SwitchSession(..) => ServerContext::SwitchSession,
151 ServerInstruction::UnblockCliPipeInput(..) => ServerContext::UnblockCliPipeInput,
152 ServerInstruction::CliPipeOutput(..) => ServerContext::CliPipeOutput,
153 ServerInstruction::AssociatePipeWithClient { .. } => {
154 ServerContext::AssociatePipeWithClient
155 },
156 ServerInstruction::DisconnectAllClientsExcept(..) => {
157 ServerContext::DisconnectAllClientsExcept
158 },
159 ServerInstruction::ChangeMode(..) => ServerContext::ChangeMode,
160 ServerInstruction::ChangeModeForAllClients(..) => {
161 ServerContext::ChangeModeForAllClients
162 },
163 ServerInstruction::Reconfigure { .. } => ServerContext::Reconfigure,
164 ServerInstruction::ConfigWrittenToDisk(..) => ServerContext::ConfigWrittenToDisk,
165 ServerInstruction::FailedToWriteConfigToDisk(..) => {
166 ServerContext::FailedToWriteConfigToDisk
167 },
168 ServerInstruction::RebindKeys { .. } => ServerContext::RebindKeys,
169 ServerInstruction::StartWebServer(..) => ServerContext::StartWebServer,
170 ServerInstruction::ShareCurrentSession(..) => ServerContext::ShareCurrentSession,
171 ServerInstruction::StopSharingCurrentSession(..) => {
172 ServerContext::StopSharingCurrentSession
173 },
174 ServerInstruction::WebServerStarted(..) => ServerContext::WebServerStarted,
175 ServerInstruction::FailedToStartWebServer(..) => ServerContext::FailedToStartWebServer,
176 ServerInstruction::SendWebClientsForbidden(..) => {
177 ServerContext::SendWebClientsForbidden
178 },
179 }
180 }
181}
182
183impl ErrorInstruction for ServerInstruction {
184 fn error(err: String) -> Self {
185 ServerInstruction::Error(err)
186 }
187}
188
189#[derive(Debug, Clone, Default)]
190pub(crate) struct SessionConfiguration {
191 runtime_config: HashMap<ClientId, Config>, saved_config: HashMap<ClientId, Config>, }
197
198impl SessionConfiguration {
199 pub fn new_saved_config(
200 &mut self,
201 client_id: ClientId,
202 new_saved_config: Config,
203 ) -> Vec<(ClientId, Config)> {
204 self.saved_config
205 .insert(client_id, new_saved_config.clone());
206
207 let mut config_changes = vec![];
208 for (client_id, current_runtime_config) in self.runtime_config.iter_mut() {
209 if *current_runtime_config != new_saved_config {
210 *current_runtime_config = new_saved_config.clone();
211 config_changes.push((*client_id, new_saved_config.clone()))
212 }
213 }
214 config_changes
215 }
216 pub fn set_client_saved_configuration(&mut self, client_id: ClientId, client_config: Config) {
217 self.saved_config.insert(client_id, client_config);
218 }
219 pub fn set_client_runtime_configuration(&mut self, client_id: ClientId, client_config: Config) {
220 self.runtime_config.insert(client_id, client_config);
221 }
222 pub fn get_client_keybinds(&self, client_id: &ClientId) -> Keybinds {
223 self.runtime_config
224 .get(client_id)
225 .or_else(|| self.saved_config.get(client_id))
226 .map(|c| c.keybinds.clone())
227 .unwrap_or_default()
228 }
229 pub fn get_client_default_input_mode(&self, client_id: &ClientId) -> InputMode {
230 self.runtime_config
231 .get(client_id)
232 .or_else(|| self.saved_config.get(client_id))
233 .and_then(|c| c.options.default_mode.clone())
234 .unwrap_or_default()
235 }
236 pub fn get_client_configuration(&self, client_id: &ClientId) -> Config {
237 self.runtime_config
238 .get(client_id)
239 .or_else(|| self.saved_config.get(client_id))
240 .cloned()
241 .unwrap_or_default()
242 }
243 pub fn reconfigure_runtime_config(
244 &mut self,
245 client_id: &ClientId,
246 stringified_config: String,
247 ) -> (Option<Config>, bool) {
248 let mut full_reconfigured_config = None;
250 let mut config_changed = false;
251 let current_client_configuration = self.get_client_configuration(client_id);
252 match Config::from_kdl(
253 &stringified_config,
254 Some(current_client_configuration.clone()),
255 ) {
256 Ok(new_config) => {
257 config_changed = current_client_configuration != new_config;
258 full_reconfigured_config = Some(new_config.clone());
259 self.runtime_config.insert(*client_id, new_config);
260 },
261 Err(e) => {
262 log::error!("Failed to reconfigure runtime config: {}", e);
263 },
264 }
265 (full_reconfigured_config, config_changed)
266 }
267 pub fn rebind_keys(
268 &mut self,
269 client_id: &ClientId,
270 keys_to_rebind: Vec<(InputMode, KeyWithModifier, Vec<Action>)>,
271 keys_to_unbind: Vec<(InputMode, KeyWithModifier)>,
272 ) -> (Option<Config>, bool) {
273 let mut full_reconfigured_config = None;
274 let mut config_changed = false;
275
276 if self.runtime_config.get(client_id).is_none() {
277 if let Some(saved_config) = self.saved_config.get(client_id) {
278 self.runtime_config.insert(*client_id, saved_config.clone());
279 }
280 }
281 match self.runtime_config.get_mut(client_id) {
282 Some(config) => {
283 for (input_mode, key_with_modifier) in keys_to_unbind {
284 let keys_in_mode = config
285 .keybinds
286 .0
287 .entry(input_mode)
288 .or_insert_with(Default::default);
289 let removed = keys_in_mode.remove(&key_with_modifier);
290 if removed.is_some() {
291 config_changed = true;
292 }
293 }
294 for (input_mode, key_with_modifier, actions) in keys_to_rebind {
295 let keys_in_mode = config
296 .keybinds
297 .0
298 .entry(input_mode)
299 .or_insert_with(Default::default);
300 if keys_in_mode.get(&key_with_modifier) != Some(&actions) {
301 config_changed = true;
302 keys_in_mode.insert(key_with_modifier, actions);
303 }
304 }
305 if config_changed {
306 full_reconfigured_config = Some(config.clone());
307 }
308 },
309 None => {
310 log::error!(
311 "Could not find runtime or saved configuration for client, cannot rebind keys"
312 );
313 },
314 }
315
316 (full_reconfigured_config, config_changed)
317 }
318}
319
320pub(crate) struct SessionMetaData {
321 pub senders: ThreadSenders,
322 pub capabilities: PluginCapabilities,
323 pub client_attributes: ClientAttributes,
324 pub default_shell: Option<TerminalAction>,
325 pub layout: Box<Layout>,
326 pub current_input_modes: HashMap<ClientId, InputMode>,
327 pub session_configuration: SessionConfiguration,
328 pub web_sharing: WebSharing, screen_thread: Option<thread::JoinHandle<()>>,
333 pty_thread: Option<thread::JoinHandle<()>>,
334 plugin_thread: Option<thread::JoinHandle<()>>,
335 pty_writer_thread: Option<thread::JoinHandle<()>>,
336 background_jobs_thread: Option<thread::JoinHandle<()>>,
337}
338
339impl SessionMetaData {
340 pub fn get_client_keybinds_and_mode(
341 &self,
342 client_id: &ClientId,
343 ) -> Option<(Keybinds, &InputMode, InputMode)> {
344 let client_keybinds = self.session_configuration.get_client_keybinds(client_id);
347 let default_input_mode = self
348 .session_configuration
349 .get_client_default_input_mode(client_id);
350 match self.current_input_modes.get(client_id) {
351 Some(client_input_mode) => {
352 Some((client_keybinds, client_input_mode, default_input_mode))
353 },
354 _ => None,
355 }
356 }
357 pub fn change_mode_for_all_clients(&mut self, input_mode: InputMode) {
358 let all_clients: Vec<ClientId> = self.current_input_modes.keys().copied().collect();
359 for client_id in all_clients {
360 self.current_input_modes.insert(client_id, input_mode);
361 }
362 }
363 pub fn propagate_configuration_changes(
364 &mut self,
365 config_changes: Vec<(ClientId, Config)>,
366 config_was_written_to_disk: bool,
367 ) {
368 for (client_id, new_config) in config_changes {
369 self.default_shell = new_config.options.default_shell.as_ref().map(|shell| {
370 TerminalAction::RunCommand(RunCommand {
371 command: shell.clone(),
372 cwd: new_config.options.default_cwd.clone(),
373 use_terminal_title: true,
374 ..Default::default()
375 })
376 });
377 self.senders
378 .send_to_screen(ScreenInstruction::Reconfigure {
379 client_id,
380 keybinds: new_config.keybinds.clone(),
381 default_mode: new_config
382 .options
383 .default_mode
384 .unwrap_or_else(Default::default),
385 theme: new_config
386 .theme_config(new_config.options.theme.as_ref())
387 .unwrap_or_else(|| default_palette().into()),
388 simplified_ui: new_config.options.simplified_ui.unwrap_or(false),
389 default_shell: new_config.options.default_shell,
390 pane_frames: new_config.options.pane_frames.unwrap_or(true),
391 copy_command: new_config.options.copy_command,
392 copy_to_clipboard: new_config.options.copy_clipboard,
393 copy_on_select: new_config.options.copy_on_select.unwrap_or(true),
394 auto_layout: new_config.options.auto_layout.unwrap_or(true),
395 rounded_corners: new_config.ui.pane_frames.rounded_corners,
396 hide_session_name: new_config.ui.pane_frames.hide_session_name,
397 stacked_resize: new_config.options.stacked_resize.unwrap_or(true),
398 default_editor: new_config.options.scrollback_editor.clone(),
399 advanced_mouse_actions: new_config
400 .options
401 .advanced_mouse_actions
402 .unwrap_or(true),
403 })
404 .unwrap();
405 self.senders
406 .send_to_plugin(PluginInstruction::Reconfigure {
407 client_id,
408 keybinds: Some(new_config.keybinds),
409 default_mode: new_config.options.default_mode,
410 default_shell: self.default_shell.clone(),
411 was_written_to_disk: config_was_written_to_disk,
412 })
413 .unwrap();
414 self.senders
415 .send_to_pty(PtyInstruction::Reconfigure {
416 client_id,
417 default_editor: new_config.options.scrollback_editor,
418 post_command_discovery_hook: new_config.options.post_command_discovery_hook,
419 })
420 .unwrap();
421 }
422 }
423}
424
425impl Drop for SessionMetaData {
426 fn drop(&mut self) {
427 let _ = self.senders.send_to_pty(PtyInstruction::Exit);
428 let _ = self.senders.send_to_screen(ScreenInstruction::Exit);
429 let _ = self.senders.send_to_plugin(PluginInstruction::Exit);
430 let _ = self.senders.send_to_pty_writer(PtyWriteInstruction::Exit);
431 let _ = self.senders.send_to_background_jobs(BackgroundJob::Exit);
432 if let Some(screen_thread) = self.screen_thread.take() {
433 let _ = screen_thread.join();
434 }
435 if let Some(pty_thread) = self.pty_thread.take() {
436 let _ = pty_thread.join();
437 }
438 if let Some(plugin_thread) = self.plugin_thread.take() {
439 let _ = plugin_thread.join();
440 }
441 if let Some(pty_writer_thread) = self.pty_writer_thread.take() {
442 let _ = pty_writer_thread.join();
443 }
444 if let Some(background_jobs_thread) = self.background_jobs_thread.take() {
445 let _ = background_jobs_thread.join();
446 }
447 }
448}
449
450macro_rules! remove_client {
451 ($client_id:expr, $os_input:expr, $session_state:expr) => {
452 $os_input.remove_client($client_id).unwrap();
453 $session_state.write().unwrap().remove_client($client_id);
454 };
455}
456
457macro_rules! send_to_client {
458 ($client_id:expr, $os_input:expr, $msg:expr, $session_state:expr) => {
459 let send_to_client_res = $os_input.send_to_client($client_id, $msg);
460 if let Err(e) = send_to_client_res {
461 let context = match e.downcast_ref::<ZellijError>() {
463 Some(ZellijError::ClientTooSlow { .. }) => {
464 format!(
465 "client {} is processing server messages too slow",
466 $client_id
467 )
468 },
469 _ => {
470 format!("failed to route server message to client {}", $client_id)
471 },
472 };
473 Err::<(), _>(e).context(context).non_fatal();
475 remove_client!($client_id, $os_input, $session_state);
477 }
478 };
479}
480
481#[derive(Clone, Debug, PartialEq)]
482pub(crate) struct SessionState {
483 clients: HashMap<ClientId, Option<(Size, bool)>>, pipes: HashMap<String, ClientId>, }
486
487impl SessionState {
488 pub fn new() -> Self {
489 SessionState {
490 clients: HashMap::new(),
491 pipes: HashMap::new(),
492 }
493 }
494 pub fn new_client(&mut self) -> ClientId {
495 let clients: HashSet<ClientId> = self.clients.keys().copied().collect();
496 let mut next_client_id = 1;
497 loop {
498 if clients.contains(&next_client_id) {
499 next_client_id += 1;
500 } else {
501 break;
502 }
503 }
504 self.clients.insert(next_client_id, None);
505 next_client_id
506 }
507 pub fn associate_pipe_with_client(&mut self, pipe_id: String, client_id: ClientId) {
508 self.pipes.insert(pipe_id, client_id);
509 }
510 pub fn remove_client(&mut self, client_id: ClientId) {
511 self.clients.remove(&client_id);
512 self.pipes.retain(|_p_id, c_id| c_id != &client_id);
513 }
514 pub fn set_client_size(&mut self, client_id: ClientId, size: Size) {
515 self.clients
516 .entry(client_id)
517 .or_insert_with(Default::default)
518 .as_mut()
519 .map(|(s, _is_web_client)| *s = size);
520 }
521 pub fn set_client_data(&mut self, client_id: ClientId, size: Size, is_web_client: bool) {
522 self.clients.insert(client_id, Some((size, is_web_client)));
523 }
524 pub fn min_client_terminal_size(&self) -> Option<Size> {
525 let mut rows: Vec<usize> = self
527 .clients
528 .values()
529 .filter_map(|size_and_is_web_client| {
530 size_and_is_web_client.map(|(size, _is_web_client)| size.rows)
531 })
532 .collect();
533 rows.sort_unstable();
534 let mut cols: Vec<usize> = self
535 .clients
536 .values()
537 .filter_map(|size_and_is_web_client| {
538 size_and_is_web_client.map(|(size, _is_web_client)| size.cols)
539 })
540 .collect();
541 cols.sort_unstable();
542 let min_rows = rows.first();
543 let min_cols = cols.first();
544 match (min_rows, min_cols) {
545 (Some(min_rows), Some(min_cols)) => Some(Size {
546 rows: *min_rows,
547 cols: *min_cols,
548 }),
549 _ => None,
550 }
551 }
552 pub fn client_ids(&self) -> Vec<ClientId> {
553 self.clients.keys().copied().collect()
554 }
555 pub fn web_client_ids(&self) -> Vec<ClientId> {
556 self.clients
557 .iter()
558 .filter_map(|(c_id, size_and_is_web_client)| {
559 size_and_is_web_client
560 .and_then(|(_s, is_web_client)| if is_web_client { Some(*c_id) } else { None })
561 })
562 .collect()
563 }
564 pub fn get_pipe(&self, pipe_name: &str) -> Option<ClientId> {
565 self.pipes.get(pipe_name).copied()
566 }
567 pub fn active_clients_are_connected(&self) -> bool {
568 let ids_of_pipe_clients: HashSet<ClientId> = self.pipes.values().copied().collect();
569 let mut active_clients_connected = false;
570 for client_id in self.clients.keys() {
571 if ids_of_pipe_clients.contains(client_id) {
572 continue;
573 }
574 active_clients_connected = true;
575 }
576 active_clients_connected
577 }
578}
579
580pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
581 info!("Starting Zellij server!");
582
583 let current_umask = umask(Mode::all());
585 umask(current_umask);
586 daemonize::Daemonize::new()
587 .working_directory(std::env::current_dir().unwrap())
588 .umask(current_umask.bits() as u32)
589 .start()
590 .expect("could not daemonize the server process");
591
592 envs::set_zellij("0".to_string());
593
594 let (to_server, server_receiver): ChannelWithContext<ServerInstruction> = channels::bounded(50);
595 let to_server = SenderWithContext::new(to_server);
596 let session_data: Arc<RwLock<Option<SessionMetaData>>> = Arc::new(RwLock::new(None));
597 let session_state = Arc::new(RwLock::new(SessionState::new()));
598
599 std::panic::set_hook({
600 use zellij_utils::errors::handle_panic;
601 let to_server = to_server.clone();
602 Box::new(move |info| {
603 handle_panic(info, &to_server);
604 })
605 });
606
607 let _ = thread::Builder::new()
608 .name("server_listener".to_string())
609 .spawn({
610 use interprocess::local_socket::LocalSocketListener;
611 use zellij_utils::shared::set_permissions;
612
613 let os_input = os_input.clone();
614 let session_data = session_data.clone();
615 let session_state = session_state.clone();
616 let to_server = to_server.clone();
617 let socket_path = socket_path.clone();
618 move || {
619 drop(std::fs::remove_file(&socket_path));
620 let listener = LocalSocketListener::bind(&*socket_path).unwrap();
621 drop(set_permissions(&socket_path, 0o1700));
626 for stream in listener.incoming() {
627 match stream {
628 Ok(stream) => {
629 let mut os_input = os_input.clone();
630 let client_id = session_state.write().unwrap().new_client();
631 let receiver = os_input.new_client(client_id, stream).unwrap();
632 let session_data = session_data.clone();
633 let session_state = session_state.clone();
634 let to_server = to_server.clone();
635 thread::Builder::new()
636 .name("server_router".to_string())
637 .spawn(move || {
638 route_thread_main(
639 session_data,
640 session_state,
641 os_input,
642 to_server,
643 receiver,
644 client_id,
645 )
646 .fatal()
647 })
648 .unwrap();
649 },
650 Err(err) => {
651 panic!("err {:?}", err);
652 },
653 }
654 }
655 }
656 });
657
658 loop {
659 let (instruction, mut err_ctx) = server_receiver.recv().unwrap();
660 err_ctx.add_call(ContextType::IPCServer((&instruction).into()));
661 match instruction {
662 ServerInstruction::NewClient(
663 client_attributes,
665 opts,
666 config,
667 runtime_config_options,
668 layout,
669 plugin_aliases,
670 should_launch_setup_wizard,
671 is_web_client,
672 layout_is_welcome_screen,
673 client_id,
674 ) => {
675 let mut session = init_session(
676 os_input.clone(),
677 to_server.clone(),
678 client_attributes.clone(),
679 SessionOptions {
680 opts,
681 layout: layout.clone(),
682 config_options: runtime_config_options.clone(),
683 },
684 *config.clone(),
685 plugin_aliases,
686 client_id,
687 );
688 let mut runtime_configuration = config.clone();
689 runtime_configuration.options = *runtime_config_options.clone();
690 session
691 .session_configuration
692 .set_client_saved_configuration(client_id, *config.clone());
693 session
694 .session_configuration
695 .set_client_runtime_configuration(client_id, *runtime_configuration);
696 let default_input_mode = runtime_config_options.default_mode.unwrap_or_default();
697 session
698 .current_input_modes
699 .insert(client_id, default_input_mode);
700
701 *session_data.write().unwrap() = Some(session);
702 session_state.write().unwrap().set_client_data(
703 client_id,
704 client_attributes.size,
705 is_web_client,
706 );
707
708 let default_shell = runtime_config_options.default_shell.map(|shell| {
709 TerminalAction::RunCommand(RunCommand {
710 command: shell,
711 cwd: config.options.default_cwd.clone(),
712 use_terminal_title: true,
713 ..Default::default()
714 })
715 });
716 let cwd = runtime_config_options.default_cwd;
717
718 let spawn_tabs = |tab_layout,
719 floating_panes_layout,
720 tab_name,
721 swap_layouts,
722 should_focus_tab| {
723 session_data
724 .read()
725 .unwrap()
726 .as_ref()
727 .unwrap()
728 .senders
729 .send_to_screen(ScreenInstruction::NewTab(
730 cwd.clone(),
731 default_shell.clone(),
732 tab_layout,
733 floating_panes_layout,
734 tab_name,
735 swap_layouts,
736 should_focus_tab,
737 (client_id, is_web_client),
738 ))
739 .unwrap()
740 };
741
742 if layout.has_tabs() {
743 let focused_tab_index = layout.focused_tab_index().unwrap_or(0);
744 for (tab_index, (tab_name, tab_layout, floating_panes_layout)) in
745 layout.tabs().into_iter().enumerate()
746 {
747 let should_focus_tab = tab_index == focused_tab_index;
748 spawn_tabs(
749 Some(tab_layout.clone()),
750 floating_panes_layout.clone(),
751 tab_name,
752 (
753 layout.swap_tiled_layouts.clone(),
754 layout.swap_floating_layouts.clone(),
755 ),
756 should_focus_tab,
757 );
758 }
759 } else {
760 let mut floating_panes =
761 layout.template.map(|t| t.1).clone().unwrap_or_default();
762 if should_launch_setup_wizard {
763 let setup_wizard = setup_wizard_floating_pane();
766 floating_panes.push(setup_wizard);
767 } else if should_show_release_notes(
768 runtime_config_options.show_release_notes,
769 layout_is_welcome_screen,
770 ) {
771 let about = about_floating_pane();
772 floating_panes.push(about);
773 } else if should_show_startup_tip(
774 runtime_config_options.show_startup_tips,
775 layout_is_welcome_screen,
776 ) {
777 let tip = tip_floating_pane();
778 floating_panes.push(tip);
779 }
780 spawn_tabs(
781 None,
782 floating_panes,
783 None,
784 (
785 layout.swap_tiled_layouts.clone(),
786 layout.swap_floating_layouts.clone(),
787 ),
788 true,
789 );
790 }
791 session_data
792 .read()
793 .unwrap()
794 .as_ref()
795 .unwrap()
796 .senders
797 .send_to_plugin(PluginInstruction::AddClient(client_id))
798 .unwrap();
799 },
800 ServerInstruction::AttachClient(
801 attrs,
802 config,
803 runtime_config_options,
804 tab_position_to_focus,
805 pane_id_to_focus,
806 is_web_client,
807 client_id,
808 ) => {
809 let mut rlock = session_data.write().unwrap();
810 let session_data = rlock.as_mut().unwrap();
811
812 let mut runtime_configuration = config.clone();
813 runtime_configuration.options = runtime_config_options.clone();
814 session_data
815 .session_configuration
816 .set_client_saved_configuration(client_id, config.clone());
817 session_data
818 .session_configuration
819 .set_client_runtime_configuration(client_id, runtime_configuration);
820
821 let default_input_mode = config.options.default_mode.unwrap_or_default();
822 session_data
823 .current_input_modes
824 .insert(client_id, default_input_mode);
825
826 session_state.write().unwrap().set_client_data(
827 client_id,
828 attrs.size,
829 is_web_client,
830 );
831 let min_size = session_state
832 .read()
833 .unwrap()
834 .min_client_terminal_size()
835 .unwrap();
836 session_data
837 .senders
838 .send_to_screen(ScreenInstruction::TerminalResize(min_size))
839 .unwrap();
840 session_data
841 .senders
842 .send_to_screen(ScreenInstruction::AddClient(
843 client_id,
844 is_web_client,
845 tab_position_to_focus,
846 pane_id_to_focus,
847 ))
848 .unwrap();
849 session_data
850 .senders
851 .send_to_plugin(PluginInstruction::AddClient(client_id))
852 .unwrap();
853 let default_mode = config.options.default_mode.unwrap_or_default();
854 let mode_info = get_mode_info(
855 default_mode,
856 &attrs,
857 session_data.capabilities,
858 &session_data
859 .session_configuration
860 .get_client_keybinds(&client_id),
861 Some(default_mode),
862 );
863 session_data
864 .senders
865 .send_to_screen(ScreenInstruction::ChangeMode(mode_info.clone(), client_id))
866 .unwrap();
867 session_data
868 .senders
869 .send_to_plugin(PluginInstruction::Update(vec![(
870 None,
871 Some(client_id),
872 Event::ModeUpdate(mode_info),
873 )]))
874 .unwrap();
875 },
876 ServerInstruction::UnblockInputThread => {
877 let client_ids = session_state.read().unwrap().client_ids();
878 for client_id in client_ids {
879 send_to_client!(
880 client_id,
881 os_input,
882 ServerToClientMsg::UnblockInputThread,
883 session_state
884 );
885 }
886 },
887 ServerInstruction::UnblockCliPipeInput(pipe_name) => {
888 let pipe = session_state.read().unwrap().get_pipe(&pipe_name);
889 match pipe {
890 Some(client_id) => {
891 send_to_client!(
892 client_id,
893 os_input,
894 ServerToClientMsg::UnblockCliPipeInput(pipe_name.clone()),
895 session_state
896 );
897 },
898 None => {
899 let client_ids = session_state.read().unwrap().client_ids();
901 for client_id in client_ids {
902 send_to_client!(
903 client_id,
904 os_input,
905 ServerToClientMsg::UnblockCliPipeInput(pipe_name.clone()),
906 session_state
907 );
908 }
909 },
910 }
911 },
912 ServerInstruction::CliPipeOutput(pipe_name, output) => {
913 let pipe = session_state.read().unwrap().get_pipe(&pipe_name);
914 match pipe {
915 Some(client_id) => {
916 send_to_client!(
917 client_id,
918 os_input,
919 ServerToClientMsg::CliPipeOutput(pipe_name.clone(), output.clone()),
920 session_state
921 );
922 },
923 None => {
924 let client_ids = session_state.read().unwrap().client_ids();
926 for client_id in client_ids {
927 send_to_client!(
928 client_id,
929 os_input,
930 ServerToClientMsg::CliPipeOutput(pipe_name.clone(), output.clone()),
931 session_state
932 );
933 }
934 },
935 }
936 },
937 ServerInstruction::ClientExit(client_id) => {
938 let _ =
939 os_input.send_to_client(client_id, ServerToClientMsg::Exit(ExitReason::Normal));
940 remove_client!(client_id, os_input, session_state);
941 if let Some(min_size) = session_state.read().unwrap().min_client_terminal_size() {
942 session_data
943 .write()
944 .unwrap()
945 .as_ref()
946 .unwrap()
947 .senders
948 .send_to_screen(ScreenInstruction::TerminalResize(min_size))
949 .unwrap();
950 }
951 session_data
952 .write()
953 .unwrap()
954 .as_ref()
955 .unwrap()
956 .senders
957 .send_to_screen(ScreenInstruction::RemoveClient(client_id))
958 .unwrap();
959 session_data
960 .write()
961 .unwrap()
962 .as_ref()
963 .unwrap()
964 .senders
965 .send_to_plugin(PluginInstruction::RemoveClient(client_id))
966 .unwrap();
967 if !session_state.read().unwrap().active_clients_are_connected() {
968 *session_data.write().unwrap() = None;
969 let client_ids_to_cleanup: Vec<ClientId> = session_state
970 .read()
971 .unwrap()
972 .clients
973 .keys()
974 .copied()
975 .collect();
976 for client_id in client_ids_to_cleanup {
978 remove_client!(client_id, os_input, session_state);
979 }
980 break;
981 }
982 },
983 ServerInstruction::RemoveClient(client_id) => {
984 remove_client!(client_id, os_input, session_state);
985 if let Some(min_size) = session_state.read().unwrap().min_client_terminal_size() {
986 session_data
987 .write()
988 .unwrap()
989 .as_ref()
990 .unwrap()
991 .senders
992 .send_to_screen(ScreenInstruction::TerminalResize(min_size))
993 .unwrap();
994 }
995 session_data
996 .write()
997 .unwrap()
998 .as_ref()
999 .unwrap()
1000 .senders
1001 .send_to_screen(ScreenInstruction::RemoveClient(client_id))
1002 .unwrap();
1003 session_data
1004 .write()
1005 .unwrap()
1006 .as_ref()
1007 .unwrap()
1008 .senders
1009 .send_to_plugin(PluginInstruction::RemoveClient(client_id))
1010 .unwrap();
1011 },
1012 ServerInstruction::SendWebClientsForbidden(client_id) => {
1013 let _ = os_input.send_to_client(
1014 client_id,
1015 ServerToClientMsg::Exit(ExitReason::WebClientsForbidden),
1016 );
1017 remove_client!(client_id, os_input, session_state);
1018 if let Some(min_size) = session_state.read().unwrap().min_client_terminal_size() {
1019 session_data
1020 .write()
1021 .unwrap()
1022 .as_ref()
1023 .unwrap()
1024 .senders
1025 .send_to_screen(ScreenInstruction::TerminalResize(min_size))
1026 .unwrap();
1027 }
1028 },
1029 ServerInstruction::KillSession => {
1030 let client_ids = session_state.read().unwrap().client_ids();
1031 for client_id in client_ids {
1032 let _ = os_input
1033 .send_to_client(client_id, ServerToClientMsg::Exit(ExitReason::Normal));
1034 remove_client!(client_id, os_input, session_state);
1035 }
1036 break;
1037 },
1038 ServerInstruction::DisconnectAllClientsExcept(client_id) => {
1039 let client_ids: Vec<ClientId> = session_state
1040 .read()
1041 .unwrap()
1042 .client_ids()
1043 .iter()
1044 .copied()
1045 .filter(|c| c != &client_id)
1046 .collect();
1047 for client_id in client_ids {
1048 let _ = os_input
1049 .send_to_client(client_id, ServerToClientMsg::Exit(ExitReason::Normal));
1050 remove_client!(client_id, os_input, session_state);
1051 }
1052 },
1053 ServerInstruction::DetachSession(client_ids) => {
1054 for client_id in client_ids {
1055 let _ = os_input
1056 .send_to_client(client_id, ServerToClientMsg::Exit(ExitReason::Normal));
1057 remove_client!(client_id, os_input, session_state);
1058 if let Some(min_size) = session_state.read().unwrap().min_client_terminal_size()
1059 {
1060 session_data
1061 .write()
1062 .unwrap()
1063 .as_ref()
1064 .unwrap()
1065 .senders
1066 .send_to_screen(ScreenInstruction::TerminalResize(min_size))
1067 .unwrap();
1068 }
1069 session_data
1070 .write()
1071 .unwrap()
1072 .as_ref()
1073 .unwrap()
1074 .senders
1075 .send_to_screen(ScreenInstruction::RemoveClient(client_id))
1076 .unwrap();
1077 session_data
1078 .write()
1079 .unwrap()
1080 .as_ref()
1081 .unwrap()
1082 .senders
1083 .send_to_plugin(PluginInstruction::RemoveClient(client_id))
1084 .unwrap();
1085 }
1086 },
1087 ServerInstruction::Render(serialized_output) => {
1088 let client_ids = session_state.read().unwrap().client_ids();
1089 if let Some(output) = &serialized_output {
1092 for (client_id, client_render_instruction) in output.iter() {
1093 send_to_client!(
1097 *client_id,
1098 os_input,
1099 ServerToClientMsg::Render(client_render_instruction.clone()),
1100 session_state
1101 );
1102 }
1103 } else {
1104 for client_id in client_ids {
1105 let _ = os_input
1106 .send_to_client(client_id, ServerToClientMsg::Exit(ExitReason::Normal));
1107 remove_client!(client_id, os_input, session_state);
1108 }
1109 break;
1110 }
1111 },
1112 ServerInstruction::Error(backtrace) => {
1113 let client_ids = session_state.read().unwrap().client_ids();
1114 for client_id in client_ids {
1115 let _ = os_input.send_to_client(
1116 client_id,
1117 ServerToClientMsg::Exit(ExitReason::Error(backtrace.clone())),
1118 );
1119 remove_client!(client_id, os_input, session_state);
1120 }
1121 break;
1122 },
1123 ServerInstruction::ConnStatus(client_id) => {
1124 let _ = os_input.send_to_client(client_id, ServerToClientMsg::Connected);
1125 remove_client!(client_id, os_input, session_state);
1126 },
1127 ServerInstruction::Log(lines_to_log, client_id) => {
1128 send_to_client!(
1129 client_id,
1130 os_input,
1131 ServerToClientMsg::Log(lines_to_log),
1132 session_state
1133 );
1134 },
1135 ServerInstruction::LogError(lines_to_log, client_id) => {
1136 send_to_client!(
1137 client_id,
1138 os_input,
1139 ServerToClientMsg::LogError(lines_to_log),
1140 session_state
1141 );
1142 },
1143 ServerInstruction::SwitchSession(mut connect_to_session, client_id) => {
1144 let current_session_name = envs::get_session_name();
1145 if connect_to_session.name == current_session_name.ok() {
1146 log::error!("Cannot attach to same session");
1147 } else {
1148 let layout_dir = session_data
1149 .read()
1150 .unwrap()
1151 .as_ref()
1152 .unwrap()
1153 .session_configuration
1154 .get_client_configuration(&client_id)
1155 .options
1156 .layout_dir
1157 .or_else(|| default_layout_dir());
1158 if let Some(layout_dir) = layout_dir {
1159 connect_to_session.apply_layout_dir(&layout_dir);
1160 }
1161 if let Some(min_size) = session_state.read().unwrap().min_client_terminal_size()
1162 {
1163 session_data
1164 .write()
1165 .unwrap()
1166 .as_ref()
1167 .unwrap()
1168 .senders
1169 .send_to_screen(ScreenInstruction::TerminalResize(min_size))
1170 .unwrap();
1171 }
1172 session_data
1173 .write()
1174 .unwrap()
1175 .as_ref()
1176 .unwrap()
1177 .senders
1178 .send_to_screen(ScreenInstruction::RemoveClient(client_id))
1179 .unwrap();
1180 session_data
1181 .write()
1182 .unwrap()
1183 .as_ref()
1184 .unwrap()
1185 .senders
1186 .send_to_plugin(PluginInstruction::RemoveClient(client_id))
1187 .unwrap();
1188 send_to_client!(
1189 client_id,
1190 os_input,
1191 ServerToClientMsg::SwitchSession(connect_to_session),
1192 session_state
1193 );
1194 remove_client!(client_id, os_input, session_state);
1195 }
1196 },
1197 ServerInstruction::AssociatePipeWithClient { pipe_id, client_id } => {
1198 session_state
1199 .write()
1200 .unwrap()
1201 .associate_pipe_with_client(pipe_id, client_id);
1202 },
1203 ServerInstruction::ChangeMode(client_id, input_mode) => {
1204 session_data
1205 .write()
1206 .unwrap()
1207 .as_mut()
1208 .unwrap()
1209 .current_input_modes
1210 .insert(client_id, input_mode);
1211 },
1212 ServerInstruction::ChangeModeForAllClients(input_mode) => {
1213 session_data
1214 .write()
1215 .unwrap()
1216 .as_mut()
1217 .unwrap()
1218 .change_mode_for_all_clients(input_mode);
1219 },
1220 ServerInstruction::Reconfigure {
1221 client_id,
1222 config,
1223 write_config_to_disk,
1224 } => {
1225 let (new_config, runtime_config_changed) = session_data
1226 .write()
1227 .unwrap()
1228 .as_mut()
1229 .unwrap()
1230 .session_configuration
1231 .reconfigure_runtime_config(&client_id, config);
1232
1233 if let Some(new_config) = new_config {
1234 if write_config_to_disk {
1235 let clear_defaults = true;
1236 send_to_client!(
1237 client_id,
1238 os_input,
1239 ServerToClientMsg::WriteConfigToDisk {
1240 config: new_config.to_string(clear_defaults)
1241 },
1242 session_state
1243 );
1244 }
1245
1246 if runtime_config_changed {
1247 let config_was_written_to_disk = false;
1248 session_data
1249 .write()
1250 .unwrap()
1251 .as_mut()
1252 .unwrap()
1253 .propagate_configuration_changes(
1254 vec![(client_id, new_config)],
1255 config_was_written_to_disk,
1256 );
1257 }
1258 }
1259 },
1260 ServerInstruction::ConfigWrittenToDisk(client_id, new_config) => {
1261 let changes = session_data
1262 .write()
1263 .unwrap()
1264 .as_mut()
1265 .unwrap()
1266 .session_configuration
1267 .new_saved_config(client_id, new_config);
1268 let config_was_written_to_disk = true;
1269 session_data
1270 .write()
1271 .unwrap()
1272 .as_mut()
1273 .unwrap()
1274 .propagate_configuration_changes(changes, config_was_written_to_disk);
1275 },
1276 ServerInstruction::FailedToWriteConfigToDisk(_client_id, file_path) => {
1277 session_data
1278 .write()
1279 .unwrap()
1280 .as_ref()
1281 .unwrap()
1282 .senders
1283 .send_to_plugin(PluginInstruction::FailedToWriteConfigToDisk { file_path })
1284 .unwrap();
1285 },
1286 ServerInstruction::RebindKeys {
1287 client_id,
1288 keys_to_rebind,
1289 keys_to_unbind,
1290 write_config_to_disk,
1291 } => {
1292 let (new_config, runtime_config_changed) = session_data
1293 .write()
1294 .unwrap()
1295 .as_mut()
1296 .unwrap()
1297 .session_configuration
1298 .rebind_keys(&client_id, keys_to_rebind, keys_to_unbind);
1299 if let Some(new_config) = new_config {
1300 if write_config_to_disk {
1301 let clear_defaults = true;
1302 send_to_client!(
1303 client_id,
1304 os_input,
1305 ServerToClientMsg::WriteConfigToDisk {
1306 config: new_config.to_string(clear_defaults)
1307 },
1308 session_state
1309 );
1310 }
1311
1312 if runtime_config_changed {
1313 let config_was_written_to_disk = false;
1314 session_data
1315 .write()
1316 .unwrap()
1317 .as_mut()
1318 .unwrap()
1319 .propagate_configuration_changes(
1320 vec![(client_id, new_config)],
1321 config_was_written_to_disk,
1322 );
1323 }
1324 }
1325 },
1326 ServerInstruction::StartWebServer(client_id) => {
1327 if cfg!(feature = "web_server_capability") {
1328 send_to_client!(
1329 client_id,
1330 os_input,
1331 ServerToClientMsg::StartWebServer,
1332 session_state
1333 );
1334 } else {
1335 log::error!("Cannot start web server: this instance of Zellij was compiled without web_server_capability");
1337 }
1338 },
1339 ServerInstruction::ShareCurrentSession(_client_id) => {
1340 if cfg!(feature = "web_server_capability") {
1341 let successfully_changed = session_data
1342 .write()
1343 .ok()
1344 .and_then(|mut s| s.as_mut().map(|s| s.web_sharing.set_sharing()))
1345 .unwrap_or(false);
1346 if successfully_changed {
1347 session_data
1348 .write()
1349 .unwrap()
1350 .as_ref()
1351 .unwrap()
1352 .senders
1353 .send_to_screen(ScreenInstruction::SessionSharingStatusChange(true))
1354 .unwrap();
1355 }
1356 } else {
1357 log::error!("Cannot share session: this instance of Zellij was compiled without web_server_capability");
1358 }
1359 },
1360 ServerInstruction::StopSharingCurrentSession(_client_id) => {
1361 if cfg!(feature = "web_server_capability") {
1362 let successfully_changed = session_data
1363 .write()
1364 .ok()
1365 .and_then(|mut s| s.as_mut().map(|s| s.web_sharing.set_not_sharing()))
1366 .unwrap_or(false);
1367 if successfully_changed {
1368 let web_client_ids: Vec<ClientId> = session_state
1370 .read()
1371 .unwrap()
1372 .web_client_ids()
1373 .iter()
1374 .copied()
1375 .collect();
1376 for client_id in web_client_ids {
1377 let _ = os_input.send_to_client(
1378 client_id,
1379 ServerToClientMsg::Exit(ExitReason::WebClientsForbidden),
1380 );
1381 remove_client!(client_id, os_input, session_state);
1382 }
1383
1384 session_data
1385 .write()
1386 .unwrap()
1387 .as_ref()
1388 .unwrap()
1389 .senders
1390 .send_to_screen(ScreenInstruction::SessionSharingStatusChange(false))
1391 .unwrap();
1392 }
1393 } else {
1394 log::error!("Cannot start web server: this instance of Zellij was compiled without web_server_capability");
1396 }
1397 },
1398 ServerInstruction::WebServerStarted(base_url) => {
1399 session_data
1400 .write()
1401 .unwrap()
1402 .as_ref()
1403 .unwrap()
1404 .senders
1405 .send_to_plugin(PluginInstruction::WebServerStarted(base_url))
1406 .unwrap();
1407 },
1408 ServerInstruction::FailedToStartWebServer(error) => {
1409 session_data
1410 .write()
1411 .unwrap()
1412 .as_ref()
1413 .unwrap()
1414 .senders
1415 .send_to_plugin(PluginInstruction::FailedToStartWebServer(error))
1416 .unwrap();
1417 },
1418 }
1419 }
1420
1421 *session_data.write().unwrap() = None;
1423
1424 drop(std::fs::remove_file(&socket_path));
1425}
1426
1427pub struct SessionOptions {
1428 pub opts: Box<CliArgs>,
1429 pub config_options: Box<Options>,
1430 pub layout: Box<Layout>,
1431}
1432
1433fn init_session(
1434 os_input: Box<dyn ServerOsApi>,
1435 to_server: SenderWithContext<ServerInstruction>,
1436 client_attributes: ClientAttributes,
1437 options: SessionOptions,
1438 mut config: Config,
1439 plugin_aliases: Box<PluginAliases>,
1440 client_id: ClientId,
1441) -> SessionMetaData {
1442 let SessionOptions {
1443 opts,
1444 config_options,
1445 layout,
1446 } = options;
1447 config.options = config.options.merge(*config_options.clone());
1448
1449 let _ = SCROLL_BUFFER_SIZE.set(
1450 config_options
1451 .scroll_buffer_size
1452 .unwrap_or(DEFAULT_SCROLL_BUFFER_SIZE),
1453 );
1454
1455 let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channels::unbounded();
1456 let to_screen = SenderWithContext::new(to_screen);
1457
1458 let (to_screen_bounded, bounded_screen_receiver): ChannelWithContext<ScreenInstruction> =
1459 channels::bounded(50);
1460 let to_screen_bounded = SenderWithContext::new(to_screen_bounded);
1461
1462 let (to_plugin, plugin_receiver): ChannelWithContext<PluginInstruction> = channels::unbounded();
1463 let to_plugin = SenderWithContext::new(to_plugin);
1464 let (to_pty, pty_receiver): ChannelWithContext<PtyInstruction> = channels::unbounded();
1465 let to_pty = SenderWithContext::new(to_pty);
1466
1467 let (to_pty_writer, pty_writer_receiver): ChannelWithContext<PtyWriteInstruction> =
1468 channels::unbounded();
1469 let to_pty_writer = SenderWithContext::new(to_pty_writer);
1470
1471 let (to_background_jobs, background_jobs_receiver): ChannelWithContext<BackgroundJob> =
1472 channels::unbounded();
1473 let to_background_jobs = SenderWithContext::new(to_background_jobs);
1474
1475 let data_dir = opts.data_dir.unwrap_or_else(get_default_data_dir);
1477
1478 let capabilities = PluginCapabilities {
1479 arrow_fonts: config_options.simplified_ui.unwrap_or_default(),
1480 };
1481
1482 let serialization_interval = config_options.serialization_interval;
1483 let disable_session_metadata = config_options.disable_session_metadata.unwrap_or(false);
1484 let web_server_ip = config_options
1485 .web_server_ip
1486 .unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
1487 let web_server_port = config_options.web_server_port.unwrap_or_else(|| 8082);
1488 let has_certificate =
1489 config_options.web_server_cert.is_some() && config_options.web_server_key.is_some();
1490 let enforce_https_for_localhost = config_options.enforce_https_for_localhost.unwrap_or(false);
1491
1492 let default_shell = config_options.default_shell.clone().map(|command| {
1493 TerminalAction::RunCommand(RunCommand {
1494 command,
1495 use_terminal_title: true,
1496 ..Default::default()
1497 })
1498 });
1499 let path_to_default_shell = config_options
1500 .default_shell
1501 .clone()
1502 .unwrap_or_else(|| get_default_shell());
1503
1504 let default_mode = config_options.default_mode.unwrap_or_default();
1505 let default_keybinds = config.keybinds.clone();
1506
1507 let pty_thread = thread::Builder::new()
1508 .name("pty".to_string())
1509 .spawn({
1510 let layout = layout.clone();
1511 let pty = Pty::new(
1512 Bus::new(
1513 vec![pty_receiver],
1514 Some(&to_screen_bounded),
1515 None,
1516 Some(&to_plugin),
1517 Some(&to_server),
1518 Some(&to_pty_writer),
1519 Some(&to_background_jobs),
1520 Some(os_input.clone()),
1521 ),
1522 opts.debug,
1523 config_options.scrollback_editor.clone(),
1524 config_options.post_command_discovery_hook.clone(),
1525 );
1526
1527 move || pty_thread_main(pty, layout.clone()).fatal()
1528 })
1529 .unwrap();
1530
1531 let screen_thread = thread::Builder::new()
1532 .name("screen".to_string())
1533 .spawn({
1534 let screen_bus = Bus::new(
1535 vec![screen_receiver, bounded_screen_receiver],
1536 Some(&to_screen), Some(&to_pty),
1539 Some(&to_plugin),
1540 Some(&to_server),
1541 Some(&to_pty_writer),
1542 Some(&to_background_jobs),
1543 Some(os_input.clone()),
1544 );
1545 let max_panes = opts.max_panes;
1546
1547 let client_attributes_clone = client_attributes.clone();
1548 let debug = opts.debug;
1549 let layout = layout.clone();
1550 let config = config.clone();
1551 move || {
1552 screen_thread_main(
1553 screen_bus,
1554 max_panes,
1555 client_attributes_clone,
1556 config,
1557 debug,
1558 layout,
1559 )
1560 .fatal();
1561 }
1562 })
1563 .unwrap();
1564
1565 let zellij_cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
1566 let plugin_thread = thread::Builder::new()
1567 .name("wasm".to_string())
1568 .spawn({
1569 let plugin_bus = Bus::new(
1570 vec![plugin_receiver],
1571 Some(&to_screen_bounded),
1572 Some(&to_pty),
1573 Some(&to_plugin),
1574 Some(&to_server),
1575 Some(&to_pty_writer),
1576 Some(&to_background_jobs),
1577 None,
1578 );
1579 let engine = get_engine();
1580
1581 let layout = layout.clone();
1582 let client_attributes = client_attributes.clone();
1583 let default_shell = default_shell.clone();
1584 let capabilities = capabilities.clone();
1585 let layout_dir = config_options.layout_dir.clone();
1586 let background_plugins = config.background_plugins.clone();
1587 move || {
1588 plugin_thread_main(
1589 plugin_bus,
1590 engine,
1591 data_dir,
1592 layout,
1593 layout_dir,
1594 path_to_default_shell,
1595 zellij_cwd,
1596 capabilities,
1597 client_attributes,
1598 default_shell,
1599 plugin_aliases,
1600 default_mode,
1601 default_keybinds,
1602 background_plugins,
1603 client_id,
1604 )
1605 .fatal()
1606 }
1607 })
1608 .unwrap();
1609
1610 let pty_writer_thread = thread::Builder::new()
1611 .name("pty_writer".to_string())
1612 .spawn({
1613 let pty_writer_bus = Bus::new(
1614 vec![pty_writer_receiver],
1615 Some(&to_screen),
1616 Some(&to_pty),
1617 Some(&to_plugin),
1618 Some(&to_server),
1619 None,
1620 Some(&to_background_jobs),
1621 Some(os_input.clone()),
1622 );
1623 || pty_writer_main(pty_writer_bus).fatal()
1624 })
1625 .unwrap();
1626
1627 let background_jobs_thread = thread::Builder::new()
1628 .name("background_jobs".to_string())
1629 .spawn({
1630 let background_jobs_bus = Bus::new(
1631 vec![background_jobs_receiver],
1632 Some(&to_screen),
1633 Some(&to_pty),
1634 Some(&to_plugin),
1635 Some(&to_server),
1636 Some(&to_pty_writer),
1637 None,
1638 Some(os_input.clone()),
1639 );
1640 let web_server_base_url = web_server_base_url(
1641 web_server_ip,
1642 web_server_port,
1643 has_certificate,
1644 enforce_https_for_localhost,
1645 );
1646 move || {
1647 background_jobs_main(
1648 background_jobs_bus,
1649 serialization_interval,
1650 disable_session_metadata,
1651 web_server_base_url,
1652 )
1653 .fatal()
1654 }
1655 })
1656 .unwrap();
1657
1658 SessionMetaData {
1659 senders: ThreadSenders {
1660 to_screen: Some(to_screen),
1661 to_pty: Some(to_pty),
1662 to_plugin: Some(to_plugin),
1663 to_pty_writer: Some(to_pty_writer),
1664 to_background_jobs: Some(to_background_jobs),
1665 to_server: Some(to_server),
1666 should_silently_fail: false,
1667 },
1668 capabilities,
1669 default_shell,
1670 client_attributes,
1671 layout,
1672 session_configuration: Default::default(),
1673 current_input_modes: HashMap::new(),
1674 screen_thread: Some(screen_thread),
1675 pty_thread: Some(pty_thread),
1676 plugin_thread: Some(plugin_thread),
1677 pty_writer_thread: Some(pty_writer_thread),
1678 background_jobs_thread: Some(background_jobs_thread),
1679 #[cfg(feature = "web_server_capability")]
1680 web_sharing: config.options.web_sharing.unwrap_or(WebSharing::Off),
1681 #[cfg(not(feature = "web_server_capability"))]
1682 web_sharing: WebSharing::Disabled,
1683 }
1684}
1685
1686fn setup_wizard_floating_pane() -> FloatingPaneLayout {
1687 let mut setup_wizard_pane = FloatingPaneLayout::new();
1688 let configuration = BTreeMap::from_iter([("is_setup_wizard".to_owned(), "true".to_owned())]);
1689 setup_wizard_pane.run = Some(Run::Plugin(RunPluginOrAlias::Alias(PluginAlias::new(
1690 "configuration",
1691 &Some(configuration),
1692 None,
1693 ))));
1694 setup_wizard_pane
1695}
1696
1697fn about_floating_pane() -> FloatingPaneLayout {
1698 let mut about_pane = FloatingPaneLayout::new();
1699 let configuration = BTreeMap::from_iter([("is_release_notes".to_owned(), "true".to_owned())]);
1700 about_pane.run = Some(Run::Plugin(RunPluginOrAlias::Alias(PluginAlias::new(
1701 "about",
1702 &Some(configuration),
1703 None,
1704 ))));
1705 about_pane
1706}
1707
1708fn tip_floating_pane() -> FloatingPaneLayout {
1709 let mut about_pane = FloatingPaneLayout::new();
1710 let configuration = BTreeMap::from_iter([("is_startup_tip".to_owned(), "true".to_owned())]);
1711 about_pane.run = Some(Run::Plugin(RunPluginOrAlias::Alias(PluginAlias::new(
1712 "about",
1713 &Some(configuration),
1714 None,
1715 ))));
1716 about_pane
1717}
1718
1719fn should_show_release_notes(
1720 should_show_release_notes_config: Option<bool>,
1721 layout_is_welcome_screen: bool,
1722) -> bool {
1723 if layout_is_welcome_screen {
1724 return false;
1725 }
1726 if let Some(should_show_release_notes_config) = should_show_release_notes_config {
1727 if !should_show_release_notes_config {
1728 return false;
1732 }
1733 }
1734 if ZELLIJ_SEEN_RELEASE_NOTES_CACHE_FILE.exists() {
1735 return false;
1736 } else {
1737 if let Err(e) = std::fs::write(&*ZELLIJ_SEEN_RELEASE_NOTES_CACHE_FILE, &[]) {
1738 log::error!(
1739 "Failed to write seen release notes indication to disk: {}",
1740 e
1741 );
1742 return false;
1743 }
1744 return true;
1745 }
1746}
1747
1748fn should_show_startup_tip(
1749 should_show_startup_tip_config: Option<bool>,
1750 layout_is_welcome_screen: bool,
1751) -> bool {
1752 if layout_is_welcome_screen {
1753 false
1754 } else {
1755 should_show_startup_tip_config.unwrap_or(true)
1756 }
1757}
1758
1759#[cfg(not(feature = "singlepass"))]
1760fn get_engine() -> Engine {
1761 log::info!("Compiling plugins using Cranelift");
1762 Engine::new(WasmtimeConfig::new().strategy(Strategy::Cranelift)).unwrap()
1763}
1764
1765#[cfg(feature = "singlepass")]
1766fn get_engine() -> Engine {
1767 log::info!("Compiling plugins using Singlepass");
1768 Engine::new(WasmtimeConfig::new().strategy(Strategy::Winch)).unwrap()
1769}