zellij_server/
lib.rs

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/// Instructions related to server-side application
71#[derive(Debug, Clone)]
72pub enum ServerInstruction {
73    NewClient(
74        ClientAttributes,
75        Box<CliArgs>,
76        Box<Config>,  // represents the saved config
77        Box<Options>, // represents the runtime configuration options
78        Box<Layout>,
79        Box<PluginAliases>,
80        bool, // should launch setup wizard
81        bool, // is_web_client
82        bool, // layout_is_welcome_screen
83        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,              // represents the saved config
95        Options,             // represents the runtime configuration options
96        Option<usize>,       // tab position to focus
97        Option<(u32, bool)>, // (pane_id, is_plugin) => pane_id to focus
98        bool,                // is_web_client
99        ClientId,
100    ),
101    ConnStatus(ClientId),
102    Log(Vec<String>, ClientId),
103    LogError(Vec<String>, ClientId),
104    SwitchSession(ConnectToSession, ClientId),
105    UnblockCliPipeInput(String),   // String -> Pipe name
106    CliPipeOutput(String, String), // String -> Pipe name, String -> Output
107    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>), // Pathbuf - file we failed to write
121    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), // String -> base_url
132    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>, // if present, overrides the saved_config
192    saved_config: HashMap<ClientId, Config>,   // the config as it is on disk (not guaranteed),
193                                               // when changed, this resets the runtime config to
194                                               // be identical to it and override any previous
195                                               // changes
196}
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        // bool is whether the config changed
249        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, // this is a special attribute explicitly set on session
329    // initialization because we don't want it to be overridden by
330    // configuration changes, the only way it can be overwritten is by
331    // explicit plugin action
332    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        // (keybinds, current_input_mode,
345        // default_input_mode)
346        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            // Try to recover the message
462            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            // Log it so it isn't lost
474            Err::<(), _>(e).context(context).non_fatal();
475            // failed to send to client, remove it
476            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)>>, // bool -> is_web_client
484    pipes: HashMap<String, ClientId>,                 // String => pipe_id
485}
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        // None if there are no client sizes
526        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    // preserve the current umask: read current value by setting to another mode, and then restoring it
584    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                // set the sticky bit to avoid the socket file being potentially cleaned up
622                // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html states that for XDG_RUNTIME_DIR:
623                // "To ensure that your files are not removed, they should have their access time timestamp modified at least once every 6 hours of monotonic time or the 'sticky' bit should be set on the file. "
624                // It is not guaranteed that all platforms allow setting the sticky bit on sockets!
625                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                // TODO: rename to FirstClientConnected?
664                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                        // we only do this here (and only once) because otherwise it will be
764                        // intrusive
765                        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                        // send to all clients, this pipe might not have been associated yet
900                        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                        // send to all clients, this pipe might not have been associated yet
925                        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                    // these are just the pipes
977                    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 `Some(_)`- unwrap it and forward it to the clients to render.
1090                // If `None`- Send an exit instruction. This is the case when a user closes the last Tab/Pane.
1091                if let Some(output) = &serialized_output {
1092                    for (client_id, client_render_instruction) in output.iter() {
1093                        // TODO: When a client is too slow or unresponsive, the channel fills up
1094                        // and this call will disconnect the client in turn. Should this be
1095                        // changed?
1096                        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                    // TODO: test this
1336                    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                        // disconnect existing web clients
1369                        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                    // TODO: test this
1395                    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    // Drop cached session data before exit.
1422    *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    // Determine and initialize the data directory
1476    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), // there are certain occasions (eg. caching) where the screen
1537                // needs to send messages to itself
1538                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            // if we were explicitly told not to show release notes, we don't show them,
1729            // otherwise we make sure we only show them if they were not seen AND we know
1730            // we are able to write to the cache
1731            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}