1pub mod os_input_output;
2
3pub mod cli_client;
4mod command_is_executing;
5mod input_handler;
6mod keyboard_parser;
7pub mod old_config_converter;
8mod stdin_ansi_parser;
9mod stdin_handler;
10#[cfg(feature = "web_server_capability")]
11pub mod web_client;
12
13use log::info;
14use std::env::current_exe;
15use std::io::{self, Write};
16use std::net::{IpAddr, Ipv4Addr};
17use std::path::Path;
18use std::process::Command;
19use std::sync::{Arc, Mutex};
20use std::thread;
21use zellij_utils::errors::FatalError;
22use zellij_utils::shared::web_server_base_url;
23
24use crate::stdin_ansi_parser::{AnsiStdinInstruction, StdinAnsiParser, SyncOutput};
25use crate::{
26 command_is_executing::CommandIsExecuting, input_handler::input_loop,
27 os_input_output::ClientOsApi, stdin_handler::stdin_loop,
28};
29use termwiz::input::InputEvent;
30use zellij_utils::{
31 channels::{self, ChannelWithContext, SenderWithContext},
32 consts::{set_permissions, ZELLIJ_SOCK_DIR},
33 data::{ClientId, ConnectToSession, KeyWithModifier, Style},
34 envs,
35 errors::{ClientContext, ContextType, ErrorInstruction},
36 input::{
37 config::{watch_config_file_changes, Config},
38 options::Options,
39 },
40 ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
41 pane_size::Size,
42};
43use zellij_utils::{cli::CliArgs, input::layout::Layout};
44
45#[derive(Debug, Clone)]
47pub(crate) enum ClientInstruction {
48 Error(String),
49 Render(String),
50 UnblockInputThread,
51 Exit(ExitReason),
52 Connected,
53 StartedParsingStdinQuery,
54 DoneParsingStdinQuery,
55 Log(Vec<String>),
56 LogError(Vec<String>),
57 SwitchSession(ConnectToSession),
58 SetSynchronizedOutput(Option<SyncOutput>),
59 UnblockCliPipeInput(()), CliPipeOutput((), ()), QueryTerminalSize,
62 WriteConfigToDisk {
63 config: String,
64 },
65 StartWebServer,
66 #[allow(dead_code)] RenamedSession(String), }
69
70impl From<ServerToClientMsg> for ClientInstruction {
71 fn from(instruction: ServerToClientMsg) -> Self {
72 match instruction {
73 ServerToClientMsg::Exit(e) => ClientInstruction::Exit(e),
74 ServerToClientMsg::Render(buffer) => ClientInstruction::Render(buffer),
75 ServerToClientMsg::UnblockInputThread => ClientInstruction::UnblockInputThread,
76 ServerToClientMsg::Connected => ClientInstruction::Connected,
77 ServerToClientMsg::Log(log_lines) => ClientInstruction::Log(log_lines),
78 ServerToClientMsg::LogError(log_lines) => ClientInstruction::LogError(log_lines),
79 ServerToClientMsg::SwitchSession(connect_to_session) => {
80 ClientInstruction::SwitchSession(connect_to_session)
81 },
82 ServerToClientMsg::UnblockCliPipeInput(_pipe_name) => {
83 ClientInstruction::UnblockCliPipeInput(())
84 },
85 ServerToClientMsg::CliPipeOutput(_pipe_name, _output) => {
86 ClientInstruction::CliPipeOutput((), ())
87 },
88 ServerToClientMsg::QueryTerminalSize => ClientInstruction::QueryTerminalSize,
89 ServerToClientMsg::WriteConfigToDisk { config } => {
90 ClientInstruction::WriteConfigToDisk { config }
91 },
92 ServerToClientMsg::StartWebServer => ClientInstruction::StartWebServer,
93 ServerToClientMsg::RenamedSession(name) => ClientInstruction::RenamedSession(name),
94 }
95 }
96}
97
98impl From<&ClientInstruction> for ClientContext {
99 fn from(client_instruction: &ClientInstruction) -> Self {
100 match *client_instruction {
101 ClientInstruction::Exit(_) => ClientContext::Exit,
102 ClientInstruction::Error(_) => ClientContext::Error,
103 ClientInstruction::Render(_) => ClientContext::Render,
104 ClientInstruction::UnblockInputThread => ClientContext::UnblockInputThread,
105 ClientInstruction::Connected => ClientContext::Connected,
106 ClientInstruction::Log(_) => ClientContext::Log,
107 ClientInstruction::LogError(_) => ClientContext::LogError,
108 ClientInstruction::StartedParsingStdinQuery => ClientContext::StartedParsingStdinQuery,
109 ClientInstruction::DoneParsingStdinQuery => ClientContext::DoneParsingStdinQuery,
110 ClientInstruction::SwitchSession(..) => ClientContext::SwitchSession,
111 ClientInstruction::SetSynchronizedOutput(..) => ClientContext::SetSynchronisedOutput,
112 ClientInstruction::UnblockCliPipeInput(..) => ClientContext::UnblockCliPipeInput,
113 ClientInstruction::CliPipeOutput(..) => ClientContext::CliPipeOutput,
114 ClientInstruction::QueryTerminalSize => ClientContext::QueryTerminalSize,
115 ClientInstruction::WriteConfigToDisk { .. } => ClientContext::WriteConfigToDisk,
116 ClientInstruction::StartWebServer => ClientContext::StartWebServer,
117 ClientInstruction::RenamedSession(..) => ClientContext::RenamedSession,
118 }
119 }
120}
121
122impl ErrorInstruction for ClientInstruction {
123 fn error(err: String) -> Self {
124 ClientInstruction::Error(err)
125 }
126}
127
128#[cfg(feature = "web_server_capability")]
129fn spawn_web_server(opts: &CliArgs) -> Result<String, String> {
130 let mut cmd = Command::new(current_exe().map_err(|e| e.to_string())?);
131 if let Some(config_file_path) = Config::config_file_path(opts) {
132 let config_file_path_exists = Path::new(&config_file_path).exists();
133 if !config_file_path_exists {
134 return Err(format!(
135 "Config file: {} does not exist",
136 config_file_path.display()
137 ));
138 }
139 cmd.arg("--config");
142 cmd.arg(format!("{}", config_file_path.display()));
143 }
144 cmd.arg("web");
145 cmd.arg("-d");
146 let output = cmd.output();
147 match output {
148 Ok(output) => {
149 if output.status.success() {
150 Ok(String::from_utf8_lossy(&output.stdout).to_string())
151 } else {
152 Err(String::from_utf8_lossy(&output.stderr).to_string())
153 }
154 },
155 Err(e) => Err(e.to_string()),
156 }
157}
158
159#[cfg(not(feature = "web_server_capability"))]
160fn spawn_web_server(_opts: &CliArgs) -> Result<String, String> {
161 log::error!(
162 "This version of Zellij was compiled without web server support, cannot run web server!"
163 );
164 Ok("".to_owned())
165}
166
167pub fn spawn_server(socket_path: &Path, debug: bool) -> io::Result<()> {
168 let mut cmd = Command::new(current_exe()?);
169 cmd.arg("--server");
170 cmd.arg(socket_path);
171 if debug {
172 cmd.arg("--debug");
173 }
174 let status = cmd.status()?;
175
176 if status.success() {
177 Ok(())
178 } else {
179 let msg = "Process returned non-zero exit code";
180 let err_msg = match status.code() {
181 Some(c) => format!("{}: {}", msg, c),
182 None => msg.to_string(),
183 };
184 Err(io::Error::new(io::ErrorKind::Other, err_msg))
185 }
186}
187
188#[derive(Debug, Clone)]
189pub enum ClientInfo {
190 Attach(String, Options),
191 New(String),
192 Resurrect(String, Layout),
193}
194
195impl ClientInfo {
196 pub fn get_session_name(&self) -> &str {
197 match self {
198 Self::Attach(ref name, _) => name,
199 Self::New(ref name) => name,
200 Self::Resurrect(ref name, _) => name,
201 }
202 }
203}
204
205#[derive(Debug, Clone)]
206pub(crate) enum InputInstruction {
207 KeyEvent(InputEvent, Vec<u8>),
208 KeyWithModifierEvent(KeyWithModifier, Vec<u8>),
209 AnsiStdinInstructions(Vec<AnsiStdinInstruction>),
210 StartedParsing,
211 DoneParsing,
212 Exit,
213}
214
215pub fn start_client(
216 mut os_input: Box<dyn ClientOsApi>,
217 opts: CliArgs,
218 config: Config, config_options: Options, info: ClientInfo,
221 layout: Option<Layout>,
222 tab_position_to_focus: Option<usize>,
223 pane_id_to_focus: Option<(u32, bool)>, is_a_reconnect: bool,
225 start_detached_and_exit: bool,
226 layout_is_welcome_screen: bool,
227) -> Option<ConnectToSession> {
228 if start_detached_and_exit {
229 start_server_detached(os_input, opts, config, config_options, info, layout);
230 return None;
231 }
232 info!("Starting Zellij client!");
233
234 let explicitly_disable_kitty_keyboard_protocol = config_options
235 .support_kitty_keyboard_protocol
236 .map(|e| !e)
237 .unwrap_or(false);
238 let should_start_web_server = config_options.web_server.map(|w| w).unwrap_or(false);
239 let mut reconnect_to_session = None;
240 let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l";
241 let take_snapshot = "\u{1b}[?1049h";
242 let bracketed_paste = "\u{1b}[?2004h";
243 let enter_kitty_keyboard_mode = "\u{1b}[>1u";
244 os_input.unset_raw_mode(0).unwrap();
245
246 if !is_a_reconnect {
247 let _ = os_input
251 .get_stdout_writer()
252 .write(take_snapshot.as_bytes())
253 .unwrap();
254 let _ = os_input
255 .get_stdout_writer()
256 .write(clear_client_terminal_attributes.as_bytes())
257 .unwrap();
258 if !explicitly_disable_kitty_keyboard_protocol {
259 let _ = os_input
260 .get_stdout_writer()
261 .write(enter_kitty_keyboard_mode.as_bytes())
262 .unwrap();
263 }
264 }
265 envs::set_zellij("0".to_string());
266 config.env.set_vars();
267
268 let palette = config
269 .theme_config(config_options.theme.as_ref())
270 .unwrap_or_else(|| os_input.load_palette().into());
271
272 let full_screen_ws = os_input.get_terminal_size_using_fd(0);
273 let client_attributes = ClientAttributes {
274 size: full_screen_ws,
275 style: Style {
276 colors: palette,
277 rounded_corners: config.ui.pane_frames.rounded_corners,
278 hide_session_name: config.ui.pane_frames.hide_session_name,
279 },
280 };
281 let web_server_ip = config_options
282 .web_server_ip
283 .unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
284 let web_server_port = config_options.web_server_port.unwrap_or_else(|| 8082);
285 let has_certificate =
286 config_options.web_server_cert.is_some() && config_options.web_server_key.is_some();
287 let enforce_https_for_localhost = config_options.enforce_https_for_localhost.unwrap_or(false);
288
289 let create_ipc_pipe = || -> std::path::PathBuf {
290 let mut sock_dir = ZELLIJ_SOCK_DIR.clone();
291 std::fs::create_dir_all(&sock_dir).unwrap();
292 set_permissions(&sock_dir, 0o700).unwrap();
293 sock_dir.push(envs::get_session_name().unwrap());
294 sock_dir
295 };
296
297 let (first_msg, ipc_pipe) = match info {
298 ClientInfo::Attach(name, config_options) => {
299 envs::set_session_name(name.clone());
300 os_input.update_session_name(name);
301 let ipc_pipe = create_ipc_pipe();
302 let is_web_client = false;
303
304 (
305 ClientToServerMsg::AttachClient(
306 client_attributes,
307 config.clone(),
308 config_options.clone(),
309 tab_position_to_focus,
310 pane_id_to_focus,
311 is_web_client,
312 ),
313 ipc_pipe,
314 )
315 },
316 ClientInfo::New(name) | ClientInfo::Resurrect(name, _) => {
317 envs::set_session_name(name.clone());
318 os_input.update_session_name(name);
319 let ipc_pipe = create_ipc_pipe();
320
321 spawn_server(&*ipc_pipe, opts.debug).unwrap();
322 if should_start_web_server {
323 if let Err(e) = spawn_web_server(&opts) {
324 log::error!("Failed to start web server: {}", e);
325 }
326 }
327
328 let successfully_written_config =
329 Config::write_config_to_disk_if_it_does_not_exist(config.to_string(true), &opts);
330 let should_launch_setup_wizard = successfully_written_config;
337 let is_web_client = false;
338
339 (
340 ClientToServerMsg::NewClient(
341 client_attributes,
342 Box::new(opts.clone()),
343 Box::new(config.clone()),
344 Box::new(config_options.clone()),
345 Box::new(layout.unwrap()),
346 Box::new(config.plugins.clone()),
347 is_web_client,
348 should_launch_setup_wizard,
349 layout_is_welcome_screen,
350 ),
351 ipc_pipe,
352 )
353 },
354 };
355
356 os_input.connect_to_server(&*ipc_pipe);
357 os_input.send_to_server(first_msg);
358
359 let mut command_is_executing = CommandIsExecuting::new();
360
361 os_input.set_raw_mode(0);
362 let _ = os_input
363 .get_stdout_writer()
364 .write(bracketed_paste.as_bytes())
365 .unwrap();
366
367 let (send_client_instructions, receive_client_instructions): ChannelWithContext<
368 ClientInstruction,
369 > = channels::bounded(50);
370 let send_client_instructions = SenderWithContext::new(send_client_instructions);
371
372 let (send_input_instructions, receive_input_instructions): ChannelWithContext<
373 InputInstruction,
374 > = channels::bounded(50);
375 let send_input_instructions = SenderWithContext::new(send_input_instructions);
376
377 std::panic::set_hook({
378 use zellij_utils::errors::handle_panic;
379 let send_client_instructions = send_client_instructions.clone();
380 let os_input = os_input.clone();
381 Box::new(move |info| {
382 if let Ok(()) = os_input.unset_raw_mode(0) {
383 handle_panic(info, &send_client_instructions);
384 }
385 })
386 });
387
388 let on_force_close = config_options.on_force_close.unwrap_or_default();
389 let stdin_ansi_parser = Arc::new(Mutex::new(StdinAnsiParser::new()));
390
391 let _stdin_thread = thread::Builder::new()
392 .name("stdin_handler".to_string())
393 .spawn({
394 let os_input = os_input.clone();
395 let send_input_instructions = send_input_instructions.clone();
396 let stdin_ansi_parser = stdin_ansi_parser.clone();
397 move || {
398 stdin_loop(
399 os_input,
400 send_input_instructions,
401 stdin_ansi_parser,
402 explicitly_disable_kitty_keyboard_protocol,
403 )
404 }
405 });
406
407 let _input_thread = thread::Builder::new()
408 .name("input_handler".to_string())
409 .spawn({
410 let send_client_instructions = send_client_instructions.clone();
411 let command_is_executing = command_is_executing.clone();
412 let os_input = os_input.clone();
413 let default_mode = config_options.default_mode.unwrap_or_default();
414 move || {
415 input_loop(
416 os_input,
417 config,
418 config_options,
419 command_is_executing,
420 send_client_instructions,
421 default_mode,
422 receive_input_instructions,
423 )
424 }
425 });
426
427 let _signal_thread = thread::Builder::new()
428 .name("signal_listener".to_string())
429 .spawn({
430 let os_input = os_input.clone();
431 let opts = opts.clone();
432 move || {
433 report_changes_in_config_file(&opts, &os_input);
434 os_input.handle_signals(
435 Box::new({
436 let os_api = os_input.clone();
437 move || {
438 os_api.send_to_server(ClientToServerMsg::TerminalResize(
439 os_api.get_terminal_size_using_fd(0),
440 ));
441 }
442 }),
443 Box::new({
444 let os_api = os_input.clone();
445 move || {
446 os_api.send_to_server(ClientToServerMsg::Action(
447 on_force_close.into(),
448 None,
449 None,
450 ));
451 }
452 }),
453 );
454 }
455 })
456 .unwrap();
457
458 let router_thread = thread::Builder::new()
459 .name("router".to_string())
460 .spawn({
461 let os_input = os_input.clone();
462 let mut should_break = false;
463 move || loop {
464 match os_input.recv_from_server() {
465 Some((instruction, err_ctx)) => {
466 err_ctx.update_thread_ctx();
467 if let ServerToClientMsg::Exit(_) = instruction {
468 should_break = true;
469 }
470 send_client_instructions.send(instruction.into()).unwrap();
471 if should_break {
472 break;
473 }
474 },
475 None => {
476 send_client_instructions
477 .send(ClientInstruction::UnblockInputThread)
478 .unwrap();
479 log::error!("Received empty message from server");
480 send_client_instructions
481 .send(ClientInstruction::Error(
482 "Received empty message from server".to_string(),
483 ))
484 .unwrap();
485 break;
486 },
487 }
488 }
489 })
490 .unwrap();
491
492 let handle_error = |backtrace: String| {
493 os_input.unset_raw_mode(0).unwrap();
494 let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
495 let restore_snapshot = "\u{1b}[?1049l";
496 os_input.disable_mouse().non_fatal();
497 let error = format!(
498 "{}\n{}{}\n",
499 restore_snapshot, goto_start_of_last_line, backtrace
500 );
501 let _ = os_input
502 .get_stdout_writer()
503 .write(error.as_bytes())
504 .unwrap();
505 let _ = os_input.get_stdout_writer().flush().unwrap();
506 std::process::exit(1);
507 };
508
509 let mut exit_msg = String::new();
510 let mut loading = true;
511 let mut pending_instructions = vec![];
512 let mut synchronised_output = match os_input.env_variable("TERM").as_deref() {
513 Some("alacritty") => Some(SyncOutput::DCS),
514 _ => None,
515 };
516
517 let mut stdout = os_input.get_stdout_writer();
518 stdout
519 .write_all("\u{1b}[1m\u{1b}[HLoading Zellij\u{1b}[m\n\r".as_bytes())
520 .expect("cannot write to stdout");
521 stdout.flush().expect("could not flush");
522
523 loop {
524 let (client_instruction, mut err_ctx) = if !loading && !pending_instructions.is_empty() {
525 pending_instructions.remove(0)
528 } else {
529 receive_client_instructions
530 .recv()
531 .expect("failed to receive app instruction on channel")
532 };
533
534 if loading {
535 match client_instruction {
537 ClientInstruction::StartedParsingStdinQuery => {
538 stdout
539 .write_all("Querying terminal emulator for \u{1b}[32;1mdefault colors\u{1b}[m and \u{1b}[32;1mpixel/cell\u{1b}[m ratio...".as_bytes())
540 .expect("cannot write to stdout");
541 stdout.flush().expect("could not flush");
542 },
543 ClientInstruction::DoneParsingStdinQuery => {
544 stdout
545 .write_all("done".as_bytes())
546 .expect("cannot write to stdout");
547 stdout.flush().expect("could not flush");
548 loading = false;
549 },
550 instruction => {
551 pending_instructions.push((instruction, err_ctx));
552 },
553 }
554 continue;
555 }
556
557 err_ctx.add_call(ContextType::Client((&client_instruction).into()));
558
559 match client_instruction {
560 ClientInstruction::Exit(reason) => {
561 os_input.send_to_server(ClientToServerMsg::ClientExited);
562
563 if let ExitReason::Error(_) = reason {
564 handle_error(reason.to_string());
565 }
566 exit_msg = reason.to_string();
567 break;
568 },
569 ClientInstruction::Error(backtrace) => {
570 handle_error(backtrace);
571 },
572 ClientInstruction::Render(output) => {
573 let mut stdout = os_input.get_stdout_writer();
574 if let Some(sync) = synchronised_output {
575 stdout
576 .write_all(sync.start_seq())
577 .expect("cannot write to stdout");
578 }
579 stdout
580 .write_all(output.as_bytes())
581 .expect("cannot write to stdout");
582 if let Some(sync) = synchronised_output {
583 stdout
584 .write_all(sync.end_seq())
585 .expect("cannot write to stdout");
586 }
587 stdout.flush().expect("could not flush");
588 },
589 ClientInstruction::UnblockInputThread => {
590 command_is_executing.unblock_input_thread();
591 },
592 ClientInstruction::Log(lines_to_log) => {
593 for line in lines_to_log {
594 log::info!("{line}");
595 }
596 },
597 ClientInstruction::LogError(lines_to_log) => {
598 for line in lines_to_log {
599 log::error!("{line}");
600 }
601 },
602 ClientInstruction::SwitchSession(connect_to_session) => {
603 reconnect_to_session = Some(connect_to_session);
604 os_input.send_to_server(ClientToServerMsg::ClientExited);
605 break;
606 },
607 ClientInstruction::SetSynchronizedOutput(enabled) => {
608 synchronised_output = enabled;
609 },
610 ClientInstruction::QueryTerminalSize => {
611 os_input.send_to_server(ClientToServerMsg::TerminalResize(
612 os_input.get_terminal_size_using_fd(0),
613 ));
614 },
615 ClientInstruction::WriteConfigToDisk { config } => {
616 match Config::write_config_to_disk(config, &opts) {
617 Ok(written_config) => {
618 let _ = os_input
619 .send_to_server(ClientToServerMsg::ConfigWrittenToDisk(written_config));
620 },
621 Err(e) => {
622 let error_path = e
623 .as_ref()
624 .map(|p| p.display().to_string())
625 .unwrap_or_else(String::new);
626 log::error!("Failed to write config to disk: {}", error_path);
627 let _ = os_input
628 .send_to_server(ClientToServerMsg::FailedToWriteConfigToDisk(e));
629 },
630 }
631 },
632 ClientInstruction::StartWebServer => {
633 let web_server_base_url = web_server_base_url(
634 web_server_ip,
635 web_server_port,
636 has_certificate,
637 enforce_https_for_localhost,
638 );
639 match spawn_web_server(&opts) {
640 Ok(_) => {
641 let _ = os_input.send_to_server(ClientToServerMsg::WebServerStarted(
642 web_server_base_url,
643 ));
644 },
645 Err(e) => {
646 log::error!("Failed to start web_server: {}", e);
647 let _ =
648 os_input.send_to_server(ClientToServerMsg::FailedToStartWebServer(e));
649 },
650 }
651 },
652 _ => {},
653 }
654 }
655
656 router_thread.join().unwrap();
657
658 if reconnect_to_session.is_none() {
659 let reset_style = "\u{1b}[m";
660 let show_cursor = "\u{1b}[?25h";
661 let restore_snapshot = "\u{1b}[?1049l";
662 let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
663 let goodbye_message = format!(
664 "{}\n{}{}{}{}\n",
665 goto_start_of_last_line, restore_snapshot, reset_style, show_cursor, exit_msg
666 );
667
668 os_input.disable_mouse().non_fatal();
669 info!("{}", exit_msg);
670 os_input.unset_raw_mode(0).unwrap();
671 let mut stdout = os_input.get_stdout_writer();
672 let exit_kitty_keyboard_mode = "\u{1b}[<1u";
673 if !explicitly_disable_kitty_keyboard_protocol {
674 let _ = stdout.write(exit_kitty_keyboard_mode.as_bytes()).unwrap();
675 stdout.flush().unwrap();
676 }
677 let _ = stdout.write(goodbye_message.as_bytes()).unwrap();
678 stdout.flush().unwrap();
679 } else {
680 let clear_screen = "\u{1b}[2J";
681 let mut stdout = os_input.get_stdout_writer();
682 let _ = stdout.write(clear_screen.as_bytes()).unwrap();
683 stdout.flush().unwrap();
684 }
685
686 let _ = send_input_instructions.send(InputInstruction::Exit);
687
688 reconnect_to_session
689}
690
691pub fn start_server_detached(
692 mut os_input: Box<dyn ClientOsApi>,
693 opts: CliArgs,
694 config: Config,
695 config_options: Options,
696 info: ClientInfo,
697 layout: Option<Layout>,
698) {
699 envs::set_zellij("0".to_string());
700 config.env.set_vars();
701
702 let should_start_web_server = config_options.web_server.map(|w| w).unwrap_or(false);
703
704 let palette = config
705 .theme_config(config_options.theme.as_ref())
706 .unwrap_or_else(|| os_input.load_palette().into());
707
708 let client_attributes = ClientAttributes {
709 size: Size { rows: 50, cols: 50 }, style: Style {
712 colors: palette,
713 rounded_corners: config.ui.pane_frames.rounded_corners,
714 hide_session_name: config.ui.pane_frames.hide_session_name,
715 },
716 };
717
718 let create_ipc_pipe = || -> std::path::PathBuf {
719 let mut sock_dir = ZELLIJ_SOCK_DIR.clone();
720 std::fs::create_dir_all(&sock_dir).unwrap();
721 set_permissions(&sock_dir, 0o700).unwrap();
722 sock_dir.push(envs::get_session_name().unwrap());
723 sock_dir
724 };
725
726 let (first_msg, ipc_pipe) = match info {
727 ClientInfo::New(name) | ClientInfo::Resurrect(name, _) => {
728 envs::set_session_name(name.clone());
729 os_input.update_session_name(name);
730 let ipc_pipe = create_ipc_pipe();
731
732 spawn_server(&*ipc_pipe, opts.debug).unwrap();
733 if should_start_web_server {
734 if let Err(e) = spawn_web_server(&opts) {
735 log::error!("Failed to start web server: {}", e);
736 }
737 }
738 let should_launch_setup_wizard = false; let is_web_client = false;
741
742 (
743 ClientToServerMsg::NewClient(
744 client_attributes,
745 Box::new(opts),
746 Box::new(config.clone()),
747 Box::new(config_options.clone()),
748 Box::new(layout.unwrap()),
749 Box::new(config.plugins.clone()),
750 is_web_client,
751 should_launch_setup_wizard,
752 false,
753 ),
754 ipc_pipe,
755 )
756 },
757 _ => {
758 eprintln!("Session already exists");
759 std::process::exit(1);
760 },
761 };
762
763 os_input.connect_to_server(&*ipc_pipe);
764 os_input.send_to_server(first_msg);
765}
766
767pub fn report_changes_in_config_file(opts: &CliArgs, os_input: &Box<dyn ClientOsApi>) {
768 if let Some(config_file_path) = Config::config_file_path(&opts) {
769 let os_input = os_input.clone();
770 std::thread::spawn(move || {
771 let rt = tokio::runtime::Runtime::new().unwrap();
772 rt.block_on(async move {
773 watch_config_file_changes(config_file_path, move |new_config| {
774 let os_input = os_input.clone();
775 async move {
776 os_input.send_to_server(ClientToServerMsg::ConfigWrittenToDisk(new_config));
777 }
778 })
779 .await;
780 });
781 });
782 }
783}