Skip to main content

zellij_utils/
consts.rs

1//! Zellij program-wide constants.
2
3use crate::home::find_default_config_dir;
4use directories::ProjectDirs;
5use include_dir::{include_dir, Dir};
6use lazy_static::lazy_static;
7use std::{path::PathBuf, sync::OnceLock};
8use uuid::Uuid;
9
10pub const ZELLIJ_CONFIG_FILE_ENV: &str = "ZELLIJ_CONFIG_FILE";
11pub const ZELLIJ_CONFIG_DIR_ENV: &str = "ZELLIJ_CONFIG_DIR";
12pub const ZELLIJ_LAYOUT_DIR_ENV: &str = "ZELLIJ_LAYOUT_DIR";
13pub const VERSION: &str = env!("CARGO_PKG_VERSION");
14pub const DEFAULT_SCROLL_BUFFER_SIZE: usize = 10_000;
15pub static SCROLL_BUFFER_SIZE: OnceLock<usize> = OnceLock::new();
16pub static DEBUG_MODE: OnceLock<bool> = OnceLock::new();
17
18#[cfg(not(windows))]
19pub const SYSTEM_DEFAULT_CONFIG_DIR: &str = "/etc/zellij";
20#[cfg(windows)]
21pub const SYSTEM_DEFAULT_CONFIG_DIR: &str = "C:\\ProgramData\\Zellij";
22pub const SYSTEM_DEFAULT_DATA_DIR_PREFIX: &str = system_default_data_dir();
23
24pub static ZELLIJ_DEFAULT_THEMES: Dir = include_dir!("$CARGO_MANIFEST_DIR/assets/themes");
25
26pub const CLIENT_SERVER_CONTRACT_VERSION: usize = 1;
27
28pub fn session_info_cache_file_name(session_name: &str) -> PathBuf {
29    session_info_folder_for_session(session_name).join("session-metadata.kdl")
30}
31
32pub fn session_layout_cache_file_name(session_name: &str) -> PathBuf {
33    session_info_folder_for_session(session_name).join("session-layout.kdl")
34}
35
36pub fn session_info_folder_for_session(session_name: &str) -> PathBuf {
37    ZELLIJ_SESSION_INFO_CACHE_DIR.join(session_name)
38}
39
40pub fn create_config_and_cache_folders() {
41    if let Err(e) = std::fs::create_dir_all(&ZELLIJ_CACHE_DIR.as_path()) {
42        log::error!("Failed to create cache dir: {:?}", e);
43    }
44    if let Some(config_dir) = find_default_config_dir() {
45        if let Err(e) = std::fs::create_dir_all(&config_dir.as_path()) {
46            log::error!("Failed to create config dir: {:?}", e);
47        }
48    }
49    // while session_info is a child of cache currently, it won't necessarily always be this way,
50    // and so it's explicitly created here
51    if let Err(e) = std::fs::create_dir_all(&ZELLIJ_SESSION_INFO_CACHE_DIR.as_path()) {
52        log::error!("Failed to create session_info cache dir: {:?}", e);
53    }
54}
55
56const fn system_default_data_dir() -> &'static str {
57    if let Some(data_dir) = std::option_env!("PREFIX") {
58        data_dir
59    } else if cfg!(windows) {
60        "C:\\ProgramData\\Zellij"
61    } else {
62        "/usr"
63    }
64}
65
66lazy_static! {
67    pub static ref CLIENT_SERVER_CONTRACT_DIR: String =
68        format!("contract_version_{}", CLIENT_SERVER_CONTRACT_VERSION);
69    pub static ref ZELLIJ_PROJ_DIR: ProjectDirs = {
70        if cfg!(windows) {
71            ProjectDirs::from("", "", "Zellij").unwrap()
72        } else {
73            ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap()
74        }
75    };
76    pub static ref ZELLIJ_CACHE_DIR: PathBuf = ZELLIJ_PROJ_DIR.cache_dir().to_path_buf();
77    pub static ref ZELLIJ_SESSION_CACHE_DIR: PathBuf = ZELLIJ_PROJ_DIR
78        .cache_dir()
79        .to_path_buf()
80        .join(format!("{}", Uuid::new_v4()));
81    pub static ref ZELLIJ_PLUGIN_PERMISSIONS_CACHE: PathBuf =
82        ZELLIJ_CACHE_DIR.join("permissions.kdl");
83    pub static ref ZELLIJ_SESSION_INFO_CACHE_DIR: PathBuf = ZELLIJ_CACHE_DIR
84        .join(CLIENT_SERVER_CONTRACT_DIR.clone())
85        .join("session_info");
86    pub static ref ZELLIJ_STDIN_CACHE_FILE: PathBuf =
87        ZELLIJ_CACHE_DIR.join(VERSION).join("stdin_cache");
88    pub static ref ZELLIJ_PLUGIN_ARTIFACT_DIR: PathBuf = ZELLIJ_CACHE_DIR.join(VERSION);
89    pub static ref ZELLIJ_SEEN_RELEASE_NOTES_CACHE_FILE: PathBuf =
90        ZELLIJ_CACHE_DIR.join(VERSION).join("seen_release_notes");
91}
92
93pub const FEATURES: &[&str] = &[
94    #[cfg(feature = "disable_automatic_asset_installation")]
95    "disable_automatic_asset_installation",
96];
97
98#[cfg(not(target_family = "wasm"))]
99pub use not_wasm::*;
100
101#[cfg(not(target_family = "wasm"))]
102mod not_wasm {
103    use lazy_static::lazy_static;
104    use std::collections::HashMap;
105    use std::path::PathBuf;
106
107    // Convenience macro to add plugins to the asset map (see `ASSET_MAP`)
108    //
109    // Plugins are taken from:
110    //
111    // - `zellij-utils/assets/plugins`: When building in release mode OR when the
112    //   `plugins_from_target` feature IS NOT set
113    // - `zellij-utils/../target/wasm32-wasip1/debug`: When building in debug mode AND the
114    //   `plugins_from_target` feature IS set
115    macro_rules! add_plugin {
116        ($assets:expr, $plugin:literal) => {
117            $assets.insert(
118                PathBuf::from("plugins").join($plugin),
119                #[cfg(any(not(feature = "plugins_from_target"), not(debug_assertions)))]
120                include_bytes!(concat!(
121                    env!("CARGO_MANIFEST_DIR"),
122                    "/assets/plugins/",
123                    $plugin
124                ))
125                .to_vec(),
126                #[cfg(all(feature = "plugins_from_target", debug_assertions))]
127                include_bytes!(concat!(
128                    env!("CARGO_MANIFEST_DIR"),
129                    "/../target/wasm32-wasip1/debug/",
130                    $plugin
131                ))
132                .to_vec(),
133            );
134        };
135    }
136
137    lazy_static! {
138        // Zellij asset map
139        pub static ref ASSET_MAP: HashMap<PathBuf, Vec<u8>> = {
140            let mut assets = std::collections::HashMap::new();
141            add_plugin!(assets, "compact-bar.wasm");
142            add_plugin!(assets, "status-bar.wasm");
143            add_plugin!(assets, "tab-bar.wasm");
144            add_plugin!(assets, "strider.wasm");
145            add_plugin!(assets, "session-manager.wasm");
146            add_plugin!(assets, "configuration.wasm");
147            add_plugin!(assets, "plugin-manager.wasm");
148            add_plugin!(assets, "about.wasm");
149            add_plugin!(assets, "share.wasm");
150            add_plugin!(assets, "multiple-select.wasm");
151            add_plugin!(assets, "layout-manager.wasm");
152            add_plugin!(assets, "link.wasm");
153            assets
154        };
155    }
156}
157
158/// Check if a filesystem entry is an IPC socket.
159///
160/// On Unix, this checks `FileTypeExt::is_socket()`. On non-Unix platforms,
161/// this checks `is_file()` to detect marker files created by `ipc_bind()`
162/// and `ipc_bind_async()` alongside kernel-level named pipes.
163#[cfg(unix)]
164pub fn is_ipc_socket(file_type: &std::fs::FileType) -> bool {
165    use std::os::unix::fs::FileTypeExt;
166    file_type.is_socket()
167}
168
169#[cfg(not(unix))]
170pub fn is_ipc_socket(file_type: &std::fs::FileType) -> bool {
171    file_type.is_file()
172}
173
174/// Connect to an IPC socket at the given path.
175///
176/// On Unix, this uses Unix domain sockets via `GenericFilePath`.
177/// On Windows, this uses named pipes via `GenericNamespaced`.
178#[cfg(unix)]
179pub fn ipc_connect(path: &std::path::Path) -> std::io::Result<interprocess::local_socket::Stream> {
180    use interprocess::local_socket::{prelude::*, GenericFilePath, Stream as LocalSocketStream};
181    let fs_name = path.to_fs_name::<GenericFilePath>()?;
182    LocalSocketStream::connect(fs_name)
183}
184
185#[cfg(windows)]
186pub fn ipc_connect(path: &std::path::Path) -> std::io::Result<interprocess::local_socket::Stream> {
187    use interprocess::local_socket::{prelude::*, GenericNamespaced, Stream as LocalSocketStream};
188    let name = path.to_string_lossy().to_string();
189    let ns_name = name.to_ns_name::<GenericNamespaced>()?;
190    LocalSocketStream::connect(ns_name)
191}
192
193/// Create an IPC listener bound to the given path.
194///
195/// On Unix, this uses Unix domain sockets via `GenericFilePath`.
196/// On Windows, this uses named pipes via `GenericNamespaced` and creates
197/// a marker file for session discovery.
198#[cfg(unix)]
199pub fn ipc_bind(path: &std::path::Path) -> std::io::Result<interprocess::local_socket::Listener> {
200    use interprocess::local_socket::{prelude::*, GenericFilePath, ListenerOptions};
201    let fs_name = path.to_fs_name::<GenericFilePath>()?;
202    ListenerOptions::new().name(fs_name).create_sync()
203}
204
205#[cfg(windows)]
206pub fn ipc_bind(path: &std::path::Path) -> std::io::Result<interprocess::local_socket::Listener> {
207    use interprocess::local_socket::{prelude::*, GenericNamespaced, ListenerOptions};
208    let name = path.to_string_lossy().to_string();
209    let ns_name = name.to_ns_name::<GenericNamespaced>()?;
210    let listener = ListenerOptions::new().name(ns_name).create_sync()?;
211    std::fs::write(path, std::process::id().to_string())?;
212    Ok(listener)
213}
214
215/// Create an async (tokio) IPC listener bound to the given path.
216///
217/// On Unix, this uses Unix domain sockets via `GenericFilePath`.
218/// On Windows, this uses named pipes via `GenericNamespaced` and creates
219/// a marker file for session discovery.
220#[cfg(unix)]
221pub fn ipc_bind_async(
222    path: &std::path::Path,
223) -> std::io::Result<interprocess::local_socket::tokio::Listener> {
224    use interprocess::local_socket::{prelude::*, GenericFilePath, ListenerOptions};
225    let fs_name = path.to_fs_name::<GenericFilePath>()?;
226    ListenerOptions::new().name(fs_name).create_tokio()
227}
228
229#[cfg(windows)]
230pub fn ipc_bind_async(
231    path: &std::path::Path,
232) -> std::io::Result<interprocess::local_socket::tokio::Listener> {
233    use interprocess::local_socket::{prelude::*, GenericNamespaced, ListenerOptions};
234    let name = path.to_string_lossy().to_string();
235    let ns_name = name.to_ns_name::<GenericNamespaced>()?;
236    let listener = ListenerOptions::new().name(ns_name).create_tokio()?;
237    std::fs::write(path, std::process::id().to_string())?;
238    Ok(listener)
239}
240
241/// Connect to the reply pipe for a given IPC path (Windows only).
242///
243/// Uses `path-reply` as the named pipe for the server→client direction.
244#[cfg(windows)]
245pub fn ipc_connect_reply(
246    path: &std::path::Path,
247) -> std::io::Result<interprocess::local_socket::Stream> {
248    use interprocess::local_socket::{prelude::*, GenericNamespaced, Stream as LocalSocketStream};
249    let name = format!("{}-reply", path.to_string_lossy());
250    let ns_name = name.to_ns_name::<GenericNamespaced>()?;
251    LocalSocketStream::connect(ns_name)
252}
253
254/// Create an IPC listener for the reply pipe (Windows only).
255///
256/// Binds to `path-reply` as the named pipe for the server→client direction.
257#[cfg(windows)]
258pub fn ipc_bind_reply(
259    path: &std::path::Path,
260) -> std::io::Result<interprocess::local_socket::Listener> {
261    use interprocess::local_socket::{prelude::*, GenericNamespaced, ListenerOptions};
262    let name = format!("{}-reply", path.to_string_lossy());
263    let ns_name = name.to_ns_name::<GenericNamespaced>()?;
264    ListenerOptions::new().name(ns_name).create_sync()
265}
266
267#[cfg(unix)]
268pub use unix_only::*;
269
270#[cfg(unix)]
271mod unix_only {
272    use super::*;
273    use crate::envs;
274    pub use crate::shared::set_permissions;
275    use lazy_static::lazy_static;
276    use nix::unistd::Uid;
277    use std::env::temp_dir;
278
279    pub const ZELLIJ_SOCK_MAX_LENGTH: usize = 108;
280
281    lazy_static! {
282        static ref UID: Uid = Uid::current();
283        pub static ref ZELLIJ_TMP_DIR: PathBuf = temp_dir().join(format!("zellij-{}", *UID));
284        pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log");
285        pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = ZELLIJ_TMP_LOG_DIR.join("zellij.log");
286        pub static ref ZELLIJ_SOCK_DIR: PathBuf = {
287            let mut ipc_dir = envs::get_socket_dir().map_or_else(
288                |_| {
289                    ZELLIJ_PROJ_DIR
290                        .runtime_dir()
291                        .map_or_else(|| ZELLIJ_TMP_DIR.clone(), |p| p.to_owned())
292                },
293                PathBuf::from,
294            );
295            ipc_dir.push(CLIENT_SERVER_CONTRACT_DIR.clone());
296            ipc_dir
297        };
298        pub static ref WEBSERVER_SOCKET_PATH: PathBuf = ZELLIJ_SOCK_DIR.join("web_server_bus");
299    }
300}
301
302#[cfg(not(unix))]
303pub use not_unix::*;
304
305#[cfg(not(unix))]
306mod not_unix {
307    use super::*;
308    use crate::envs;
309    pub use crate::shared::set_permissions;
310    use lazy_static::lazy_static;
311    use std::env::temp_dir;
312
313    pub const ZELLIJ_SOCK_MAX_LENGTH: usize = 256;
314
315    lazy_static! {
316        pub static ref ZELLIJ_TMP_DIR: PathBuf = temp_dir().join("zellij");
317        pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log");
318        pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = ZELLIJ_TMP_LOG_DIR.join("zellij.log");
319        pub static ref ZELLIJ_SOCK_DIR: PathBuf = {
320            let mut ipc_dir = envs::get_socket_dir().map_or_else(
321                |_| {
322                    ZELLIJ_PROJ_DIR
323                        .runtime_dir()
324                        .map_or_else(|| ZELLIJ_TMP_DIR.clone(), |p| p.to_owned())
325                },
326                PathBuf::from,
327            );
328            ipc_dir.push(CLIENT_SERVER_CONTRACT_DIR.clone());
329            ipc_dir
330        };
331        pub static ref WEBSERVER_SOCKET_PATH: PathBuf = ZELLIJ_SOCK_DIR.join("web_server_bus");
332    }
333}