diff --git a/configurator/src/app/daemon_setup/command.rs b/configurator/src/app/daemon_setup/command.rs index 8efe9f2d..85712036 100644 --- a/configurator/src/app/daemon_setup/command.rs +++ b/configurator/src/app/daemon_setup/command.rs @@ -2,6 +2,8 @@ use std::env; use std::path::PathBuf; use std::process::Command; +use wayscriber::env_vars::PATH_ENV; + #[derive(Debug)] pub(super) struct CommandCapture { pub(super) success: bool, @@ -14,7 +16,7 @@ pub(super) fn command_available(program: &str) -> bool { } pub(super) fn find_in_path(binary_name: &str) -> Option { - let path_var = env::var_os("PATH")?; + let path_var = env::var_os(PATH_ENV)?; env::split_paths(&path_var) .map(|directory| directory.join(binary_name)) .find(|path| path.exists()) diff --git a/configurator/src/app/daemon_setup/hyprland.rs b/configurator/src/app/daemon_setup/hyprland.rs index 81b998be..9eeba407 100644 --- a/configurator/src/app/daemon_setup/hyprland.rs +++ b/configurator/src/app/daemon_setup/hyprland.rs @@ -367,6 +367,7 @@ mod tests { use super::*; use std::env; use std::sync::Mutex; + use wayscriber::env_vars::HOME_ENV; static ENV_MUTEX: Mutex<()> = Mutex::new(()); @@ -451,9 +452,9 @@ mod tests { .unwrap_or_else(|poisoned| poisoned.into_inner()); let tmp = crate::test_temp::tempdir().unwrap(); let home = tmp.path(); - let prev_home = env::var_os("HOME"); + let prev_home = env::var_os(HOME_ENV); unsafe { - env::set_var("HOME", home); + env::set_var(HOME_ENV, home); } let absolute = home @@ -467,8 +468,8 @@ mod tests { )); match prev_home { - Some(value) => unsafe { env::set_var("HOME", value) }, - None => unsafe { env::remove_var("HOME") }, + Some(value) => unsafe { env::set_var(HOME_ENV, value) }, + None => unsafe { env::remove_var(HOME_ENV) }, } } diff --git a/configurator/src/app/daemon_setup/service.rs b/configurator/src/app/daemon_setup/service.rs index fbad1899..68789d23 100644 --- a/configurator/src/app/daemon_setup/service.rs +++ b/configurator/src/app/daemon_setup/service.rs @@ -4,6 +4,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; use crate::models::DesktopEnvironment; +use wayscriber::env_vars::{BIN_ENV, PATH_ENV}; use wayscriber::runtime_capabilities::{ RUNTIME_CAPABILITIES_FLAG, RuntimeCapabilities, parse_runtime_capabilities, }; @@ -73,7 +74,7 @@ pub(super) fn require_systemctl_available() -> Result<(), String> { if command_available("systemctl") { Ok(()) } else { - Err("systemctl is not available in PATH.".to_string()) + Err(format!("systemctl is not available in {PATH_ENV}.")) } } @@ -311,14 +312,13 @@ pub(super) fn resolve_wayscriber_binary_path() -> Result { return Ok(path); } - Err( - "Unable to locate `wayscriber` binary. Set WAYSCRIBER_BIN or install `wayscriber` in PATH." - .to_string(), - ) + Err(format!( + "Unable to locate `wayscriber` binary. Set {BIN_ENV} or install `wayscriber` in {PATH_ENV}." + )) } fn explicit_wayscriber_binary_path() -> Option { - env::var_os("WAYSCRIBER_BIN") + env::var_os(BIN_ENV) .map(PathBuf::from) .filter(|path| path.exists()) } diff --git a/configurator/src/app/daemon_setup/shortcut.rs b/configurator/src/app/daemon_setup/shortcut.rs index 5b448434..6520ba2d 100644 --- a/configurator/src/app/daemon_setup/shortcut.rs +++ b/configurator/src/app/daemon_setup/shortcut.rs @@ -1,6 +1,7 @@ use std::fs; use crate::models::{DesktopEnvironment, ShortcutApplyCapability, ShortcutBackend}; +use wayscriber::env_vars::PATH_ENV; use wayscriber::shortcut_hint::{ GNOME_MEDIA_KEYS_KEY, GNOME_MEDIA_KEYS_SCHEMA, GNOME_WAYSCRIBER_KEYBINDING_PATH, PORTAL_APP_ID_ENV, PORTAL_SHORTCUT_ENV, PORTAL_SHORTCUT_OPT_IN_ENV, PortalShortcutDropInState, @@ -163,7 +164,7 @@ fn require_gsettings_available() -> Result<(), String> { if command_available("gsettings") { Ok(()) } else { - Err("gsettings is not available in PATH.".to_string()) + Err(format!("gsettings is not available in {PATH_ENV}.")) } } @@ -376,25 +377,27 @@ mod tests { #[test] fn parse_portal_shortcut_reads_dropin_value() { - let content = "[Service]\nEnvironment=\"WAYSCRIBER_PORTAL_SHORTCUT=g\"\n"; + let content = format!("[Service]\nEnvironment=\"{PORTAL_SHORTCUT_ENV}=g\"\n"); assert_eq!( - parse_portal_shortcut_from_dropin(content), + parse_portal_shortcut_from_dropin(&content), Some("g".to_string()) ); } #[test] fn parse_portal_shortcut_ignores_blank_value() { - let content = "[Service]\nEnvironment=\"WAYSCRIBER_PORTAL_SHORTCUT= \"\n"; - assert_eq!(parse_portal_shortcut_from_dropin(content), None); + let content = format!("[Service]\nEnvironment=\"{PORTAL_SHORTCUT_ENV}= \"\n"); + assert_eq!(parse_portal_shortcut_from_dropin(&content), None); } #[test] fn render_portal_shortcut_dropin_includes_explicit_opt_in_marker() { let rendered = render_portal_shortcut_dropin("g", PORTAL_APP_ID); - assert!(rendered.contains("Environment=\"WAYSCRIBER_ENABLE_PORTAL_SHORTCUTS=1\"")); - assert!(rendered.contains("Environment=\"WAYSCRIBER_PORTAL_SHORTCUT=g\"")); - assert!(rendered.contains("Environment=\"WAYSCRIBER_PORTAL_APP_ID=wayscriber\"")); + assert!(rendered.contains(&format!("Environment=\"{PORTAL_SHORTCUT_OPT_IN_ENV}=1\""))); + assert!(rendered.contains(&format!( + "Environment=\"{PORTAL_SHORTCUT_ENV}=g\"" + ))); + assert!(rendered.contains(&format!("Environment=\"{PORTAL_APP_ID_ENV}=wayscriber\""))); } #[test] diff --git a/configurator/src/app/session_catalog/duplicate.rs b/configurator/src/app/session_catalog/duplicate.rs index 4f4b872c..a194b00d 100644 --- a/configurator/src/app/session_catalog/duplicate.rs +++ b/configurator/src/app/session_catalog/duplicate.rs @@ -92,6 +92,7 @@ mod tests { DaemonRuntimeStatus, DesktopEnvironment, LightShortcutApplyCapability, ShortcutApplyCapability, ShortcutBackend, }; + use wayscriber::env_vars::{CATALOG_HOOKS_TEST_ENV, XDG_DATA_HOME_ENV, XDG_RUNTIME_DIR_ENV}; use super::*; @@ -105,13 +106,13 @@ mod tests { impl EnvGuard { fn set_roots(path: &Path) -> Self { let guard = crate::test_env::lock(); - let catalog_hooks = std::env::var_os("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS"); - let xdg_data_home = std::env::var_os("XDG_DATA_HOME"); - let xdg_runtime_dir = std::env::var_os("XDG_RUNTIME_DIR"); + let catalog_hooks = std::env::var_os(CATALOG_HOOKS_TEST_ENV); + let xdg_data_home = std::env::var_os(XDG_DATA_HOME_ENV); + let xdg_runtime_dir = std::env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - std::env::set_var("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS", path); - std::env::set_var("XDG_DATA_HOME", path); - std::env::set_var("XDG_RUNTIME_DIR", path); + std::env::set_var(CATALOG_HOOKS_TEST_ENV, path); + std::env::set_var(XDG_DATA_HOME_ENV, path); + std::env::set_var(XDG_RUNTIME_DIR_ENV, path); } Self { catalog_hooks, @@ -125,18 +126,16 @@ mod tests { impl Drop for EnvGuard { fn drop(&mut self) { match self.catalog_hooks.take() { - Some(value) => unsafe { - std::env::set_var("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS", value) - }, - None => unsafe { std::env::remove_var("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS") }, + Some(value) => unsafe { std::env::set_var(CATALOG_HOOKS_TEST_ENV, value) }, + None => unsafe { std::env::remove_var(CATALOG_HOOKS_TEST_ENV) }, } match self.xdg_data_home.take() { - Some(value) => unsafe { std::env::set_var("XDG_DATA_HOME", value) }, - None => unsafe { std::env::remove_var("XDG_DATA_HOME") }, + Some(value) => unsafe { std::env::set_var(XDG_DATA_HOME_ENV, value) }, + None => unsafe { std::env::remove_var(XDG_DATA_HOME_ENV) }, } match self.xdg_runtime_dir.take() { - Some(value) => unsafe { std::env::set_var("XDG_RUNTIME_DIR", value) }, - None => unsafe { std::env::remove_var("XDG_RUNTIME_DIR") }, + Some(value) => unsafe { std::env::set_var(XDG_RUNTIME_DIR_ENV, value) }, + None => unsafe { std::env::remove_var(XDG_RUNTIME_DIR_ENV) }, } } } diff --git a/configurator/src/app/session_catalog/move_file.rs b/configurator/src/app/session_catalog/move_file.rs index fda15e47..20efc7b9 100644 --- a/configurator/src/app/session_catalog/move_file.rs +++ b/configurator/src/app/session_catalog/move_file.rs @@ -137,6 +137,7 @@ mod tests { DaemonRuntimeStatus, DesktopEnvironment, LightShortcutApplyCapability, ShortcutApplyCapability, ShortcutBackend, }; + use wayscriber::env_vars::{CATALOG_HOOKS_TEST_ENV, XDG_DATA_HOME_ENV, XDG_RUNTIME_DIR_ENV}; use super::*; @@ -150,13 +151,13 @@ mod tests { impl EnvGuard { fn set_roots(path: &Path) -> Self { let guard = crate::test_env::lock(); - let catalog_hooks = std::env::var_os("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS"); - let xdg_data_home = std::env::var_os("XDG_DATA_HOME"); - let xdg_runtime_dir = std::env::var_os("XDG_RUNTIME_DIR"); + let catalog_hooks = std::env::var_os(CATALOG_HOOKS_TEST_ENV); + let xdg_data_home = std::env::var_os(XDG_DATA_HOME_ENV); + let xdg_runtime_dir = std::env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - std::env::set_var("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS", path); - std::env::set_var("XDG_DATA_HOME", path); - std::env::set_var("XDG_RUNTIME_DIR", path); + std::env::set_var(CATALOG_HOOKS_TEST_ENV, path); + std::env::set_var(XDG_DATA_HOME_ENV, path); + std::env::set_var(XDG_RUNTIME_DIR_ENV, path); } Self { catalog_hooks, @@ -170,18 +171,16 @@ mod tests { impl Drop for EnvGuard { fn drop(&mut self) { match self.catalog_hooks.take() { - Some(value) => unsafe { - std::env::set_var("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS", value) - }, - None => unsafe { std::env::remove_var("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS") }, + Some(value) => unsafe { std::env::set_var(CATALOG_HOOKS_TEST_ENV, value) }, + None => unsafe { std::env::remove_var(CATALOG_HOOKS_TEST_ENV) }, } match self.xdg_data_home.take() { - Some(value) => unsafe { std::env::set_var("XDG_DATA_HOME", value) }, - None => unsafe { std::env::remove_var("XDG_DATA_HOME") }, + Some(value) => unsafe { std::env::set_var(XDG_DATA_HOME_ENV, value) }, + None => unsafe { std::env::remove_var(XDG_DATA_HOME_ENV) }, } match self.xdg_runtime_dir.take() { - Some(value) => unsafe { std::env::set_var("XDG_RUNTIME_DIR", value) }, - None => unsafe { std::env::remove_var("XDG_RUNTIME_DIR") }, + Some(value) => unsafe { std::env::set_var(XDG_RUNTIME_DIR_ENV, value) }, + None => unsafe { std::env::remove_var(XDG_RUNTIME_DIR_ENV) }, } } } diff --git a/configurator/src/app/session_catalog/tests.rs b/configurator/src/app/session_catalog/tests.rs index 953f9925..54d78378 100644 --- a/configurator/src/app/session_catalog/tests.rs +++ b/configurator/src/app/session_catalog/tests.rs @@ -6,6 +6,7 @@ use std::sync::MutexGuard; use crate::models::{ DesktopEnvironment, LightShortcutApplyCapability, ShortcutApplyCapability, ShortcutBackend, }; +use wayscriber::env_vars::XDG_RUNTIME_DIR_ENV; struct RuntimeEnvGuard { previous: Option, @@ -15,9 +16,9 @@ struct RuntimeEnvGuard { impl RuntimeEnvGuard { fn set_xdg_runtime_dir(path: &Path) -> Self { let guard = crate::test_env::lock(); - let previous = std::env::var_os("XDG_RUNTIME_DIR"); + let previous = std::env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - std::env::set_var("XDG_RUNTIME_DIR", path); + std::env::set_var(XDG_RUNTIME_DIR_ENV, path); } Self { previous, @@ -29,8 +30,8 @@ impl RuntimeEnvGuard { impl Drop for RuntimeEnvGuard { fn drop(&mut self) { match self.previous.take() { - Some(value) => unsafe { std::env::set_var("XDG_RUNTIME_DIR", value) }, - None => unsafe { std::env::remove_var("XDG_RUNTIME_DIR") }, + Some(value) => unsafe { std::env::set_var(XDG_RUNTIME_DIR_ENV, value) }, + None => unsafe { std::env::remove_var(XDG_RUNTIME_DIR_ENV) }, } } } diff --git a/configurator/src/app/update/session_catalog/tests.rs b/configurator/src/app/update/session_catalog/tests.rs index 626cf0d2..3f7087fc 100644 --- a/configurator/src/app/update/session_catalog/tests.rs +++ b/configurator/src/app/update/session_catalog/tests.rs @@ -7,6 +7,7 @@ use crate::models::{ DesktopEnvironment, LightShortcutApplyCapability, SessionCatalogState, ShortcutApplyCapability, ShortcutBackend, }; +use wayscriber::env_vars::XDG_RUNTIME_DIR_ENV; struct RuntimeEnvGuard { previous: Option, @@ -16,9 +17,9 @@ struct RuntimeEnvGuard { impl RuntimeEnvGuard { fn set_xdg_runtime_dir(path: &Path) -> Self { let guard = crate::test_env::lock(); - let previous = std::env::var_os("XDG_RUNTIME_DIR"); + let previous = std::env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - std::env::set_var("XDG_RUNTIME_DIR", path); + std::env::set_var(XDG_RUNTIME_DIR_ENV, path); } Self { previous, @@ -30,8 +31,8 @@ impl RuntimeEnvGuard { impl Drop for RuntimeEnvGuard { fn drop(&mut self) { match self.previous.take() { - Some(value) => unsafe { std::env::set_var("XDG_RUNTIME_DIR", value) }, - None => unsafe { std::env::remove_var("XDG_RUNTIME_DIR") }, + Some(value) => unsafe { std::env::set_var(XDG_RUNTIME_DIR_ENV, value) }, + None => unsafe { std::env::remove_var(XDG_RUNTIME_DIR_ENV) }, } } } diff --git a/configurator/src/models/daemon.rs b/configurator/src/models/daemon.rs index 5118f58a..3e4f1880 100644 --- a/configurator/src/models/daemon.rs +++ b/configurator/src/models/daemon.rs @@ -1,3 +1,6 @@ +use wayscriber::env_vars::{ + HYPRLAND_INSTANCE_SIGNATURE_ENV, XDG_CURRENT_DESKTOP_ENV, XDG_SESSION_DESKTOP_ENV, +}; use wayscriber::shortcut_hint::{ PortalShortcutDropInState, ShortcutRuntimeBackend, ShortcutRuntimeInputs, is_gnome_desktop, resolve_shortcut_runtime_backend, @@ -13,11 +16,11 @@ pub enum DesktopEnvironment { impl DesktopEnvironment { pub fn detect_current() -> Self { - if std::env::var_os("HYPRLAND_INSTANCE_SIGNATURE").is_some() { + if std::env::var_os(HYPRLAND_INSTANCE_SIGNATURE_ENV).is_some() { return Self::Hyprland; } - let current = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(); - let session = std::env::var("XDG_SESSION_DESKTOP").unwrap_or_default(); + let current = std::env::var(XDG_CURRENT_DESKTOP_ENV).unwrap_or_default(); + let session = std::env::var(XDG_SESSION_DESKTOP_ENV).unwrap_or_default(); Self::from_desktop_strings(¤t, &session) } diff --git a/src/app/env.rs b/src/app/env.rs index a9074671..2020abcd 100644 --- a/src/app/env.rs +++ b/src/app/env.rs @@ -15,6 +15,7 @@ mod tests { use std::env; use std::sync::Mutex; + const TEST_FLAG_ENV: &str = "WAYSCRIBER_TEST_FLAG"; static ENV_MUTEX: Mutex<()> = Mutex::new(()); #[test] @@ -26,16 +27,16 @@ mod tests { for value in ["1", "true", "yes", "on", "TrUe"] { // SAFETY: serialized via ENV_MUTEX unsafe { - env::set_var("WAYSCRIBER_TEST_FLAG", value); + env::set_var(TEST_FLAG_ENV, value); } assert!( - env_flag_enabled("WAYSCRIBER_TEST_FLAG"), + env_flag_enabled(TEST_FLAG_ENV), "expected '{value}' to be treated as truthy" ); } unsafe { - env::remove_var("WAYSCRIBER_TEST_FLAG"); + env::remove_var(TEST_FLAG_ENV); } } @@ -48,16 +49,16 @@ mod tests { for value in ["0", "false", "no", "off", "", "random"] { // SAFETY: serialized via ENV_MUTEX unsafe { - env::set_var("WAYSCRIBER_TEST_FLAG", value); + env::set_var(TEST_FLAG_ENV, value); } assert!( - !env_flag_enabled("WAYSCRIBER_TEST_FLAG"), + !env_flag_enabled(TEST_FLAG_ENV), "expected '{value}' to be treated as falsey" ); } unsafe { - env::remove_var("WAYSCRIBER_TEST_FLAG"); + env::remove_var(TEST_FLAG_ENV); } } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 49449b7e..9fc268a4 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -5,6 +5,7 @@ mod usage; use crate::backend::ExitAfterCaptureMode; use crate::cli::Cli; use crate::daemon::DaemonToggleRequest; +use crate::env_vars::{DETACHED_ENV, NO_DETACH_ENV, NO_TRAY_ENV, WAYLAND_DISPLAY_ENV}; use crate::paths::overlay_lock_file; use crate::session::try_lock_exclusive; use crate::session_override::set_runtime_session_override; @@ -44,15 +45,14 @@ fn maybe_detach_active(cli: &Cli) -> anyhow::Result { if !(cli.active || cli.freeze) { return Ok(false); } - if env_flag_enabled("WAYSCRIBER_NO_DETACH") || std::env::var_os("WAYSCRIBER_DETACHED").is_some() - { + if env_flag_enabled(NO_DETACH_ENV) || std::env::var_os(DETACHED_ENV).is_some() { return Ok(false); } let exe = std::env::current_exe()?; let args: Vec = std::env::args_os().skip(1).collect(); let mut cmd = Command::new(exe); cmd.args(args) - .env("WAYSCRIBER_DETACHED", "1") + .env(DETACHED_ENV, "1") .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()); @@ -130,7 +130,7 @@ pub fn run(cli: Cli) -> anyhow::Result<()> { } #[cfg(unix)] - if std::env::var_os("WAYSCRIBER_DETACHED").is_some() { + if std::env::var_os(DETACHED_ENV).is_some() { detach_from_tty(); } @@ -182,18 +182,18 @@ pub fn run(cli: Cli) -> anyhow::Result<()> { } // Check for Wayland environment - if std::env::var("WAYLAND_DISPLAY").is_err() && (cli.daemon || cli.active || cli.freeze) { + if std::env::var(WAYLAND_DISPLAY_ENV).is_err() && (cli.daemon || cli.active || cli.freeze) { return Err(anyhow::anyhow!( - "WAYLAND_DISPLAY not set - this application requires Wayland." + "{WAYLAND_DISPLAY_ENV} not set - this application requires Wayland." )); } if cli.daemon { // Daemon mode: background service with toggle activation log::info!("Starting in daemon mode"); - let tray_disabled = cli.no_tray || env_flag_enabled("WAYSCRIBER_NO_TRAY"); + let tray_disabled = cli.no_tray || env_flag_enabled(NO_TRAY_ENV); if tray_disabled { - log::info!("Tray disabled via --no-tray / WAYSCRIBER_NO_TRAY"); + log::info!("Tray disabled via --no-tray / {NO_TRAY_ENV}"); } let mut daemon = crate::daemon::Daemon::new( cli.mode, diff --git a/src/app/session.rs b/src/app/session.rs index 22beb401..87de484e 100644 --- a/src/app/session.rs +++ b/src/app/session.rs @@ -1,9 +1,10 @@ use crate::cli::Cli; +use crate::env_vars::WAYLAND_DISPLAY_ENV; pub(crate) fn run_session_cli_commands(cli: &Cli) -> anyhow::Result<()> { let loaded = crate::config::Config::load()?; let config_dir = crate::config::Config::config_directory_from_source(&loaded.source)?; - let display_env = std::env::var("WAYLAND_DISPLAY").ok(); + let display_env = std::env::var(WAYLAND_DISPLAY_ENV).ok(); let options = if let Some(raw_path) = cli.session_file.as_ref() { let raw = raw_path diff --git a/src/app_id.rs b/src/app_id.rs index 142f1b31..d9342024 100644 --- a/src/app_id.rs +++ b/src/app_id.rs @@ -1,6 +1,6 @@ const DEFAULT_APP_ID: &str = "wayscriber"; -const APP_ID_ENV: &str = "WAYSCRIBER_APP_ID"; -const PORTAL_APP_ID_ENV: &str = "WAYSCRIBER_PORTAL_APP_ID"; + +use crate::env_vars::{APP_ID_ENV, PORTAL_APP_ID_ENV}; pub(crate) fn runtime_app_id() -> String { std::env::var(APP_ID_ENV) diff --git a/src/backend/mod.rs b/src/backend/mod.rs index f89d6221..44034aa6 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,6 +1,8 @@ use anyhow::Result; use wayland_client::Connection; +use crate::env_vars::WAYLAND_DISPLAY_ENV; + pub mod wayland; // Removed: Backend trait - no longer needed with single backend @@ -38,9 +40,9 @@ pub fn run_wayland( } pub fn preflight_wayland_connection() -> Result<()> { - if std::env::var("WAYLAND_DISPLAY").is_err() { + if std::env::var(WAYLAND_DISPLAY_ENV).is_err() { return Err(anyhow::anyhow!( - "WAYLAND_DISPLAY not set - this application requires Wayland." + "{WAYLAND_DISPLAY_ENV} not set - this application requires Wayland." )); } let _conn = Connection::connect_to_env() @@ -53,8 +55,11 @@ mod tests { #[test] #[ignore] fn wayland_backend_smoke_test() { - if std::env::var("WAYLAND_DISPLAY").is_err() { - eprintln!("WAYLAND_DISPLAY not set; skipping Wayland smoke test"); + if std::env::var(super::WAYLAND_DISPLAY_ENV).is_err() { + eprintln!( + "{} not set; skipping Wayland smoke test", + super::WAYLAND_DISPLAY_ENV + ); return; } super::run_wayland(None, false, super::ExitAfterCaptureMode::Never, None) diff --git a/src/backend/wayland/backend/setup.rs b/src/backend/wayland/backend/setup.rs index c5e5918a..3ebbe983 100644 --- a/src/backend/wayland/backend/setup.rs +++ b/src/backend/wayland/backend/setup.rs @@ -16,6 +16,8 @@ use smithay_client_toolkit::{ use wayland_client::{Connection, EventQueue, globals::registry_queue_init}; use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1; +use crate::env_vars::{XDG_CURRENT_DESKTOP_ENV, XDG_SESSION_DESKTOP_ENV}; + use super::super::state::{WaylandGlobals, WaylandState}; // Freeze/zoom capture currently consumes wl_shm buffer events and ignores linux-dmabuf. @@ -56,9 +58,9 @@ pub(super) fn setup_wayland() -> Result { } Err(err) => { let desktop_env = - std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_else(|_| "unknown".into()); + std::env::var(XDG_CURRENT_DESKTOP_ENV).unwrap_or_else(|_| "unknown".into()); let session_env = - std::env::var("XDG_SESSION_DESKTOP").unwrap_or_else(|_| "unknown".into()); + std::env::var(XDG_SESSION_DESKTOP_ENV).unwrap_or_else(|_| "unknown".into()); warn!( "Layer shell not available: {} (desktop='{}', session='{}'); toolbars will be disabled and xdg fallback may not cover docks/panels.", err, desktop_env, session_env diff --git a/src/backend/wayland/backend/state_init/mod.rs b/src/backend/wayland/backend/state_init/mod.rs index 3a7ffe89..e3bebe98 100644 --- a/src/backend/wayland/backend/state_init/mod.rs +++ b/src/backend/wayland/backend/state_init/mod.rs @@ -8,6 +8,7 @@ use super::WaylandBackend; use super::setup::WaylandSetup; use super::tray::process_tray_action; use crate::backend::wayland::portal_capture::screenshot_portal_available; +use crate::env_vars::{DESKTOP_SESSION_ENV, XDG_CURRENT_DESKTOP_ENV, XDG_SESSION_DESKTOP_ENV}; use crate::{ capture::CaptureManager, config::Config, @@ -162,9 +163,9 @@ fn apply_initial_mode(backend: &WaylandBackend, _config: &Config, input_state: & fn desktop_environment_from_env() -> DesktopEnvironment { let values = [ - env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(), - env::var("XDG_SESSION_DESKTOP").unwrap_or_default(), - env::var("DESKTOP_SESSION").unwrap_or_default(), + env::var(XDG_CURRENT_DESKTOP_ENV).unwrap_or_default(), + env::var(XDG_SESSION_DESKTOP_ENV).unwrap_or_default(), + env::var(DESKTOP_SESSION_ENV).unwrap_or_default(), ]; if values .iter() diff --git a/src/backend/wayland/backend/state_init/output.rs b/src/backend/wayland/backend/state_init/output.rs index 0f4f89a8..289049fe 100644 --- a/src/backend/wayland/backend/state_init/output.rs +++ b/src/backend/wayland/backend/state_init/output.rs @@ -2,6 +2,10 @@ use log::{info, warn}; use std::env; use crate::config::Config; +use crate::env_vars::{ + DESKTOP_SESSION_ENV, SWAYSOCK_ENV, XDG_CURRENT_DESKTOP_ENV, XDG_FULLSCREEN_ENV, + XDG_FULLSCREEN_FORCE_ENV, XDG_OUTPUT_ENV, XDG_SESSION_DESKTOP_ENV, +}; pub(super) struct OutputPreferences { pub(super) preferred_output_identity: Option, @@ -10,7 +14,7 @@ pub(super) struct OutputPreferences { } pub(super) fn resolve(config: &Config) -> OutputPreferences { - let preferred_output_identity = env::var("WAYSCRIBER_XDG_OUTPUT") + let preferred_output_identity = env::var(XDG_OUTPUT_ENV) .ok() .or_else(|| config.ui.preferred_output.clone()); if let Some(ref output) = preferred_output_identity { @@ -20,20 +24,20 @@ pub(super) fn resolve(config: &Config) -> OutputPreferences { ); } - let mut xdg_fullscreen = env::var("WAYSCRIBER_XDG_FULLSCREEN") + let mut xdg_fullscreen = env::var(XDG_FULLSCREEN_ENV) .ok() .map(|v| v == "1" || v.eq_ignore_ascii_case("true")) .unwrap_or(config.ui.xdg_fullscreen); - let desktop_env = env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(); - let session_env = env::var("XDG_SESSION_DESKTOP").unwrap_or_default(); - let desktop_session = env::var("DESKTOP_SESSION").unwrap_or_default(); - let sway_sock = env::var("SWAYSOCK").unwrap_or_default(); - let force_fullscreen = env::var("WAYSCRIBER_XDG_FULLSCREEN_FORCE") + let desktop_env = env::var(XDG_CURRENT_DESKTOP_ENV).unwrap_or_default(); + let session_env = env::var(XDG_SESSION_DESKTOP_ENV).unwrap_or_default(); + let desktop_session = env::var(DESKTOP_SESSION_ENV).unwrap_or_default(); + let sway_sock = env::var(SWAYSOCK_ENV).unwrap_or_default(); + let force_fullscreen = env::var(XDG_FULLSCREEN_FORCE_ENV) .map(|v| v == "1" || v.eq_ignore_ascii_case("true")) .unwrap_or(false); if xdg_fullscreen && desktop_env.to_uppercase().contains("GNOME") && !force_fullscreen { warn!( - "GNOME fullscreen xdg fallback is opaque; falling back to maximized. Set WAYSCRIBER_XDG_FULLSCREEN_FORCE=1 to force fullscreen anyway." + "GNOME fullscreen xdg fallback is opaque; falling back to maximized. Set {XDG_FULLSCREEN_FORCE_ENV}=1 to force fullscreen anyway." ); xdg_fullscreen = false; } diff --git a/src/backend/wayland/backend/state_init/session.rs b/src/backend/wayland/backend/state_init/session.rs index 841c29fa..db94f328 100644 --- a/src/backend/wayland/backend/state_init/session.rs +++ b/src/backend/wayland/backend/state_init/session.rs @@ -3,6 +3,7 @@ use std::env; use std::path::{Path, PathBuf}; use crate::config::Config; +use crate::env_vars::WAYLAND_DISPLAY_ENV; use crate::{RESUME_SESSION_ENV, paths, session}; use super::super::helpers::resume_override_from_env; @@ -12,7 +13,7 @@ pub(super) fn build_session_options( config_dir: &Path, named_session_file: Option, ) -> Option { - let display_env = env::var("WAYLAND_DISPLAY").ok(); + let display_env = env::var(WAYLAND_DISPLAY_ENV).ok(); let resume_override = resume_override_from_env(); let mut session_options = if let Some(path) = named_session_file { let mut options = session::options_from_config_for_named_file( diff --git a/src/backend/wayland/session/tests.rs b/src/backend/wayland/session/tests.rs index 08966aa3..7c47c880 100644 --- a/src/backend/wayland/session/tests.rs +++ b/src/backend/wayland/session/tests.rs @@ -4,6 +4,7 @@ use crate::draw::{ Color, EraserKind, FontDescriptor, Frame, PageDeleteOutcome, REGULAR_POLYGON_DEFAULT_SIDES, Shape, ShapeId, }; +use crate::env_vars::{CATALOG_HOOKS_TEST_ENV, XDG_DATA_HOME_ENV}; use crate::input::{ BOARD_ID_TRANSPARENT, BOARD_ID_WHITEBOARD, ClickHighlightSettings, DrawingState, EraserMode, Tool, @@ -25,11 +26,11 @@ struct EnvGuard { impl EnvGuard { fn set_xdg_data_home(path: &Path) -> Self { let guard = crate::test_env::lock(); - let catalog_hooks = std::env::var_os("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS"); - let xdg_data_home = std::env::var_os("XDG_DATA_HOME"); + let catalog_hooks = std::env::var_os(CATALOG_HOOKS_TEST_ENV); + let xdg_data_home = std::env::var_os(XDG_DATA_HOME_ENV); unsafe { - std::env::set_var("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS", path); - std::env::set_var("XDG_DATA_HOME", path); + std::env::set_var(CATALOG_HOOKS_TEST_ENV, path); + std::env::set_var(XDG_DATA_HOME_ENV, path); } Self { _guard: guard, @@ -42,14 +43,12 @@ impl EnvGuard { impl Drop for EnvGuard { fn drop(&mut self) { match self.catalog_hooks.take() { - Some(value) => unsafe { - std::env::set_var("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS", value) - }, - None => unsafe { std::env::remove_var("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS") }, + Some(value) => unsafe { std::env::set_var(CATALOG_HOOKS_TEST_ENV, value) }, + None => unsafe { std::env::remove_var(CATALOG_HOOKS_TEST_ENV) }, } match self.xdg_data_home.take() { - Some(value) => unsafe { std::env::set_var("XDG_DATA_HOME", value) }, - None => unsafe { std::env::remove_var("XDG_DATA_HOME") }, + Some(value) => unsafe { std::env::set_var(XDG_DATA_HOME_ENV, value) }, + None => unsafe { std::env::remove_var(XDG_DATA_HOME_ENV) }, } } } diff --git a/src/backend/wayland/state/clipboard/session_paste.rs b/src/backend/wayland/state/clipboard/session_paste.rs index 11ba2403..237a0dd4 100644 --- a/src/backend/wayland/state/clipboard/session_paste.rs +++ b/src/backend/wayland/state/clipboard/session_paste.rs @@ -2,6 +2,7 @@ use super::super::WaylandState; use crate::config::{Action, Config}; use crate::draw::frame::UndoAction; use crate::draw::{EmbeddedImage, Shape}; +use crate::env_vars::HOME_ENV; use crate::input::InputState; use crate::input::boards::BoardState; use crate::input::state::{ClipboardPasteRequest, UiToastKind}; @@ -416,7 +417,7 @@ fn config_path_display() -> String { } fn compact_path(path: PathBuf) -> String { - let Some(home) = std::env::var_os("HOME").map(PathBuf::from) else { + let Some(home) = std::env::var_os(HOME_ENV).map(PathBuf::from) else { return path.display().to_string(); }; match path.strip_prefix(&home) { diff --git a/src/backend/wayland/state/core/init.rs b/src/backend/wayland/state/core/init.rs index 9879f2b8..ab761173 100644 --- a/src/backend/wayland/state/core/init.rs +++ b/src/backend/wayland/state/core/init.rs @@ -1,5 +1,6 @@ use super::super::buffer_damage::BufferDamageTracker; use super::super::*; +use crate::env_vars::{FORCE_INLINE_TOOLBARS_ENV, XDG_ACTIVATION_TOKEN_ENV}; impl WaylandState { pub(in crate::backend::wayland) fn new(init: WaylandStateInit) -> Self { @@ -60,7 +61,7 @@ impl WaylandState { layer_shell.is_none() || force_inline_toolbars || main_surface_uses_overlay_layer; if force_inline_toolbars { info!( - "Forcing inline toolbars (config/ui.toolbar.force_inline or WAYSCRIBER_FORCE_INLINE_TOOLBARS)" + "Forcing inline toolbars (config/ui.toolbar.force_inline or {FORCE_INLINE_TOOLBARS_ENV})" ); } if main_surface_uses_overlay_layer { @@ -167,7 +168,7 @@ impl WaylandState { } fn startup_activation_token_from_env() -> Option { - std::env::var("XDG_ACTIVATION_TOKEN") + std::env::var(XDG_ACTIVATION_TOKEN_ENV) .ok() .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()) diff --git a/src/backend/wayland/state/helpers.rs b/src/backend/wayland/state/helpers.rs index 29661467..83558e17 100644 --- a/src/backend/wayland/state/helpers.rs +++ b/src/backend/wayland/state/helpers.rs @@ -2,6 +2,11 @@ use std::{sync::OnceLock, time::Duration}; use wayland_client::{Proxy, protocol::wl_surface}; +use crate::env_vars::{ + DEBUG_DAMAGE_ENV, DEBUG_TOOLBAR_COLOR_ENV, DEBUG_TOOLBAR_DRAG_ENV, FORCE_INLINE_TOOLBARS_ENV, + TOOLBAR_DRAG_HANDOFF_MS_ENV, TOOLBAR_DRAG_PREVIEW_ENV, TOOLBAR_DRAG_THROTTLE_MS_ENV, + TOOLBAR_POINTER_LOCK_ENV, +}; use crate::{config::Config, util::Rect}; #[allow(dead_code)] @@ -72,7 +77,7 @@ pub(super) fn parse_debug_damage_env(raw: &str) -> bool { pub(in crate::backend::wayland) fn debug_damage_logging_enabled() -> bool { static ENABLED: OnceLock = OnceLock::new(); *ENABLED.get_or_init(|| { - parse_debug_damage_env(&std::env::var("WAYSCRIBER_DEBUG_DAMAGE").unwrap_or_default()) + parse_debug_damage_env(&std::env::var(DEBUG_DAMAGE_ENV).unwrap_or_default()) }) } @@ -83,14 +88,14 @@ pub(in crate::backend::wayland) fn surface_id(surface: &wl_surface::WlSurface) - pub(in crate::backend::wayland) fn debug_toolbar_drag_logging_enabled() -> bool { static ENABLED: OnceLock = OnceLock::new(); *ENABLED.get_or_init(|| { - parse_boolish_env(&std::env::var("WAYSCRIBER_DEBUG_TOOLBAR_DRAG").unwrap_or_default()) + parse_boolish_env(&std::env::var(DEBUG_TOOLBAR_DRAG_ENV).unwrap_or_default()) }) } pub(in crate::backend::wayland) fn debug_toolbar_color_logging_enabled() -> bool { static ENABLED: OnceLock = OnceLock::new(); *ENABLED.get_or_init(|| { - parse_boolish_env(&std::env::var("WAYSCRIBER_DEBUG_TOOLBAR_COLOR").unwrap_or_default()) + parse_boolish_env(&std::env::var(DEBUG_TOOLBAR_COLOR_ENV).unwrap_or_default()) }) } @@ -98,26 +103,21 @@ pub(in crate::backend::wayland) fn toolbar_pointer_lock_enabled() -> bool { static ENABLED: OnceLock = OnceLock::new(); *ENABLED.get_or_init(|| { // Default ON: without pointer lock, layer-shell toolbar drags jitter/flicker as surfaces move. - parse_boolish_env( - &std::env::var("WAYSCRIBER_TOOLBAR_POINTER_LOCK").unwrap_or_else(|_| "1".into()), - ) + parse_boolish_env(&std::env::var(TOOLBAR_POINTER_LOCK_ENV).unwrap_or_else(|_| "1".into())) }) } pub(in crate::backend::wayland) fn toolbar_drag_preview_enabled() -> bool { static ENABLED: OnceLock = OnceLock::new(); *ENABLED.get_or_init(|| { - parse_boolish_env( - &std::env::var("WAYSCRIBER_TOOLBAR_DRAG_PREVIEW").unwrap_or_else(|_| "1".into()), - ) + parse_boolish_env(&std::env::var(TOOLBAR_DRAG_PREVIEW_ENV).unwrap_or_else(|_| "1".into())) }) } pub(in crate::backend::wayland) fn toolbar_drag_throttle_interval() -> Option { static VALUE: OnceLock> = OnceLock::new(); *VALUE.get_or_init(|| { - let raw = - std::env::var("WAYSCRIBER_TOOLBAR_DRAG_THROTTLE_MS").unwrap_or_else(|_| "12".into()); + let raw = std::env::var(TOOLBAR_DRAG_THROTTLE_MS_ENV).unwrap_or_else(|_| "12".into()); let trimmed = raw.trim(); if trimmed.is_empty() { return Some(Duration::from_millis(12)); @@ -136,8 +136,7 @@ pub(in crate::backend::wayland) fn toolbar_drag_throttle_interval() -> Option Duration { static VALUE: OnceLock = OnceLock::new(); *VALUE.get_or_init(|| { - let raw = - std::env::var("WAYSCRIBER_TOOLBAR_DRAG_HANDOFF_MS").unwrap_or_else(|_| "250".into()); + let raw = std::env::var(TOOLBAR_DRAG_HANDOFF_MS_ENV).unwrap_or_else(|_| "250".into()); let Ok(ms) = raw.trim().parse::() else { return Duration::from_millis(250); }; @@ -160,7 +159,7 @@ pub(in crate::backend::wayland) fn color_log(message: impl AsRef) { fn force_inline_env_enabled() -> bool { static ENABLED: OnceLock = OnceLock::new(); *ENABLED.get_or_init(|| { - parse_boolish_env(&std::env::var("WAYSCRIBER_FORCE_INLINE_TOOLBARS").unwrap_or_default()) + parse_boolish_env(&std::env::var(FORCE_INLINE_TOOLBARS_ENV).unwrap_or_default()) }) } diff --git a/src/backend/wayland/state/toolbar/events/tests.rs b/src/backend/wayland/state/toolbar/events/tests.rs index 71bf79f9..0779bfd9 100644 --- a/src/backend/wayland/state/toolbar/events/tests.rs +++ b/src/backend/wayland/state/toolbar/events/tests.rs @@ -6,6 +6,7 @@ use super::session::{ use super::*; use crate::config::ToolbarLayoutMode; use crate::draw::{Color, FontDescriptor}; +use crate::env_vars::XDG_DATA_HOME_ENV; use crate::input::state::test_support::make_test_input_state; use crate::input::{EraserMode, Tool}; use crate::ui::toolbar::ToolbarSideSection; @@ -22,9 +23,9 @@ struct EnvGuard { impl EnvGuard { fn set_xdg_data_home(path: &Path) -> Self { let guard = crate::test_env::lock(); - let xdg_data_home = std::env::var_os("XDG_DATA_HOME"); + let xdg_data_home = std::env::var_os(XDG_DATA_HOME_ENV); unsafe { - std::env::set_var("XDG_DATA_HOME", path); + std::env::set_var(XDG_DATA_HOME_ENV, path); } Self { _guard: guard, @@ -36,8 +37,8 @@ impl EnvGuard { impl Drop for EnvGuard { fn drop(&mut self) { match self.xdg_data_home.take() { - Some(value) => unsafe { std::env::set_var("XDG_DATA_HOME", value) }, - None => unsafe { std::env::remove_var("XDG_DATA_HOME") }, + Some(value) => unsafe { std::env::set_var(XDG_DATA_HOME_ENV, value) }, + None => unsafe { std::env::remove_var(XDG_DATA_HOME_ENV) }, } } } diff --git a/src/backend/wayland/state/toolbar/visibility/pointer.rs b/src/backend/wayland/state/toolbar/visibility/pointer.rs index 51aa5b90..255a92f2 100644 --- a/src/backend/wayland/state/toolbar/visibility/pointer.rs +++ b/src/backend/wayland/state/toolbar/visibility/pointer.rs @@ -1,4 +1,5 @@ use super::*; +use crate::env_vars::TOOLBAR_POINTER_LOCK_ENV; use wayland_client::Proxy; impl WaylandState { @@ -19,7 +20,7 @@ impl WaylandState { surface_id(surface) )); if !toolbar_pointer_lock_enabled() { - log::info!("skip pointer lock: disabled via WAYSCRIBER_TOOLBAR_POINTER_LOCK"); + log::info!("skip pointer lock: disabled via {TOOLBAR_POINTER_LOCK_ENV}"); return; } if self.pointer_lock_active() { diff --git a/src/backend/wayland/state/toolbar/visibility/sync.rs b/src/backend/wayland/state/toolbar/visibility/sync.rs index 880f64ea..1b6b46d7 100644 --- a/src/backend/wayland/state/toolbar/visibility/sync.rs +++ b/src/backend/wayland/state/toolbar/visibility/sync.rs @@ -1,4 +1,5 @@ use super::*; +use crate::env_vars::{XDG_CURRENT_DESKTOP_ENV, XDG_SESSION_DESKTOP_ENV}; impl WaylandState { pub(in crate::backend::wayland) fn desired_keyboard_interactivity( @@ -19,8 +20,10 @@ impl WaylandState { return; } - let desktop_env = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_else(|_| "unknown".into()); - let session_env = std::env::var("XDG_SESSION_DESKTOP").unwrap_or_else(|_| "unknown".into()); + let desktop_env = + std::env::var(XDG_CURRENT_DESKTOP_ENV).unwrap_or_else(|_| "unknown".into()); + let session_env = + std::env::var(XDG_SESSION_DESKTOP_ENV).unwrap_or_else(|_| "unknown".into()); log::info!( "Layer-shell protocol unavailable; toolbar surfaces will not appear (desktop='{}', session='{}'). Overlay may be limited to the work area on compositors like GNOME.", desktop_env, diff --git a/src/canvas_export/pdf_labels.rs b/src/canvas_export/pdf_labels.rs index 4f0f489c..97099dc5 100644 --- a/src/canvas_export/pdf_labels.rs +++ b/src/canvas_export/pdf_labels.rs @@ -1,5 +1,8 @@ use crate::config::{ - PdfLabelConfig, PdfLabelContentMode, PdfLabelPosition, validate_pdf_label_template, + PDF_LABEL_APP_BOARD, PDF_LABEL_APP_BOARDS, PDF_LABEL_BOARD_NAME, PDF_LABEL_DOCUMENT_PAGE, + PDF_LABEL_DOCUMENT_PAGES, PDF_LABEL_EXPORT_BOARD, PDF_LABEL_EXPORT_BOARDS, PDF_LABEL_PAGE, + PDF_LABEL_PAGE_NAME, PDF_LABEL_PAGES, PdfLabelConfig, PdfLabelContentMode, PdfLabelPosition, + validate_pdf_label_template, }; use super::pdf::PdfPageMetadata; @@ -162,16 +165,16 @@ pub(crate) fn format_pdf_label_template( fn label_value<'a>(name: &str, metadata: &'a PdfPageMetadata) -> Option<&'a str> { match name { - "app_board" => Some(metadata.app_board_label.as_str()), - "app_boards" => Some(metadata.app_board_count_label.as_str()), - "export_board" => Some(metadata.export_board_label.as_str()), - "export_boards" => Some(metadata.export_board_count_label.as_str()), - "page" => Some(metadata.board_page_label.as_str()), - "pages" => Some(metadata.board_page_count_label.as_str()), - "document_page" => Some(metadata.document_page_label.as_str()), - "document_pages" => Some(metadata.document_page_count_label.as_str()), - "board_name" => Some(metadata.board_name.as_str()), - "page_name" => Some(metadata.page_name_label.as_str()), + PDF_LABEL_APP_BOARD => Some(metadata.app_board_label.as_str()), + PDF_LABEL_APP_BOARDS => Some(metadata.app_board_count_label.as_str()), + PDF_LABEL_EXPORT_BOARD => Some(metadata.export_board_label.as_str()), + PDF_LABEL_EXPORT_BOARDS => Some(metadata.export_board_count_label.as_str()), + PDF_LABEL_PAGE => Some(metadata.board_page_label.as_str()), + PDF_LABEL_PAGES => Some(metadata.board_page_count_label.as_str()), + PDF_LABEL_DOCUMENT_PAGE => Some(metadata.document_page_label.as_str()), + PDF_LABEL_DOCUMENT_PAGES => Some(metadata.document_page_count_label.as_str()), + PDF_LABEL_BOARD_NAME => Some(metadata.board_name.as_str()), + PDF_LABEL_PAGE_NAME => Some(metadata.page_name_label.as_str()), _ => None, } } diff --git a/src/capture/portal.rs b/src/capture/portal.rs index 72f947b2..3118152d 100644 --- a/src/capture/portal.rs +++ b/src/capture/portal.rs @@ -38,7 +38,7 @@ trait Request { /// /// # Signal Arguments /// * `response` - Response code (0 = success, 1 = cancelled, 2 = other error) - /// * `results` - Dictionary containing the results (e.g., "uri" key) + /// * `results` - Dictionary containing the screenshot URI result key #[zbus(signal)] fn response(&self, response: u32, results: HashMap) -> zbus::Result<()>; } @@ -48,6 +48,9 @@ struct PortalAttempt { options: HashMap>, } +const PORTAL_RESULT_URI_KEY: &str = "uri"; +const PORTAL_OPTION_INTERACTIVE_KEY: &str = "interactive"; + /// Capture a screenshot using xdg-desktop-portal. /// /// This function communicates with the desktop portal via D-Bus to capture @@ -168,8 +171,10 @@ fn parse_response( match response_code { 0 => { // Success - extract URI from results. - let uri_value = results.get("uri").ok_or_else(|| { - CaptureError::InvalidResponse("No 'uri' field in response".to_string()) + let uri_value = results.get(PORTAL_RESULT_URI_KEY).ok_or_else(|| { + CaptureError::InvalidResponse(format!( + "No '{PORTAL_RESULT_URI_KEY}' field in response" + )) })?; // Extract string from OwnedValue. @@ -212,14 +217,14 @@ fn build_portal_options( match capture_type { CaptureType::FullScreen => { - options.insert("interactive".to_string(), false.into()); + options.insert(PORTAL_OPTION_INTERACTIVE_KEY.to_string(), false.into()); } CaptureType::ActiveWindow => { - options.insert("interactive".to_string(), true.into()); + options.insert(PORTAL_OPTION_INTERACTIVE_KEY.to_string(), true.into()); } CaptureType::Selection { .. } => { // Interactive mode for selection. - options.insert("interactive".to_string(), true.into()); + options.insert(PORTAL_OPTION_INTERACTIVE_KEY.to_string(), true.into()); } } @@ -229,7 +234,7 @@ fn build_portal_options( /// Active-window capture options (user picks window interactively). fn build_active_window_interactive_options() -> HashMap> { let mut options = HashMap::new(); - options.insert("interactive".to_string(), true.into()); + options.insert(PORTAL_OPTION_INTERACTIVE_KEY.to_string(), true.into()); options } @@ -255,7 +260,7 @@ mod tests { // Full screen should be non-interactive. assert_eq!( - options.get("interactive"), + options.get(PORTAL_OPTION_INTERACTIVE_KEY), Some(&zbus::zvariant::Value::from(false)) ); } @@ -271,7 +276,7 @@ mod tests { // Selection should be interactive. assert_eq!( - options.get("interactive"), + options.get(PORTAL_OPTION_INTERACTIVE_KEY), Some(&zbus::zvariant::Value::from(true)) ); } @@ -282,10 +287,10 @@ mod tests { assert_eq!(attempts.len(), 1); assert_eq!(attempts[0].label, "active-window-interactive"); assert_eq!( - attempts[0].options.get("interactive"), + attempts[0].options.get(PORTAL_OPTION_INTERACTIVE_KEY), Some(&zbus::zvariant::Value::from(true)) ); - assert!(!attempts[0].options.contains_key("window")); + assert_eq!(attempts[0].options.len(), 1); } #[test] @@ -293,7 +298,7 @@ mod tests { let options = build_active_window_interactive_options(); assert_eq!( - options.get("interactive"), + options.get(PORTAL_OPTION_INTERACTIVE_KEY), Some(&zbus::zvariant::Value::from(true)) ); } diff --git a/src/config/keybindings/defaults/board.rs b/src/config/keybindings/defaults/board.rs index 246efe0b..b5d86db8 100644 --- a/src/config/keybindings/defaults/board.rs +++ b/src/config/keybindings/defaults/board.rs @@ -1,20 +1,18 @@ use std::env; +use crate::env_vars::DESKTOP_ENV_KEYS; + fn use_ubuntu_page_navigation_defaults() -> bool { if !cfg!(target_os = "linux") { return false; } - [ - "XDG_CURRENT_DESKTOP", - "XDG_SESSION_DESKTOP", - "DESKTOP_SESSION", - ] - .iter() - .filter_map(|key| env::var(key).ok()) - .any(|value| { - let value = value.to_lowercase(); - value.contains("ubuntu") || value.contains("gnome") - }) + DESKTOP_ENV_KEYS + .iter() + .filter_map(|key| env::var(key).ok()) + .any(|value| { + let value = value.to_lowercase(); + value.contains("ubuntu") || value.contains("gnome") + }) } pub(crate) fn default_toggle_whiteboard() -> Vec { diff --git a/src/config/mod.rs b/src/config/mod.rs index d543f600..7ac284d2 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -38,16 +38,19 @@ pub use keybindings::{Action, KeyBinding, KeybindingsConfig}; pub use types::{ ArrowConfig, BoardBackgroundConfig, BoardColorConfig, BoardConfig, BoardItemConfig, BoardsConfig, CaptureConfig, ClickHighlightConfig, DragButtonConfig, DrawingConfig, - ExportConfig, HelpOverlayStyle, HistoryConfig, MouseDragToolsConfig, - PDF_LABEL_DEFAULT_TEMPLATE, PDF_LABEL_PLACEHOLDERS, PRESET_SLOTS_MAX, PRESET_SLOTS_MIN, - PdfExportConfig, PdfFitMode, PdfLabelConfig, PdfLabelContentMode, PdfLabelPosition, - PdfOrientation, PdfPageSize, PdfTransparentBackground, PerformanceConfig, PresenterModeConfig, - PresenterToolBehavior, PresetSlotsConfig, PresetToolSettingConfig, PresetToolStatesConfig, - RenderColorMappingConfig, RenderProfileConfig, RenderProfileExportMode, RenderProfilesConfig, - ResolvedToolbarItems, SessionCompression, SessionConfig, SessionStorageMode, StatusBarStyle, - ToolPresetConfig, ToolbarConfig, ToolbarGroupId, ToolbarItemCategory, ToolbarItemDefinition, - ToolbarItemId, ToolbarItemOrderConfig, ToolbarItemOrderGroup, ToolbarItemSurface, - ToolbarItemsConfig, ToolbarLayoutMode, ToolbarModeOverride, ToolbarModeOverrides, UiConfig, + ExportConfig, HelpOverlayStyle, HistoryConfig, MouseDragToolsConfig, PDF_LABEL_APP_BOARD, + PDF_LABEL_APP_BOARDS, PDF_LABEL_BOARD_NAME, PDF_LABEL_DEFAULT_TEMPLATE, + PDF_LABEL_DOCUMENT_PAGE, PDF_LABEL_DOCUMENT_PAGES, PDF_LABEL_EXPORT_BOARD, + PDF_LABEL_EXPORT_BOARDS, PDF_LABEL_PAGE, PDF_LABEL_PAGE_NAME, PDF_LABEL_PAGES, + PDF_LABEL_PLACEHOLDERS, PRESET_SLOTS_MAX, PRESET_SLOTS_MIN, PdfExportConfig, PdfFitMode, + PdfLabelConfig, PdfLabelContentMode, PdfLabelPosition, PdfOrientation, PdfPageSize, + PdfTransparentBackground, PerformanceConfig, PresenterModeConfig, PresenterToolBehavior, + PresetSlotsConfig, PresetToolSettingConfig, PresetToolStatesConfig, RenderColorMappingConfig, + RenderProfileConfig, RenderProfileExportMode, RenderProfilesConfig, ResolvedToolbarItems, + SessionCompression, SessionConfig, SessionStorageMode, StatusBarStyle, ToolPresetConfig, + ToolbarConfig, ToolbarGroupId, ToolbarItemCategory, ToolbarItemDefinition, ToolbarItemId, + ToolbarItemOrderConfig, ToolbarItemOrderGroup, ToolbarItemSurface, ToolbarItemsConfig, + ToolbarLayoutMode, ToolbarModeOverride, ToolbarModeOverrides, UiConfig, toolbar_item_definitions, toolbar_item_ids, toolbar_item_order_group, validate_pdf_label_template, }; diff --git a/src/config/test_helpers.rs b/src/config/test_helpers.rs index 717b2f6c..c59f8489 100644 --- a/src/config/test_helpers.rs +++ b/src/config/test_helpers.rs @@ -1,5 +1,6 @@ use std::path::Path; +use crate::env_vars::XDG_CONFIG_HOME_ENV; use crate::test_temp::TempDir; pub(crate) fn with_temp_config_home(f: F) -> T @@ -8,15 +9,15 @@ where { let _guard = crate::test_env::lock(); let temp = TempDir::new().expect("tempdir"); - let original = std::env::var_os("XDG_CONFIG_HOME"); + let original = std::env::var_os(XDG_CONFIG_HOME_ENV); // SAFETY: tests serialize process environment access and restore the previous value. unsafe { - std::env::set_var("XDG_CONFIG_HOME", temp.path()); + std::env::set_var(XDG_CONFIG_HOME_ENV, temp.path()); } let result = f(temp.path()); match original { - Some(value) => unsafe { std::env::set_var("XDG_CONFIG_HOME", value) }, - None => unsafe { std::env::remove_var("XDG_CONFIG_HOME") }, + Some(value) => unsafe { std::env::set_var(XDG_CONFIG_HOME_ENV, value) }, + None => unsafe { std::env::remove_var(XDG_CONFIG_HOME_ENV) }, } result } diff --git a/src/config/tests/load.rs b/src/config/tests/load.rs index 2a0c305d..52b13e80 100644 --- a/src/config/tests/load.rs +++ b/src/config/tests/load.rs @@ -192,17 +192,13 @@ fn effective_drag_tools_preserve_explicit_builtin_left_mapping() { #[test] fn ui_defaults_follow_desktop_for_xdg_focus_loss() { - let desktop_like_gnome = [ - "XDG_CURRENT_DESKTOP", - "XDG_SESSION_DESKTOP", - "DESKTOP_SESSION", - ] - .iter() - .filter_map(|key| std::env::var(key).ok()) - .any(|value| { - let value = value.to_lowercase(); - value.contains("ubuntu") || value.contains("gnome") - }); + let desktop_like_gnome = crate::env_vars::DESKTOP_ENV_KEYS + .iter() + .filter_map(|key| std::env::var(key).ok()) + .any(|value| { + let value = value.to_lowercase(); + value.contains("ubuntu") || value.contains("gnome") + }); let expected = if cfg!(target_os = "linux") && desktop_like_gnome { XdgFocusLossBehavior::Stay } else { diff --git a/src/config/types/export.rs b/src/config/types/export.rs index fe175532..432b93bf 100644 --- a/src/config/types/export.rs +++ b/src/config/types/export.rs @@ -4,17 +4,27 @@ use super::capture::CaptureConfig; pub const PDF_LABEL_DEFAULT_TEMPLATE: &str = "{board_name} - {page_name} ({document_page}/{document_pages})"; +pub const PDF_LABEL_APP_BOARD: &str = "app_board"; +pub const PDF_LABEL_APP_BOARDS: &str = "app_boards"; +pub const PDF_LABEL_EXPORT_BOARD: &str = "export_board"; +pub const PDF_LABEL_EXPORT_BOARDS: &str = "export_boards"; +pub const PDF_LABEL_PAGE: &str = "page"; +pub const PDF_LABEL_PAGES: &str = "pages"; +pub const PDF_LABEL_DOCUMENT_PAGE: &str = "document_page"; +pub const PDF_LABEL_DOCUMENT_PAGES: &str = "document_pages"; +pub const PDF_LABEL_BOARD_NAME: &str = "board_name"; +pub const PDF_LABEL_PAGE_NAME: &str = "page_name"; pub const PDF_LABEL_PLACEHOLDERS: &[&str] = &[ - "app_board", - "app_boards", - "export_board", - "export_boards", - "page", - "pages", - "document_page", - "document_pages", - "board_name", - "page_name", + PDF_LABEL_APP_BOARD, + PDF_LABEL_APP_BOARDS, + PDF_LABEL_EXPORT_BOARD, + PDF_LABEL_EXPORT_BOARDS, + PDF_LABEL_PAGE, + PDF_LABEL_PAGES, + PDF_LABEL_DOCUMENT_PAGE, + PDF_LABEL_DOCUMENT_PAGES, + PDF_LABEL_BOARD_NAME, + PDF_LABEL_PAGE_NAME, ]; #[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))] diff --git a/src/config/types/mod.rs b/src/config/types/mod.rs index 6372dc17..596ac7a1 100644 --- a/src/config/types/mod.rs +++ b/src/config/types/mod.rs @@ -29,9 +29,12 @@ pub use click_highlight::ClickHighlightConfig; pub use context_menu::ContextMenuUiConfig; pub use drawing::{DragButtonConfig, DrawingConfig, MouseDragToolsConfig}; pub use export::{ - ExportConfig, PDF_LABEL_DEFAULT_TEMPLATE, PDF_LABEL_PLACEHOLDERS, PdfExportConfig, PdfFitMode, - PdfLabelConfig, PdfLabelContentMode, PdfLabelPosition, PdfOrientation, PdfPageSize, - PdfTransparentBackground, validate_pdf_label_template, + ExportConfig, PDF_LABEL_APP_BOARD, PDF_LABEL_APP_BOARDS, PDF_LABEL_BOARD_NAME, + PDF_LABEL_DEFAULT_TEMPLATE, PDF_LABEL_DOCUMENT_PAGE, PDF_LABEL_DOCUMENT_PAGES, + PDF_LABEL_EXPORT_BOARD, PDF_LABEL_EXPORT_BOARDS, PDF_LABEL_PAGE, PDF_LABEL_PAGE_NAME, + PDF_LABEL_PAGES, PDF_LABEL_PLACEHOLDERS, PdfExportConfig, PdfFitMode, PdfLabelConfig, + PdfLabelContentMode, PdfLabelPosition, PdfOrientation, PdfPageSize, PdfTransparentBackground, + validate_pdf_label_template, }; pub use help_overlay::HelpOverlayStyle; pub use history::HistoryConfig; diff --git a/src/config/types/ui.rs b/src/config/types/ui.rs index 9f2cc507..942d00e2 100644 --- a/src/config/types/ui.rs +++ b/src/config/types/ui.rs @@ -1,4 +1,5 @@ use crate::config::enums::{RadialMenuMouseBinding, StatusPosition, XdgFocusLossBehavior}; +use crate::env_vars::DESKTOP_ENV_KEYS; use serde::{Deserialize, Serialize}; use std::env; @@ -167,17 +168,13 @@ fn use_gnome_fallback_defaults() -> bool { if !cfg!(target_os = "linux") { return false; } - [ - "XDG_CURRENT_DESKTOP", - "XDG_SESSION_DESKTOP", - "DESKTOP_SESSION", - ] - .iter() - .filter_map(|key| env::var(key).ok()) - .any(|value| { - let value = value.to_lowercase(); - value.contains("ubuntu") || value.contains("gnome") - }) + DESKTOP_ENV_KEYS + .iter() + .filter_map(|key| env::var(key).ok()) + .any(|value| { + let value = value.to_lowercase(); + value.contains("ubuntu") || value.contains("gnome") + }) } fn default_help_overlay_context_filter() -> bool { diff --git a/src/daemon/control.rs b/src/daemon/control.rs index bbb0e917..1e81270d 100644 --- a/src/daemon/control.rs +++ b/src/daemon/control.rs @@ -10,6 +10,8 @@ use std::path::{Path, PathBuf}; use std::thread; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +#[cfg(test)] +use crate::env_vars::XDG_RUNTIME_DIR_ENV; use crate::paths::{daemon_command_dir, daemon_command_file, daemon_lock_file, daemon_pid_file}; use crate::session::try_lock_exclusive; use crate::tray_action::TrayAction; @@ -887,9 +889,9 @@ mod tests { .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); let tmp = crate::test_temp::tempdir().unwrap(); - let prev = env::var_os("XDG_RUNTIME_DIR"); + let prev = env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - env::set_var("XDG_RUNTIME_DIR", tmp.path()); + env::set_var(XDG_RUNTIME_DIR_ENV, tmp.path()); } write_daemon_pid_file(1234, "daemon-token").unwrap(); @@ -900,8 +902,8 @@ mod tests { ); match prev { - Some(value) => unsafe { env::set_var("XDG_RUNTIME_DIR", value) }, - None => unsafe { env::remove_var("XDG_RUNTIME_DIR") }, + Some(value) => unsafe { env::set_var(XDG_RUNTIME_DIR_ENV, value) }, + None => unsafe { env::remove_var(XDG_RUNTIME_DIR_ENV) }, } } @@ -911,9 +913,9 @@ mod tests { .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); let tmp = crate::test_temp::tempdir().unwrap(); - let prev = env::var_os("XDG_RUNTIME_DIR"); + let prev = env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - env::set_var("XDG_RUNTIME_DIR", tmp.path()); + env::set_var(XDG_RUNTIME_DIR_ENV, tmp.path()); } let runtime = DaemonRuntimeInfo { @@ -936,8 +938,8 @@ mod tests { assert!(!daemon_command_dir().exists()); match prev { - Some(value) => unsafe { env::set_var("XDG_RUNTIME_DIR", value) }, - None => unsafe { env::remove_var("XDG_RUNTIME_DIR") }, + Some(value) => unsafe { env::set_var(XDG_RUNTIME_DIR_ENV, value) }, + None => unsafe { env::remove_var(XDG_RUNTIME_DIR_ENV) }, } } @@ -947,9 +949,9 @@ mod tests { .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); let tmp = crate::test_temp::tempdir().unwrap(); - let prev = env::var_os("XDG_RUNTIME_DIR"); + let prev = env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - env::set_var("XDG_RUNTIME_DIR", tmp.path()); + env::set_var(XDG_RUNTIME_DIR_ENV, tmp.path()); } let current = DaemonRuntimeInfo { @@ -975,8 +977,8 @@ mod tests { assert!(daemon_command_dir().exists()); match prev { - Some(value) => unsafe { env::set_var("XDG_RUNTIME_DIR", value) }, - None => unsafe { env::remove_var("XDG_RUNTIME_DIR") }, + Some(value) => unsafe { env::set_var(XDG_RUNTIME_DIR_ENV, value) }, + None => unsafe { env::remove_var(XDG_RUNTIME_DIR_ENV) }, } } @@ -986,9 +988,9 @@ mod tests { .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); let tmp = crate::test_temp::tempdir().unwrap(); - let prev = env::var_os("XDG_RUNTIME_DIR"); + let prev = env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - env::set_var("XDG_RUNTIME_DIR", tmp.path()); + env::set_var(XDG_RUNTIME_DIR_ENV, tmp.path()); } let request = DaemonToggleRequest { @@ -1024,8 +1026,8 @@ mod tests { assert!(batch.commands.is_empty()); match prev { - Some(value) => unsafe { env::set_var("XDG_RUNTIME_DIR", value) }, - None => unsafe { env::remove_var("XDG_RUNTIME_DIR") }, + Some(value) => unsafe { env::set_var(XDG_RUNTIME_DIR_ENV, value) }, + None => unsafe { env::remove_var(XDG_RUNTIME_DIR_ENV) }, } } @@ -1035,9 +1037,9 @@ mod tests { .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); let tmp = crate::test_temp::tempdir().unwrap(); - let prev = env::var_os("XDG_RUNTIME_DIR"); + let prev = env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - env::set_var("XDG_RUNTIME_DIR", tmp.path()); + env::set_var(XDG_RUNTIME_DIR_ENV, tmp.path()); } write_daemon_toggle_request( @@ -1067,8 +1069,8 @@ mod tests { assert!(!entries.iter().any(|name| name.ends_with(".tmp"))); match prev { - Some(value) => unsafe { env::set_var("XDG_RUNTIME_DIR", value) }, - None => unsafe { env::remove_var("XDG_RUNTIME_DIR") }, + Some(value) => unsafe { env::set_var(XDG_RUNTIME_DIR_ENV, value) }, + None => unsafe { env::remove_var(XDG_RUNTIME_DIR_ENV) }, } } @@ -1078,9 +1080,9 @@ mod tests { .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); let tmp = crate::test_temp::tempdir().unwrap(); - let prev = env::var_os("XDG_RUNTIME_DIR"); + let prev = env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - env::set_var("XDG_RUNTIME_DIR", tmp.path()); + env::set_var(XDG_RUNTIME_DIR_ENV, tmp.path()); } let first = DaemonToggleRequest { @@ -1110,8 +1112,8 @@ mod tests { ); match prev { - Some(value) => unsafe { env::set_var("XDG_RUNTIME_DIR", value) }, - None => unsafe { env::remove_var("XDG_RUNTIME_DIR") }, + Some(value) => unsafe { env::set_var(XDG_RUNTIME_DIR_ENV, value) }, + None => unsafe { env::remove_var(XDG_RUNTIME_DIR_ENV) }, } } @@ -1121,9 +1123,9 @@ mod tests { .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); let tmp = crate::test_temp::tempdir().unwrap(); - let prev = env::var_os("XDG_RUNTIME_DIR"); + let prev = env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - env::set_var("XDG_RUNTIME_DIR", tmp.path()); + env::set_var(XDG_RUNTIME_DIR_ENV, tmp.path()); } write_daemon_toggle_request( @@ -1143,8 +1145,8 @@ mod tests { ); match prev { - Some(value) => unsafe { env::set_var("XDG_RUNTIME_DIR", value) }, - None => unsafe { env::remove_var("XDG_RUNTIME_DIR") }, + Some(value) => unsafe { env::set_var(XDG_RUNTIME_DIR_ENV, value) }, + None => unsafe { env::remove_var(XDG_RUNTIME_DIR_ENV) }, } } @@ -1154,9 +1156,9 @@ mod tests { .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); let tmp = crate::test_temp::tempdir().unwrap(); - let prev = env::var_os("XDG_RUNTIME_DIR"); + let prev = env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - env::set_var("XDG_RUNTIME_DIR", tmp.path()); + env::set_var(XDG_RUNTIME_DIR_ENV, tmp.path()); } let payload = serde_json::to_vec(&DaemonToggleEnvelope { @@ -1177,8 +1179,8 @@ mod tests { assert!(batch.commands.is_empty()); match prev { - Some(value) => unsafe { env::set_var("XDG_RUNTIME_DIR", value) }, - None => unsafe { env::remove_var("XDG_RUNTIME_DIR") }, + Some(value) => unsafe { env::set_var(XDG_RUNTIME_DIR_ENV, value) }, + None => unsafe { env::remove_var(XDG_RUNTIME_DIR_ENV) }, } } @@ -1188,9 +1190,9 @@ mod tests { .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); let tmp = crate::test_temp::tempdir().unwrap(); - let prev = env::var_os("XDG_RUNTIME_DIR"); + let prev = env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - env::set_var("XDG_RUNTIME_DIR", tmp.path()); + env::set_var(XDG_RUNTIME_DIR_ENV, tmp.path()); } let payload = serde_json::to_vec(&DaemonToggleEnvelope { @@ -1211,8 +1213,8 @@ mod tests { assert!(batch.commands.is_empty()); match prev { - Some(value) => unsafe { env::set_var("XDG_RUNTIME_DIR", value) }, - None => unsafe { env::remove_var("XDG_RUNTIME_DIR") }, + Some(value) => unsafe { env::set_var(XDG_RUNTIME_DIR_ENV, value) }, + None => unsafe { env::remove_var(XDG_RUNTIME_DIR_ENV) }, } } } diff --git a/src/daemon/core.rs b/src/daemon/core.rs index 878f2340..9ab9954b 100644 --- a/src/daemon/core.rs +++ b/src/daemon/core.rs @@ -14,6 +14,7 @@ use std::time::Duration; #[cfg(test)] use crate::SESSION_OVERRIDE_FOLLOW_CONFIG; +use crate::env_vars::NO_TRAY_ENV; use crate::paths::daemon_lock_file; use crate::session::try_lock_exclusive; use crate::tray_action::TrayAction; @@ -460,7 +461,7 @@ impl Daemon { Err(err) => { warn!("System tray unavailable: {}", err); warn!( - "Continuing without system tray; use --no-tray or WAYSCRIBER_NO_TRAY=1 to silence this warning" + "Continuing without system tray; use --no-tray or {NO_TRAY_ENV}=1 to silence this warning" ); } } diff --git a/src/daemon/global_shortcuts.rs b/src/daemon/global_shortcuts.rs index 485911ba..e011e352 100644 --- a/src/daemon/global_shortcuts.rs +++ b/src/daemon/global_shortcuts.rs @@ -31,6 +31,28 @@ const TOGGLE_SHORTCUT_ID: &str = "toggle-overlay"; const TOGGLE_SHORTCUT_DESCRIPTION: &str = "Toggle Wayscriber overlay"; #[cfg(feature = "portal")] const PORTAL_REQUEST_POLL_INTERVAL_MS: u64 = 100; +#[cfg(feature = "portal")] +const PORTAL_KEY_ACTIVATION_TOKEN: &str = "activation_token"; +#[cfg(feature = "portal")] +const PORTAL_KEY_ACTIVATION_TOKEN_KEBAB: &str = "activation-token"; +#[cfg(feature = "portal")] +const PORTAL_KEY_ACTIVATION_TOKEN_CAMEL: &str = "activationToken"; +#[cfg(feature = "portal")] +const PORTAL_KEY_APP_ID: &str = "app_id"; +#[cfg(feature = "portal")] +const PORTAL_KEY_DESKTOP_STARTUP_ID: &str = "desktop-startup-id"; +#[cfg(feature = "portal")] +const PORTAL_KEY_DESCRIPTION: &str = "description"; +#[cfg(feature = "portal")] +const PORTAL_KEY_HANDLE_TOKEN: &str = "handle_token"; +#[cfg(feature = "portal")] +const PORTAL_KEY_PREFERRED_TRIGGER: &str = "preferred_trigger"; +#[cfg(feature = "portal")] +const PORTAL_KEY_SESSION_HANDLE: &str = "session_handle"; +#[cfg(feature = "portal")] +const PORTAL_KEY_SESSION_HANDLE_TOKEN: &str = "session_handle_token"; +#[cfg(feature = "portal")] +const PORTAL_KEY_STARTUP_ID: &str = "startup_id"; pub(super) fn start_global_shortcuts_listener( toggle_flag: Arc, @@ -279,14 +301,17 @@ async fn create_global_shortcuts_session( ) -> Result { let mut options: HashMap> = HashMap::new(); options.insert( - "handle_token".to_string(), + PORTAL_KEY_HANDLE_TOKEN.to_string(), Value::from(make_handle_token("wayscribergsreq")), ); options.insert( - "session_handle_token".to_string(), + PORTAL_KEY_SESSION_HANDLE_TOKEN.to_string(), Value::from(make_handle_token("wayscribergssess")), ); - options.insert("app_id".to_string(), Value::from(portal_app_id.to_string())); + options.insert( + PORTAL_KEY_APP_ID.to_string(), + Value::from(portal_app_id.to_string()), + ); let request_path = proxy .create_session(options) @@ -303,8 +328,8 @@ async fn create_global_shortcuts_session( } let session_handle_value = results - .get("session_handle") - .ok_or_else(|| anyhow!("CreateSession response missing session_handle"))?; + .get(PORTAL_KEY_SESSION_HANDLE) + .ok_or_else(|| anyhow!("CreateSession response missing {PORTAL_KEY_SESSION_HANDLE}"))?; parse_object_path(session_handle_value) .context("failed to parse session_handle from CreateSession response") } @@ -319,11 +344,11 @@ async fn bind_toggle_shortcut( ) -> Result<()> { let mut shortcut_options: HashMap> = HashMap::new(); shortcut_options.insert( - "description".to_string(), + PORTAL_KEY_DESCRIPTION.to_string(), Value::from(TOGGLE_SHORTCUT_DESCRIPTION.to_string()), ); shortcut_options.insert( - "preferred_trigger".to_string(), + PORTAL_KEY_PREFERRED_TRIGGER.to_string(), Value::from(preferred_trigger.to_string()), ); @@ -331,7 +356,7 @@ async fn bind_toggle_shortcut( let mut bind_options: HashMap> = HashMap::new(); bind_options.insert( - "handle_token".to_string(), + PORTAL_KEY_HANDLE_TOKEN.to_string(), Value::from(make_handle_token("wayscribergsbind")), ); @@ -421,11 +446,11 @@ fn parse_object_path(value: &OwnedValue) -> Result { #[cfg(feature = "portal")] fn extract_activation_token(options: &HashMap) -> Option { const TOKEN_KEYS: [&str; 5] = [ - "activation_token", - "activation-token", - "activationToken", - "startup_id", - "desktop-startup-id", + PORTAL_KEY_ACTIVATION_TOKEN, + PORTAL_KEY_ACTIVATION_TOKEN_KEBAB, + PORTAL_KEY_ACTIVATION_TOKEN_CAMEL, + PORTAL_KEY_STARTUP_ID, + PORTAL_KEY_DESKTOP_STARTUP_ID, ]; for key in TOKEN_KEYS { diff --git a/src/daemon/overlay/spawn.rs b/src/daemon/overlay/spawn.rs index f522dc11..e30b70b7 100644 --- a/src/daemon/overlay/spawn.rs +++ b/src/daemon/overlay/spawn.rs @@ -7,6 +7,8 @@ use std::process::{Command, Stdio}; use std::sync::atomic::Ordering; use std::time::{Duration, Instant}; +use crate::env_vars::{DESKTOP_STARTUP_ID_ENV, NO_DETACH_ENV, PATH_ENV, XDG_ACTIVATION_TOKEN_ENV}; + use super::super::core::Daemon; #[cfg(feature = "tray")] use super::super::types::OverlaySpawnErrorInfo; @@ -84,7 +86,7 @@ impl Daemon { ); } } else { - warn!("Failed to resolve current executable; falling back to argv0/PATH"); + warn!("Failed to resolve current executable; falling back to argv0/{PATH_ENV}"); } if let Some(arg0) = env::args_os().next() { @@ -107,7 +109,7 @@ impl Daemon { &mut candidates, &mut seen, OsString::from("wayscriber"), - "PATH", + PATH_ENV, ); candidates @@ -138,13 +140,13 @@ impl Daemon { } // Overlay children launched by daemon are already backgrounded and tracked. // Prevent `--active` from spawning another detached grandchild process. - command.env("WAYSCRIBER_NO_DETACH", "1"); + command.env(NO_DETACH_ENV, "1"); if let Some(token) = self.pending_activation_token.as_deref() { - command.env("XDG_ACTIVATION_TOKEN", token); - command.env("DESKTOP_STARTUP_ID", token); + command.env(XDG_ACTIVATION_TOKEN_ENV, token); + command.env(DESKTOP_STARTUP_ID_ENV, token); } else { - command.env_remove("XDG_ACTIVATION_TOKEN"); - command.env_remove("DESKTOP_STARTUP_ID"); + command.env_remove(XDG_ACTIVATION_TOKEN_ENV); + command.env_remove(DESKTOP_STARTUP_ID_ENV); } if let Some(request_override) = request.and_then(|request| request.session_resume_override()) @@ -215,7 +217,7 @@ impl Daemon { self.pending_activation_token = None; warn!("Overlay spawn attempts failed: {}", failures.join("; ")); Err(anyhow!( - "Unable to launch overlay process (tried current_exe/argv0/PATH)" + "Unable to launch overlay process (tried current_exe/argv0/{PATH_ENV})" )) } } @@ -384,7 +386,7 @@ mod tests { &mut candidates, &mut seen, OsString::from("wayscriber"), - "PATH", + super::PATH_ENV, ); Daemon::push_spawn_candidate( &mut candidates, @@ -394,6 +396,6 @@ mod tests { ); assert_eq!(candidates.len(), 1); - assert_eq!(candidates[0].source, "PATH"); + assert_eq!(candidates[0].source, super::PATH_ENV); } } diff --git a/src/daemon/tray/helpers.rs b/src/daemon/tray/helpers.rs index a72522af..2ceebd67 100644 --- a/src/daemon/tray/helpers.rs +++ b/src/daemon/tray/helpers.rs @@ -1,6 +1,8 @@ #[cfg(feature = "tray")] use crate::config::Config; #[cfg(feature = "tray")] +use crate::env_vars::CONFIGURATOR_ENV; +#[cfg(feature = "tray")] use crate::paths::log_dir; #[cfg(feature = "tray")] use crate::session::{clear_session, options_from_config}; @@ -45,7 +47,7 @@ impl WayscriberTray { let not_found = err.kind() == ErrorKind::NotFound; let opened_config = if not_found { error!( - "Configurator not found (looked for '{}'). Install 'wayscriber-configurator' (Arch: yay -S wayscriber-configurator; deb/rpm users: grab the wayscriber-configurator package from the release page) or set WAYSCRIBER_CONFIGURATOR to its path.", + "Configurator not found (looked for '{}'). Install 'wayscriber-configurator' (Arch: yay -S wayscriber-configurator; deb/rpm users: grab the wayscriber-configurator package from the release page) or set {CONFIGURATOR_ENV} to its path.", self.configurator_binary ); self.open_config_file() @@ -54,9 +56,7 @@ impl WayscriberTray { "Failed to launch wayscriber-configurator using '{}': {}", self.configurator_binary, err ); - error!( - "Set WAYSCRIBER_CONFIGURATOR to override the executable path if needed." - ); + error!("Set {CONFIGURATOR_ENV} to override the executable path if needed."); false }; #[cfg(feature = "dbus")] diff --git a/src/daemon/tray/ksni.rs b/src/daemon/tray/ksni.rs index d6791ba5..4e35b1b0 100644 --- a/src/daemon/tray/ksni.rs +++ b/src/daemon/tray/ksni.rs @@ -9,6 +9,11 @@ use super::shortcut_hint_io::configured_toggle_shortcut_hint; #[cfg(feature = "tray")] use crate::config::{Action, action_label}; #[cfg(feature = "tray")] +use crate::env_vars::{ + DESKTOP_SESSION_ENV, ICON_THEME_PATH_ENV, TRAY_FORCE_PIXMAP_ENV, XDG_CURRENT_DESKTOP_ENV, + XDG_SESSION_DESKTOP_ENV, +}; +#[cfg(feature = "tray")] use crate::label_format::format_binding_label; #[cfg(feature = "tray")] use crate::tray_action::TrayAction; @@ -291,16 +296,16 @@ impl ksni::Tray for WayscriberTray { #[cfg(feature = "tray")] fn tray_theme_icons_enabled() -> bool { - if env::var_os("WAYSCRIBER_TRAY_FORCE_PIXMAP").is_some() { + if env::var_os(TRAY_FORCE_PIXMAP_ENV).is_some() { return false; } - let desktop_env = env::var("XDG_CURRENT_DESKTOP") + let desktop_env = env::var(XDG_CURRENT_DESKTOP_ENV) .unwrap_or_default() .to_lowercase(); - let session_env = env::var("XDG_SESSION_DESKTOP") + let session_env = env::var(XDG_SESSION_DESKTOP_ENV) .unwrap_or_default() .to_lowercase(); - let desktop_session = env::var("DESKTOP_SESSION") + let desktop_session = env::var(DESKTOP_SESSION_ENV) .unwrap_or_default() .to_lowercase(); tray_theme_icons_supported(&desktop_env, &session_env, &desktop_session) @@ -317,7 +322,7 @@ fn menu_icon_name(name: &str, use_theme_icons: bool) -> String { #[cfg(feature = "tray")] fn resolve_icon_theme_path() -> String { - if let Ok(value) = env::var("WAYSCRIBER_ICON_THEME_PATH") { + if let Ok(value) = env::var(ICON_THEME_PATH_ENV) { return value; } installed_icon_theme_path() diff --git a/src/daemon/tray/runtime.rs b/src/daemon/tray/runtime.rs index 0bcaf758..8039d2be 100644 --- a/src/daemon/tray/runtime.rs +++ b/src/daemon/tray/runtime.rs @@ -25,9 +25,15 @@ use super::super::types::TrayStatusShared; use super::WayscriberTray; #[cfg(feature = "tray")] use crate::config::Config; +#[cfg(feature = "tray")] +use crate::env_vars::CONFIGURATOR_ENV; #[cfg(feature = "tray")] const TRAY_START_TIMEOUT: Duration = Duration::from_secs(5); +#[cfg(feature = "tray")] +const STATUS_NOTIFIER_WATCHER_BUS: &str = "org.kde.StatusNotifierWatcher"; +#[cfg(feature = "tray")] +const STATUS_NOTIFIER_WATCHER_PATH: &str = "/StatusNotifierWatcher"; #[cfg(feature = "tray")] fn load_session_resume_enabled_from_config() -> bool { @@ -88,8 +94,8 @@ pub(crate) fn start_system_tray( overlay_pid: Arc, tray_status: Arc, ) -> Result> { - let configurator_binary = std::env::var("WAYSCRIBER_CONFIGURATOR") - .unwrap_or_else(|_| "wayscriber-configurator".to_string()); + let configurator_binary = + std::env::var(CONFIGURATOR_ENV).unwrap_or_else(|_| "wayscriber-configurator".to_string()); let session_resume_enabled = load_session_resume_enabled_from_config(); let tray_quit_flag = quit_flag.clone(); @@ -216,9 +222,9 @@ async fn log_status_notifier_state() { let proxy = match Proxy::new( &conn, - "org.kde.StatusNotifierWatcher", - "/StatusNotifierWatcher", - "org.kde.StatusNotifierWatcher", + STATUS_NOTIFIER_WATCHER_BUS, + STATUS_NOTIFIER_WATCHER_PATH, + STATUS_NOTIFIER_WATCHER_BUS, ) .await { diff --git a/src/daemon/tray/shortcut_hint_io.rs b/src/daemon/tray/shortcut_hint_io.rs index fdfa10d6..40817518 100644 --- a/src/daemon/tray/shortcut_hint_io.rs +++ b/src/daemon/tray/shortcut_hint_io.rs @@ -1,4 +1,6 @@ #[cfg(feature = "tray")] +use crate::env_vars::{XDG_CURRENT_DESKTOP_ENV, XDG_SESSION_DESKTOP_ENV}; +#[cfg(feature = "tray")] use std::env; #[cfg(feature = "tray")] use std::process::Command; @@ -34,8 +36,8 @@ pub(super) fn configured_toggle_shortcut_hint() -> Option { #[cfg(feature = "tray")] fn current_desktop_is_gnome() -> bool { - let current = env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(); - let session = env::var("XDG_SESSION_DESKTOP").unwrap_or_default(); + let current = env::var(XDG_CURRENT_DESKTOP_ENV).unwrap_or_default(); + let session = env::var(XDG_SESSION_DESKTOP_ENV).unwrap_or_default(); is_gnome_desktop(¤t, &session) } diff --git a/src/env_vars.rs b/src/env_vars.rs new file mode 100644 index 00000000..6cd37950 --- /dev/null +++ b/src/env_vars.rs @@ -0,0 +1,51 @@ +#![allow(dead_code)] + +pub const APP_ID_ENV: &str = "WAYSCRIBER_APP_ID"; +pub const PORTAL_APP_ID_ENV: &str = "WAYSCRIBER_PORTAL_APP_ID"; +pub const PORTAL_SHORTCUT_ENV: &str = "WAYSCRIBER_PORTAL_SHORTCUT"; +pub const PORTAL_SHORTCUT_OPT_IN_ENV: &str = "WAYSCRIBER_ENABLE_PORTAL_SHORTCUTS"; + +pub const CONFIGURATOR_ENV: &str = "WAYSCRIBER_CONFIGURATOR"; +pub const BIN_ENV: &str = "WAYSCRIBER_BIN"; +pub const DETACHED_ENV: &str = "WAYSCRIBER_DETACHED"; +pub const NO_DETACH_ENV: &str = "WAYSCRIBER_NO_DETACH"; +pub const NO_TRAY_ENV: &str = "WAYSCRIBER_NO_TRAY"; +pub const CATALOG_HOOKS_TEST_ENV: &str = "WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS"; +pub const FORCE_INLINE_TOOLBARS_ENV: &str = "WAYSCRIBER_FORCE_INLINE_TOOLBARS"; +pub const DEBUG_DAMAGE_ENV: &str = "WAYSCRIBER_DEBUG_DAMAGE"; +pub const DEBUG_TOOLBAR_COLOR_ENV: &str = "WAYSCRIBER_DEBUG_TOOLBAR_COLOR"; +pub const DEBUG_TOOLBAR_DRAG_ENV: &str = "WAYSCRIBER_DEBUG_TOOLBAR_DRAG"; +pub const ICON_THEME_PATH_ENV: &str = "WAYSCRIBER_ICON_THEME_PATH"; +pub const LOG_FILE_ENV: &str = "WAYSCRIBER_LOG_FILE"; +pub const LOG_MAX_SIZE_ENV: &str = "WAYSCRIBER_LOG_MAX_SIZE_MB"; +pub const TOOLBAR_DRAG_HANDOFF_MS_ENV: &str = "WAYSCRIBER_TOOLBAR_DRAG_HANDOFF_MS"; +pub const TOOLBAR_DRAG_PREVIEW_ENV: &str = "WAYSCRIBER_TOOLBAR_DRAG_PREVIEW"; +pub const TOOLBAR_DRAG_THROTTLE_MS_ENV: &str = "WAYSCRIBER_TOOLBAR_DRAG_THROTTLE_MS"; +pub const TOOLBAR_POINTER_LOCK_ENV: &str = "WAYSCRIBER_TOOLBAR_POINTER_LOCK"; +pub const TRAY_FORCE_PIXMAP_ENV: &str = "WAYSCRIBER_TRAY_FORCE_PIXMAP"; +pub const XDG_FULLSCREEN_ENV: &str = "WAYSCRIBER_XDG_FULLSCREEN"; +pub const XDG_FULLSCREEN_FORCE_ENV: &str = "WAYSCRIBER_XDG_FULLSCREEN_FORCE"; +pub const XDG_OUTPUT_ENV: &str = "WAYSCRIBER_XDG_OUTPUT"; + +pub const HOME_ENV: &str = "HOME"; +pub const HYPRLAND_INSTANCE_SIGNATURE_ENV: &str = "HYPRLAND_INSTANCE_SIGNATURE"; +pub const USERPROFILE_ENV: &str = "USERPROFILE"; +pub const PATH_ENV: &str = "PATH"; +pub const RUST_LOG_ENV: &str = "RUST_LOG"; +pub const SWAYSOCK_ENV: &str = "SWAYSOCK"; +pub const WAYLAND_DISPLAY_ENV: &str = "WAYLAND_DISPLAY"; +pub const XDG_CURRENT_DESKTOP_ENV: &str = "XDG_CURRENT_DESKTOP"; +pub const XDG_SESSION_DESKTOP_ENV: &str = "XDG_SESSION_DESKTOP"; +pub const DESKTOP_SESSION_ENV: &str = "DESKTOP_SESSION"; +pub const XDG_ACTIVATION_TOKEN_ENV: &str = "XDG_ACTIVATION_TOKEN"; +pub const XDG_CONFIG_HOME_ENV: &str = "XDG_CONFIG_HOME"; +pub const XDG_DATA_HOME_ENV: &str = "XDG_DATA_HOME"; +pub const XDG_PICTURES_DIR_ENV: &str = "XDG_PICTURES_DIR"; +pub const XDG_RUNTIME_DIR_ENV: &str = "XDG_RUNTIME_DIR"; +pub const DESKTOP_STARTUP_ID_ENV: &str = "DESKTOP_STARTUP_ID"; + +pub const DESKTOP_ENV_KEYS: [&str; 3] = [ + XDG_CURRENT_DESKTOP_ENV, + XDG_SESSION_DESKTOP_ENV, + DESKTOP_SESSION_ENV, +]; diff --git a/src/input/state/core/utility/launcher.rs b/src/input/state/core/utility/launcher.rs index d7d15aac..aa5c0977 100644 --- a/src/input/state/core/utility/launcher.rs +++ b/src/input/state/core/utility/launcher.rs @@ -1,11 +1,12 @@ use super::super::base::{InputState, UiToastKind}; use crate::config::Config; +use crate::env_vars::CONFIGURATOR_ENV; use std::io::ErrorKind; use std::process::{Command, Stdio}; impl InputState { pub(crate) fn launch_configurator(&mut self) { - let binary = std::env::var("WAYSCRIBER_CONFIGURATOR") + let binary = std::env::var(CONFIGURATOR_ENV) .unwrap_or_else(|_| "wayscriber-configurator".to_string()); match Command::new(&binary) @@ -24,7 +25,7 @@ impl InputState { Err(err) => { if err.kind() == ErrorKind::NotFound { log::error!( - "Configurator not found (looked for '{binary}'). Install 'wayscriber-configurator' (Arch: yay -S wayscriber-configurator; deb/rpm users: grab the wayscriber-configurator package from the release page) or set WAYSCRIBER_CONFIGURATOR to its path." + "Configurator not found (looked for '{binary}'). Install 'wayscriber-configurator' (Arch: yay -S wayscriber-configurator; deb/rpm users: grab the wayscriber-configurator package from the release page) or set {CONFIGURATOR_ENV} to its path." ); if self.open_config_file_default() { log::info!( @@ -39,7 +40,7 @@ impl InputState { } else { log::error!("Failed to launch wayscriber-configurator using '{binary}': {err}"); log::error!( - "Set WAYSCRIBER_CONFIGURATOR to override the executable path if needed." + "Set {CONFIGURATOR_ENV} to override the executable path if needed." ); self.set_ui_toast( UiToastKind::Error, diff --git a/src/lib.rs b/src/lib.rs index 7570b978..fe48e936 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ pub mod canvas_export; pub mod capture; pub mod config; pub mod draw; +pub mod env_vars; #[cfg(feature = "portal")] pub(crate) mod file_uri; pub(crate) mod image_decode; diff --git a/src/logger/file.rs b/src/logger/file.rs index 266d0ac1..97150608 100644 --- a/src/logger/file.rs +++ b/src/logger/file.rs @@ -4,14 +4,14 @@ use std::fs; use std::io::{self, Write}; use std::path::{Path, PathBuf}; +use crate::env_vars::{LOG_FILE_ENV, LOG_MAX_SIZE_ENV}; use crate::{paths, time_utils}; const BYTES_PER_MB: u64 = 1024 * 1024; const DEFAULT_LOG_MAX_BYTES: u64 = 10 * BYTES_PER_MB; -const LOG_MAX_SIZE_ENV: &str = "WAYSCRIBER_LOG_MAX_SIZE_MB"; pub(super) fn resolve_log_target() -> LogFileTarget { - if let Ok(path) = env::var("WAYSCRIBER_LOG_FILE") + if let Ok(path) = env::var(LOG_FILE_ENV) && !path.trim().is_empty() { let trimmed = path.trim(); diff --git a/src/logger/filter.rs b/src/logger/filter.rs index e9b27509..48595c53 100644 --- a/src/logger/filter.rs +++ b/src/logger/filter.rs @@ -1,5 +1,6 @@ use std::env; +use crate::env_vars::RUST_LOG_ENV; use log::{Level, LevelFilter}; #[derive(Clone, Debug, PartialEq, Eq)] @@ -11,7 +12,7 @@ pub(super) struct LogFilter { impl LogFilter { pub(super) fn from_env() -> Self { - match env::var("RUST_LOG") { + match env::var(RUST_LOG_ENV) { Ok(value) => Self::parse(&value, LevelFilter::Off), Err(_) => Self::parse("info", LevelFilter::Info), } diff --git a/src/main.rs b/src/main.rs index 1d197dab..c34ac158 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ mod cli; mod config; mod daemon; mod draw; +mod env_vars; mod file_uri; mod image_decode; mod input; diff --git a/src/paths/mod.rs b/src/paths/mod.rs index 46ce73d7..a1bb94a7 100644 --- a/src/paths/mod.rs +++ b/src/paths/mod.rs @@ -1,17 +1,22 @@ use std::env; use std::path::PathBuf; +use crate::env_vars::{ + HOME_ENV, USERPROFILE_ENV, XDG_CONFIG_HOME_ENV, XDG_DATA_HOME_ENV, XDG_PICTURES_DIR_ENV, + XDG_RUNTIME_DIR_ENV, +}; + /// Resolve the user's home directory. pub fn home_dir() -> Option { - env::var_os("HOME") - .or_else(|| env::var_os("USERPROFILE")) + env::var_os(HOME_ENV) + .or_else(|| env::var_os(USERPROFILE_ENV)) .map(PathBuf::from) .or_else(|| env::current_dir().ok()) } /// Resolve the XDG config directory, falling back to `~/.config`. pub fn config_dir() -> Option { - if let Some(dir) = env::var_os("XDG_CONFIG_HOME") + if let Some(dir) = env::var_os(XDG_CONFIG_HOME_ENV) && !dir.is_empty() { return Some(PathBuf::from(dir)); @@ -21,7 +26,7 @@ pub fn config_dir() -> Option { /// Resolve the XDG data directory, falling back to `~/.local/share`. pub fn data_dir() -> Option { - if let Some(dir) = env::var_os("XDG_DATA_HOME") + if let Some(dir) = env::var_os(XDG_DATA_HOME_ENV) && !dir.is_empty() { return Some(PathBuf::from(dir)); @@ -31,7 +36,7 @@ pub fn data_dir() -> Option { /// Best-effort pictures directory (XDG), falling back to `~/Pictures`. pub fn pictures_dir() -> Option { - if let Some(dir) = env::var_os("XDG_PICTURES_DIR") + if let Some(dir) = env::var_os(XDG_PICTURES_DIR_ENV) && !dir.is_empty() { return Some(PathBuf::from(dir)); @@ -56,7 +61,7 @@ fn fallback_runtime_root() -> PathBuf { fn runtime_root() -> PathBuf { // Prefer XDG runtime dir for ephemeral files; fall back to data/home/temp for portability. #[cfg(unix)] - if let Some(dir) = env::var_os("XDG_RUNTIME_DIR") + if let Some(dir) = env::var_os(XDG_RUNTIME_DIR_ENV) && !dir.is_empty() { return PathBuf::from(dir).join("wayscriber"); @@ -68,31 +73,31 @@ fn runtime_root() -> PathBuf { } /// Location for transient tray commands. -/// Uses XDG_RUNTIME_DIR when available; falls back to data/home/temp. +/// Uses [`XDG_RUNTIME_DIR_ENV`] when available; falls back to data/home/temp. pub fn tray_action_file() -> PathBuf { runtime_root().join("tray_action") } /// Location for queued transient tray commands. -/// Uses XDG_RUNTIME_DIR when available; falls back to data/home/temp. +/// Uses [`XDG_RUNTIME_DIR_ENV`] when available; falls back to data/home/temp. pub fn tray_action_dir() -> PathBuf { runtime_root().join("tray-actions") } /// Location for transient daemon toggle requests. -/// Uses XDG_RUNTIME_DIR when available; falls back to data/home/temp. +/// Uses [`XDG_RUNTIME_DIR_ENV`] when available; falls back to data/home/temp. pub fn daemon_command_file() -> PathBuf { runtime_root().join("daemon_command.json") } /// Location for queued daemon toggle requests. -/// Uses XDG_RUNTIME_DIR when available; falls back to data/home/temp. +/// Uses [`XDG_RUNTIME_DIR_ENV`] when available; falls back to data/home/temp. pub fn daemon_command_dir() -> PathBuf { runtime_root().join("daemon-commands") } /// Location for the running daemon PID. -/// Uses XDG_RUNTIME_DIR when available; falls back to data/home/temp. +/// Uses [`XDG_RUNTIME_DIR_ENV`] when available; falls back to data/home/temp. pub fn daemon_pid_file() -> PathBuf { runtime_root().join("wayscriber.pid") } diff --git a/src/paths/tests.rs b/src/paths/tests.rs index 7cbfce20..b5c57595 100644 --- a/src/paths/tests.rs +++ b/src/paths/tests.rs @@ -1,4 +1,8 @@ use super::*; +use crate::env_vars::{ + HOME_ENV, USERPROFILE_ENV, XDG_CONFIG_HOME_ENV, XDG_DATA_HOME_ENV, XDG_PICTURES_DIR_ENV, + XDG_RUNTIME_DIR_ENV, +}; use std::env; #[test] @@ -6,10 +10,10 @@ use std::env; fn tray_action_prefers_runtime_dir_when_set() { let _guard = crate::test_env::lock(); let tmp = crate::test_temp::tempdir().unwrap(); - let prev = env::var_os("XDG_RUNTIME_DIR"); + let prev = env::var_os(XDG_RUNTIME_DIR_ENV); // SAFETY: serialised via ENV_MUTEX unsafe { - env::set_var("XDG_RUNTIME_DIR", tmp.path()); + env::set_var(XDG_RUNTIME_DIR_ENV, tmp.path()); } let path = tray_action_file(); @@ -21,11 +25,11 @@ fn tray_action_prefers_runtime_dir_when_set() { if let Some(prev) = prev { unsafe { - env::set_var("XDG_RUNTIME_DIR", prev); + env::set_var(XDG_RUNTIME_DIR_ENV, prev); } } else { unsafe { - env::remove_var("XDG_RUNTIME_DIR"); + env::remove_var(XDG_RUNTIME_DIR_ENV); } } } @@ -35,30 +39,31 @@ fn config_dir_prefers_xdg_config_home_when_set() { let _guard = crate::test_env::lock(); let tmp = crate::test_temp::tempdir().unwrap(); - let prev_home = env::var_os("HOME"); - let prev_userprofile = env::var_os("USERPROFILE"); - let prev_xdg = env::var_os("XDG_CONFIG_HOME"); + let prev_home = env::var_os(HOME_ENV); + let prev_userprofile = env::var_os(USERPROFILE_ENV); + let prev_xdg = env::var_os(XDG_CONFIG_HOME_ENV); unsafe { - env::set_var("XDG_CONFIG_HOME", tmp.path()); - env::remove_var("HOME"); - env::remove_var("USERPROFILE"); + env::set_var(XDG_CONFIG_HOME_ENV, tmp.path()); + env::remove_var(HOME_ENV); + env::remove_var(USERPROFILE_ENV); } - let dir = config_dir().expect("config_dir should resolve from XDG_CONFIG_HOME"); + let dir = config_dir() + .unwrap_or_else(|| panic!("config_dir should resolve from {XDG_CONFIG_HOME_ENV}")); assert_eq!(dir, tmp.path()); match prev_xdg { - Some(v) => unsafe { env::set_var("XDG_CONFIG_HOME", v) }, - None => unsafe { env::remove_var("XDG_CONFIG_HOME") }, + Some(v) => unsafe { env::set_var(XDG_CONFIG_HOME_ENV, v) }, + None => unsafe { env::remove_var(XDG_CONFIG_HOME_ENV) }, } match prev_home { - Some(v) => unsafe { env::set_var("HOME", v) }, - None => unsafe { env::remove_var("HOME") }, + Some(v) => unsafe { env::set_var(HOME_ENV, v) }, + None => unsafe { env::remove_var(HOME_ENV) }, } match prev_userprofile { - Some(v) => unsafe { env::set_var("USERPROFILE", v) }, - None => unsafe { env::remove_var("USERPROFILE") }, + Some(v) => unsafe { env::set_var(USERPROFILE_ENV, v) }, + None => unsafe { env::remove_var(USERPROFILE_ENV) }, } } @@ -67,30 +72,30 @@ fn config_dir_falls_back_to_home_config() { let _guard = crate::test_env::lock(); let tmp = crate::test_temp::tempdir().unwrap(); - let prev_home = env::var_os("HOME"); - let prev_userprofile = env::var_os("USERPROFILE"); - let prev_xdg = env::var_os("XDG_CONFIG_HOME"); + let prev_home = env::var_os(HOME_ENV); + let prev_userprofile = env::var_os(USERPROFILE_ENV); + let prev_xdg = env::var_os(XDG_CONFIG_HOME_ENV); unsafe { - env::set_var("HOME", tmp.path()); - env::remove_var("USERPROFILE"); - env::remove_var("XDG_CONFIG_HOME"); + env::set_var(HOME_ENV, tmp.path()); + env::remove_var(USERPROFILE_ENV); + env::remove_var(XDG_CONFIG_HOME_ENV); } let dir = config_dir().expect("config_dir should resolve from HOME"); assert_eq!(dir, tmp.path().join(".config")); match prev_xdg { - Some(v) => unsafe { env::set_var("XDG_CONFIG_HOME", v) }, - None => unsafe { env::remove_var("XDG_CONFIG_HOME") }, + Some(v) => unsafe { env::set_var(XDG_CONFIG_HOME_ENV, v) }, + None => unsafe { env::remove_var(XDG_CONFIG_HOME_ENV) }, } match prev_home { - Some(v) => unsafe { env::set_var("HOME", v) }, - None => unsafe { env::remove_var("HOME") }, + Some(v) => unsafe { env::set_var(HOME_ENV, v) }, + None => unsafe { env::remove_var(HOME_ENV) }, } match prev_userprofile { - Some(v) => unsafe { env::set_var("USERPROFILE", v) }, - None => unsafe { env::remove_var("USERPROFILE") }, + Some(v) => unsafe { env::set_var(USERPROFILE_ENV, v) }, + None => unsafe { env::remove_var(USERPROFILE_ENV) }, } } @@ -99,30 +104,31 @@ fn data_dir_prefers_xdg_data_home_when_set() { let _guard = crate::test_env::lock(); let tmp = crate::test_temp::tempdir().unwrap(); - let prev_home = env::var_os("HOME"); - let prev_userprofile = env::var_os("USERPROFILE"); - let prev_xdg = env::var_os("XDG_DATA_HOME"); + let prev_home = env::var_os(HOME_ENV); + let prev_userprofile = env::var_os(USERPROFILE_ENV); + let prev_xdg = env::var_os(XDG_DATA_HOME_ENV); unsafe { - env::set_var("XDG_DATA_HOME", tmp.path()); - env::remove_var("HOME"); - env::remove_var("USERPROFILE"); + env::set_var(XDG_DATA_HOME_ENV, tmp.path()); + env::remove_var(HOME_ENV); + env::remove_var(USERPROFILE_ENV); } - let dir = data_dir().expect("data_dir should resolve from XDG_DATA_HOME"); + let dir = + data_dir().unwrap_or_else(|| panic!("data_dir should resolve from {XDG_DATA_HOME_ENV}")); assert_eq!(dir, tmp.path()); match prev_xdg { - Some(v) => unsafe { env::set_var("XDG_DATA_HOME", v) }, - None => unsafe { env::remove_var("XDG_DATA_HOME") }, + Some(v) => unsafe { env::set_var(XDG_DATA_HOME_ENV, v) }, + None => unsafe { env::remove_var(XDG_DATA_HOME_ENV) }, } match prev_home { - Some(v) => unsafe { env::set_var("HOME", v) }, - None => unsafe { env::remove_var("HOME") }, + Some(v) => unsafe { env::set_var(HOME_ENV, v) }, + None => unsafe { env::remove_var(HOME_ENV) }, } match prev_userprofile { - Some(v) => unsafe { env::set_var("USERPROFILE", v) }, - None => unsafe { env::remove_var("USERPROFILE") }, + Some(v) => unsafe { env::set_var(USERPROFILE_ENV, v) }, + None => unsafe { env::remove_var(USERPROFILE_ENV) }, } } @@ -131,30 +137,30 @@ fn data_dir_falls_back_to_home_share() { let _guard = crate::test_env::lock(); let tmp = crate::test_temp::tempdir().unwrap(); - let prev_home = env::var_os("HOME"); - let prev_userprofile = env::var_os("USERPROFILE"); - let prev_xdg = env::var_os("XDG_DATA_HOME"); + let prev_home = env::var_os(HOME_ENV); + let prev_userprofile = env::var_os(USERPROFILE_ENV); + let prev_xdg = env::var_os(XDG_DATA_HOME_ENV); unsafe { - env::set_var("HOME", tmp.path()); - env::remove_var("USERPROFILE"); - env::remove_var("XDG_DATA_HOME"); + env::set_var(HOME_ENV, tmp.path()); + env::remove_var(USERPROFILE_ENV); + env::remove_var(XDG_DATA_HOME_ENV); } let dir = data_dir().expect("data_dir should resolve from HOME"); assert_eq!(dir, tmp.path().join(".local").join("share")); match prev_xdg { - Some(v) => unsafe { env::set_var("XDG_DATA_HOME", v) }, - None => unsafe { env::remove_var("XDG_DATA_HOME") }, + Some(v) => unsafe { env::set_var(XDG_DATA_HOME_ENV, v) }, + None => unsafe { env::remove_var(XDG_DATA_HOME_ENV) }, } match prev_home { - Some(v) => unsafe { env::set_var("HOME", v) }, - None => unsafe { env::remove_var("HOME") }, + Some(v) => unsafe { env::set_var(HOME_ENV, v) }, + None => unsafe { env::remove_var(HOME_ENV) }, } match prev_userprofile { - Some(v) => unsafe { env::set_var("USERPROFILE", v) }, - None => unsafe { env::remove_var("USERPROFILE") }, + Some(v) => unsafe { env::set_var(USERPROFILE_ENV, v) }, + None => unsafe { env::remove_var(USERPROFILE_ENV) }, } } @@ -163,30 +169,31 @@ fn pictures_dir_prefers_xdg_when_set() { let _guard = crate::test_env::lock(); let tmp = crate::test_temp::tempdir().unwrap(); - let prev_home = env::var_os("HOME"); - let prev_userprofile = env::var_os("USERPROFILE"); - let prev_xdg = env::var_os("XDG_PICTURES_DIR"); + let prev_home = env::var_os(HOME_ENV); + let prev_userprofile = env::var_os(USERPROFILE_ENV); + let prev_xdg = env::var_os(XDG_PICTURES_DIR_ENV); unsafe { - env::set_var("XDG_PICTURES_DIR", tmp.path()); - env::remove_var("HOME"); - env::remove_var("USERPROFILE"); + env::set_var(XDG_PICTURES_DIR_ENV, tmp.path()); + env::remove_var(HOME_ENV); + env::remove_var(USERPROFILE_ENV); } - let dir = pictures_dir().expect("pictures_dir should resolve from XDG_PICTURES_DIR"); + let dir = pictures_dir() + .unwrap_or_else(|| panic!("pictures_dir should resolve from {XDG_PICTURES_DIR_ENV}")); assert_eq!(dir, tmp.path()); match prev_xdg { - Some(v) => unsafe { env::set_var("XDG_PICTURES_DIR", v) }, - None => unsafe { env::remove_var("XDG_PICTURES_DIR") }, + Some(v) => unsafe { env::set_var(XDG_PICTURES_DIR_ENV, v) }, + None => unsafe { env::remove_var(XDG_PICTURES_DIR_ENV) }, } match prev_home { - Some(v) => unsafe { env::set_var("HOME", v) }, - None => unsafe { env::remove_var("HOME") }, + Some(v) => unsafe { env::set_var(HOME_ENV, v) }, + None => unsafe { env::remove_var(HOME_ENV) }, } match prev_userprofile { - Some(v) => unsafe { env::set_var("USERPROFILE", v) }, - None => unsafe { env::remove_var("USERPROFILE") }, + Some(v) => unsafe { env::set_var(USERPROFILE_ENV, v) }, + None => unsafe { env::remove_var(USERPROFILE_ENV) }, } } @@ -195,30 +202,30 @@ fn pictures_dir_falls_back_to_home() { let _guard = crate::test_env::lock(); let tmp = crate::test_temp::tempdir().unwrap(); - let prev_home = env::var_os("HOME"); - let prev_userprofile = env::var_os("USERPROFILE"); - let prev_xdg = env::var_os("XDG_PICTURES_DIR"); + let prev_home = env::var_os(HOME_ENV); + let prev_userprofile = env::var_os(USERPROFILE_ENV); + let prev_xdg = env::var_os(XDG_PICTURES_DIR_ENV); unsafe { - env::set_var("HOME", tmp.path()); - env::remove_var("USERPROFILE"); - env::remove_var("XDG_PICTURES_DIR"); + env::set_var(HOME_ENV, tmp.path()); + env::remove_var(USERPROFILE_ENV); + env::remove_var(XDG_PICTURES_DIR_ENV); } let dir = pictures_dir().expect("pictures_dir should resolve from HOME"); assert_eq!(dir, tmp.path().join("Pictures")); match prev_xdg { - Some(v) => unsafe { env::set_var("XDG_PICTURES_DIR", v) }, - None => unsafe { env::remove_var("XDG_PICTURES_DIR") }, + Some(v) => unsafe { env::set_var(XDG_PICTURES_DIR_ENV, v) }, + None => unsafe { env::remove_var(XDG_PICTURES_DIR_ENV) }, } match prev_home { - Some(v) => unsafe { env::set_var("HOME", v) }, - None => unsafe { env::remove_var("HOME") }, + Some(v) => unsafe { env::set_var(HOME_ENV, v) }, + None => unsafe { env::remove_var(HOME_ENV) }, } match prev_userprofile { - Some(v) => unsafe { env::set_var("USERPROFILE", v) }, - None => unsafe { env::remove_var("USERPROFILE") }, + Some(v) => unsafe { env::set_var(USERPROFILE_ENV, v) }, + None => unsafe { env::remove_var(USERPROFILE_ENV) }, } } @@ -227,17 +234,17 @@ fn expand_tilde_replaces_home() { let _guard = crate::test_env::lock(); let tmp = crate::test_temp::tempdir().unwrap(); - let prev_home = env::var_os("HOME"); + let prev_home = env::var_os(HOME_ENV); unsafe { - env::set_var("HOME", tmp.path()); + env::set_var(HOME_ENV, tmp.path()); } let path = expand_tilde("~/test"); assert_eq!(path, tmp.path().join("test")); match prev_home { - Some(v) => unsafe { env::set_var("HOME", v) }, - None => unsafe { env::remove_var("HOME") }, + Some(v) => unsafe { env::set_var(HOME_ENV, v) }, + None => unsafe { env::remove_var(HOME_ENV) }, } } diff --git a/src/session/catalog.rs b/src/session/catalog.rs index 0c6ac742..f779d07f 100644 --- a/src/session/catalog.rs +++ b/src/session/catalog.rs @@ -7,6 +7,9 @@ use std::path::{Component, Path, PathBuf}; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::{SystemTime, UNIX_EPOCH}; +#[cfg(test)] +use crate::env_vars::CATALOG_HOOKS_TEST_ENV; + use super::lock::{lock_exclusive, open_runtime_lock_file, unlock}; use super::options::SessionOptions; @@ -219,7 +222,7 @@ pub(crate) fn record_named_session_saved(options: &SessionOptions) { #[cfg(test)] fn test_catalog_hooks_enabled_for_path(path: &Path) -> bool { - let Some(raw) = std::env::var_os("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS") else { + let Some(raw) = std::env::var_os(CATALOG_HOOKS_TEST_ENV) else { return false; }; if raw.is_empty() || raw == std::ffi::OsStr::new("1") { diff --git a/src/session/catalog/tests.rs b/src/session/catalog/tests.rs index a4eff2ab..2d79d5b3 100644 --- a/src/session/catalog/tests.rs +++ b/src/session/catalog/tests.rs @@ -1,5 +1,6 @@ use super::*; use crate::draw::{Color, Frame, Shape}; +use crate::env_vars::{CATALOG_HOOKS_TEST_ENV, XDG_DATA_HOME_ENV}; use crate::session::{BoardPagesSnapshot, BoardSnapshot, SessionOptions, SessionSnapshot}; use std::fs; use std::path::{Path, PathBuf}; @@ -14,11 +15,11 @@ struct EnvGuard { impl EnvGuard { fn set_xdg_data_home(path: &Path) -> Self { let guard = crate::test_env::lock(); - let catalog_hooks = std::env::var_os("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS"); - let xdg_data_home = std::env::var_os("XDG_DATA_HOME"); + let catalog_hooks = std::env::var_os(CATALOG_HOOKS_TEST_ENV); + let xdg_data_home = std::env::var_os(XDG_DATA_HOME_ENV); unsafe { - std::env::set_var("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS", path); - std::env::set_var("XDG_DATA_HOME", path); + std::env::set_var(CATALOG_HOOKS_TEST_ENV, path); + std::env::set_var(XDG_DATA_HOME_ENV, path); } Self { _guard: guard, @@ -31,14 +32,12 @@ impl EnvGuard { impl Drop for EnvGuard { fn drop(&mut self) { match self.catalog_hooks.take() { - Some(value) => unsafe { - std::env::set_var("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS", value) - }, - None => unsafe { std::env::remove_var("WAYSCRIBER_ENABLE_CATALOG_HOOKS_IN_TESTS") }, + Some(value) => unsafe { std::env::set_var(CATALOG_HOOKS_TEST_ENV, value) }, + None => unsafe { std::env::remove_var(CATALOG_HOOKS_TEST_ENV) }, } match self.xdg_data_home.take() { - Some(value) => unsafe { std::env::set_var("XDG_DATA_HOME", value) }, - None => unsafe { std::env::remove_var("XDG_DATA_HOME") }, + Some(value) => unsafe { std::env::set_var(XDG_DATA_HOME_ENV, value) }, + None => unsafe { std::env::remove_var(XDG_DATA_HOME_ENV) }, } } } diff --git a/src/session/options/identifiers.rs b/src/session/options/identifiers.rs index 7280517a..97f6a89f 100644 --- a/src/session/options/identifiers.rs +++ b/src/session/options/identifiers.rs @@ -1,5 +1,7 @@ use std::env; +use crate::env_vars::WAYLAND_DISPLAY_ENV; + pub(super) fn sanitize_identifier(raw: &str) -> String { if raw.is_empty() { return "default".to_string(); @@ -15,13 +17,13 @@ pub(super) fn resolve_display_id(display_id: Option<&str>) -> String { return sanitize_identifier(id); } - match env::var("WAYLAND_DISPLAY") { + match env::var(WAYLAND_DISPLAY_ENV) { Ok(value) => { - log::info!("Session display id from WAYLAND_DISPLAY='{}'", value); + log::info!("Session display id from {WAYLAND_DISPLAY_ENV}='{}'", value); sanitize_identifier(&value) } Err(_) => { - log::info!("Session display id fallback to 'default' (WAYLAND_DISPLAY missing)"); + log::info!("Session display id fallback to 'default' ({WAYLAND_DISPLAY_ENV} missing)"); "default".to_string() } } diff --git a/src/session/options/tests.rs b/src/session/options/tests.rs index 2ec00be5..883aa9c3 100644 --- a/src/session/options/tests.rs +++ b/src/session/options/tests.rs @@ -15,6 +15,7 @@ use super::identifiers::{resolve_display_id, sanitize_identifier}; use super::types::{SessionOptions, SessionTarget}; use super::validation; use crate::config::{SessionConfig, SessionStorageMode}; +use crate::env_vars::WAYLAND_DISPLAY_ENV; static ENV_MUTEX: Mutex<()> = Mutex::new(()); @@ -38,10 +39,10 @@ fn resolve_display_id_prefers_argument_and_uses_env_fallback() { .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); - let prev = env::var_os("WAYLAND_DISPLAY"); + let prev = env::var_os(WAYLAND_DISPLAY_ENV); // SAFETY: serialized via ENV_MUTEX unsafe { - env::set_var("WAYLAND_DISPLAY", "wayland-0"); + env::set_var(WAYLAND_DISPLAY_ENV, "wayland-0"); } let from_arg = resolve_display_id(Some("custom-display")); @@ -51,8 +52,8 @@ fn resolve_display_id_prefers_argument_and_uses_env_fallback() { assert_eq!(from_env, "wayland_0"); match prev { - Some(v) => unsafe { env::set_var("WAYLAND_DISPLAY", v) }, - None => unsafe { env::remove_var("WAYLAND_DISPLAY") }, + Some(v) => unsafe { env::set_var(WAYLAND_DISPLAY_ENV, v) }, + None => unsafe { env::remove_var(WAYLAND_DISPLAY_ENV) }, } } diff --git a/src/session/tests/options.rs b/src/session/tests/options.rs index 73585178..c228189a 100644 --- a/src/session/tests/options.rs +++ b/src/session/tests/options.rs @@ -1,5 +1,6 @@ use super::super::*; use crate::config::{SessionConfig, SessionStorageMode}; +use crate::env_vars::WAYLAND_DISPLAY_ENV; #[test] fn options_from_config_custom_storage() { @@ -37,14 +38,14 @@ fn options_from_config_config_storage_uses_config_dir() { ..SessionConfig::default() }; - let original_display = std::env::var_os("WAYLAND_DISPLAY"); + let original_display = std::env::var_os(WAYLAND_DISPLAY_ENV); unsafe { - std::env::remove_var("WAYLAND_DISPLAY"); + std::env::remove_var(WAYLAND_DISPLAY_ENV); } let mut options = options_from_config(&cfg, temp.path(), None).unwrap(); if let Some(value) = original_display { - unsafe { std::env::set_var("WAYLAND_DISPLAY", value) } + unsafe { std::env::set_var(WAYLAND_DISPLAY_ENV, value) } } assert_eq!(options.base_dir, temp.path()); diff --git a/src/shortcut_hint.rs b/src/shortcut_hint.rs index b58f7d99..e3aa4df2 100644 --- a/src/shortcut_hint.rs +++ b/src/shortcut_hint.rs @@ -1,5 +1,7 @@ use std::fs; +pub use crate::env_vars::{PORTAL_APP_ID_ENV, PORTAL_SHORTCUT_ENV, PORTAL_SHORTCUT_OPT_IN_ENV}; +use crate::env_vars::{XDG_CURRENT_DESKTOP_ENV, XDG_SESSION_DESKTOP_ENV}; use crate::systemd_user_service::portal_shortcut_dropin_path; pub const GNOME_MEDIA_KEYS_SCHEMA: &str = "org.gnome.settings-daemon.plugins.media-keys"; @@ -8,9 +10,6 @@ pub const GNOME_CUSTOM_KEYBINDING_SCHEMA: &str = "org.gnome.settings-daemon.plugins.media-keys.custom-keybinding"; pub const GNOME_WAYSCRIBER_KEYBINDING_PATH: &str = "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/wayscriber-toggle/"; -pub const PORTAL_SHORTCUT_ENV: &str = "WAYSCRIBER_PORTAL_SHORTCUT"; -pub const PORTAL_APP_ID_ENV: &str = "WAYSCRIBER_PORTAL_APP_ID"; -pub const PORTAL_SHORTCUT_OPT_IN_ENV: &str = "WAYSCRIBER_ENABLE_PORTAL_SHORTCUTS"; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct PortalShortcutDropInState { @@ -63,8 +62,8 @@ pub fn resolve_shortcut_runtime_backend(inputs: ShortcutRuntimeInputs) -> Shortc } pub fn current_shortcut_runtime_backend() -> ShortcutRuntimeBackend { - let current = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(); - let session = std::env::var("XDG_SESSION_DESKTOP").unwrap_or_default(); + let current = std::env::var(XDG_CURRENT_DESKTOP_ENV).unwrap_or_default(); + let session = std::env::var(XDG_SESSION_DESKTOP_ENV).unwrap_or_default(); resolve_shortcut_runtime_backend(ShortcutRuntimeInputs { gnome_desktop: is_gnome_desktop(¤t, &session), portal_runtime_supported: portal_runtime_supported(), @@ -347,9 +346,11 @@ mod tests { #[test] fn parse_portal_shortcut_dropin_state_handles_legacy_gnome_dropin() { - let content = "[Service]\nEnvironment=\"WAYSCRIBER_PORTAL_SHORTCUT=g\"\nEnvironment=\"WAYSCRIBER_PORTAL_APP_ID=com.devmobasa.wayscriber\"\n"; + let content = format!( + "[Service]\nEnvironment=\"{PORTAL_SHORTCUT_ENV}=g\"\nEnvironment=\"{PORTAL_APP_ID_ENV}=com.devmobasa.wayscriber\"\n" + ); assert_eq!( - parse_portal_shortcut_dropin_state(content), + parse_portal_shortcut_dropin_state(&content), PortalShortcutDropInState { portal_shortcut_present: true, portal_app_id_present: true, @@ -360,7 +361,7 @@ mod tests { resolve_shortcut_runtime_backend(ShortcutRuntimeInputs { gnome_desktop: true, portal_runtime_supported: true, - portal_dropin_state: parse_portal_shortcut_dropin_state(content), + portal_dropin_state: parse_portal_shortcut_dropin_state(&content), }), ShortcutRuntimeBackend::GnomeCustomShortcut ); @@ -368,9 +369,11 @@ mod tests { #[test] fn parse_portal_shortcut_dropin_state_handles_explicit_opt_in() { - let content = "[Service]\nEnvironment=\"WAYSCRIBER_ENABLE_PORTAL_SHORTCUTS=1\"\nEnvironment=\"WAYSCRIBER_PORTAL_SHORTCUT=g\"\nEnvironment=\"WAYSCRIBER_PORTAL_APP_ID=wayscriber\"\n"; + let content = format!( + "[Service]\nEnvironment=\"{PORTAL_SHORTCUT_OPT_IN_ENV}=1\"\nEnvironment=\"{PORTAL_SHORTCUT_ENV}=g\"\nEnvironment=\"{PORTAL_APP_ID_ENV}=wayscriber\"\n" + ); assert_eq!( - parse_portal_shortcut_dropin_state(content), + parse_portal_shortcut_dropin_state(&content), PortalShortcutDropInState { portal_shortcut_present: true, portal_app_id_present: true, @@ -378,7 +381,7 @@ mod tests { } ); assert_eq!( - parse_portal_shortcut_from_dropin(content), + parse_portal_shortcut_from_dropin(&content), Some("g".to_string()) ); } diff --git a/src/systemd_user_service.rs b/src/systemd_user_service.rs index e08f3d94..b9da7e47 100644 --- a/src/systemd_user_service.rs +++ b/src/systemd_user_service.rs @@ -1,5 +1,6 @@ use std::path::{Path, PathBuf}; +use crate::env_vars::{PATH_ENV, WAYLAND_DISPLAY_ENV, XDG_RUNTIME_DIR_ENV}; use crate::paths::config_dir; pub const USER_SERVICE_NAME: &str = "wayscriber.service"; @@ -48,7 +49,7 @@ pub fn render_user_service_unit(binary_path: &Path) -> String { let escaped_path_env = escape_systemd_env_value(&format!("{binary_dir}:/usr/local/bin:/usr/bin:/bin")); format!( - "[Unit]\nDescription=Wayscriber - Screen annotation tool for Wayland\nDocumentation=https://wayscriber.com\nPartOf=graphical-session.target\nAfter=graphical-session.target\n\n[Service]\nType=simple\nExecStartPre=/bin/sh -c '[ -n \"$WAYLAND_DISPLAY\" ] && [ -S \"$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY\" ]'\nExecStart={} --daemon\nRestart=on-failure\nRestartSec=5\nRestartPreventExitStatus=75\nSuccessExitStatus=75\nEnvironment=\"PATH={}\"\n\n[Install]\nWantedBy=graphical-session.target\n", + "[Unit]\nDescription=Wayscriber - Screen annotation tool for Wayland\nDocumentation=https://wayscriber.com\nPartOf=graphical-session.target\nAfter=graphical-session.target\n\n[Service]\nType=simple\nExecStartPre=/bin/sh -c '[ -n \"${WAYLAND_DISPLAY_ENV}\" ] && [ -S \"${XDG_RUNTIME_DIR_ENV}/${WAYLAND_DISPLAY_ENV}\" ]'\nExecStart={} --daemon\nRestart=on-failure\nRestartSec=5\nRestartPreventExitStatus=75\nSuccessExitStatus=75\nEnvironment=\"{PATH_ENV}={}\"\n\n[Install]\nWantedBy=graphical-session.target\n", quoted_exec, escaped_path_env ) } diff --git a/src/tray_action.rs b/src/tray_action.rs index 1adb2f6a..1f04a2b0 100644 --- a/src/tray_action.rs +++ b/src/tray_action.rs @@ -180,6 +180,7 @@ pub(crate) fn take_pending_actions() -> Vec { #[cfg(test)] mod tests { use super::{TrayAction, queue_action, take_pending_actions}; + use crate::env_vars::XDG_RUNTIME_DIR_ENV; use std::env; use std::sync::Mutex; @@ -213,9 +214,9 @@ mod tests { .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); let tmp = crate::test_temp::tempdir().unwrap(); - let prev = env::var_os("XDG_RUNTIME_DIR"); + let prev = env::var_os(XDG_RUNTIME_DIR_ENV); unsafe { - env::set_var("XDG_RUNTIME_DIR", tmp.path()); + env::set_var(XDG_RUNTIME_DIR_ENV, tmp.path()); } queue_action(TrayAction::LightDrawOn).unwrap(); @@ -229,11 +230,11 @@ mod tests { if let Some(prev) = prev { unsafe { - env::set_var("XDG_RUNTIME_DIR", prev); + env::set_var(XDG_RUNTIME_DIR_ENV, prev); } } else { unsafe { - env::remove_var("XDG_RUNTIME_DIR"); + env::remove_var(XDG_RUNTIME_DIR_ENV); } } } diff --git a/tests/cli.rs b/tests/cli.rs index 0d88a42b..71a57791 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -4,6 +4,7 @@ use std::os::unix::fs::symlink; use std::path::{Path, PathBuf}; use std::process::{Command, Output}; use std::sync::atomic::{AtomicU64, Ordering}; +use wayscriber::env_vars::{NO_DETACH_ENV, WAYLAND_DISPLAY_ENV, XDG_CONFIG_HOME_ENV}; use wayscriber::runtime_capabilities::RUNTIME_CAPABILITIES_FLAG; static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(0); @@ -215,11 +216,11 @@ fn bare_usage_mentions_freeze_on_show() { fn active_mode_requires_wayland_env() { run_command( wayscriber_cmd() - .env_remove("WAYLAND_DISPLAY") + .env_remove(WAYLAND_DISPLAY_ENV) .arg("--active"), ) .failure() - .stderr_contains("WAYLAND_DISPLAY not set"); + .stderr_contains(&format!("{WAYLAND_DISPLAY_ENV} not set")); } #[test] @@ -230,8 +231,8 @@ fn session_clear_command_succeeds_without_files() { run_command( wayscriber_cmd() - .env("XDG_CONFIG_HOME", temp.path()) - .env_remove("WAYLAND_DISPLAY") + .env(XDG_CONFIG_HOME_ENV, temp.path()) + .env_remove(WAYLAND_DISPLAY_ENV) .arg("--clear-session"), ) .success() @@ -249,8 +250,8 @@ fn named_session_info_missing_parent_reports_not_found() { run_command( wayscriber_cmd() - .env("XDG_CONFIG_HOME", temp.path()) - .env_remove("WAYLAND_DISPLAY") + .env(XDG_CONFIG_HOME_ENV, temp.path()) + .env_remove(WAYLAND_DISPLAY_ENV) .arg("--session-info") .arg("--session-file") .arg(&named_path), @@ -269,8 +270,8 @@ fn named_session_info_forces_persistence_despite_disabled_config() { run_command( wayscriber_cmd() - .env("XDG_CONFIG_HOME", temp.path()) - .env_remove("WAYLAND_DISPLAY") + .env(XDG_CONFIG_HOME_ENV, temp.path()) + .env_remove(WAYLAND_DISPLAY_ENV) .arg("--session-info") .arg("--session-file") .arg(&named_path), @@ -293,8 +294,8 @@ fn named_session_clear_missing_parent_reports_no_artifacts() { run_command( wayscriber_cmd() - .env("XDG_CONFIG_HOME", temp.path()) - .env_remove("WAYLAND_DISPLAY") + .env(XDG_CONFIG_HOME_ENV, temp.path()) + .env_remove(WAYLAND_DISPLAY_ENV) .arg("--clear-session") .arg("--session-file") .arg(&named_path), @@ -315,8 +316,8 @@ fn named_session_info_rejects_symlink_primary() { run_command( wayscriber_cmd() - .env("XDG_CONFIG_HOME", temp.path()) - .env_remove("WAYLAND_DISPLAY") + .env(XDG_CONFIG_HOME_ENV, temp.path()) + .env_remove(WAYLAND_DISPLAY_ENV) .arg("--session-info") .arg("--session-file") .arg(&link), @@ -340,8 +341,8 @@ fn named_session_clear_rejects_symlink_primary_without_removing_artifacts() { run_command( wayscriber_cmd() - .env("XDG_CONFIG_HOME", temp.path()) - .env_remove("WAYLAND_DISPLAY") + .env(XDG_CONFIG_HOME_ENV, temp.path()) + .env_remove(WAYLAND_DISPLAY_ENV) .arg("--clear-session") .arg("--session-file") .arg(&link), @@ -370,9 +371,9 @@ fn active_named_session_rejects_symlink_primary_before_wayland_preflight() { run_command( wayscriber_cmd() - .env("XDG_CONFIG_HOME", temp.path()) - .env("WAYSCRIBER_NO_DETACH", "1") - .env_remove("WAYLAND_DISPLAY") + .env(XDG_CONFIG_HOME_ENV, temp.path()) + .env(NO_DETACH_ENV, "1") + .env_remove(WAYLAND_DISPLAY_ENV) .arg("--active") .arg("--session-file") .arg(&link), @@ -392,9 +393,9 @@ fn freeze_named_session_rejects_symlink_primary_before_wayland_preflight() { run_command( wayscriber_cmd() - .env("XDG_CONFIG_HOME", temp.path()) - .env("WAYSCRIBER_NO_DETACH", "1") - .env_remove("WAYLAND_DISPLAY") + .env(XDG_CONFIG_HOME_ENV, temp.path()) + .env(NO_DETACH_ENV, "1") + .env_remove(WAYLAND_DISPLAY_ENV) .arg("--freeze") .arg("--session-file") .arg(&link), @@ -413,8 +414,8 @@ fn active_named_session_missing_parent_fails_before_wayland_preflight() { run_command( wayscriber_cmd() - .env("XDG_CONFIG_HOME", temp.path()) - .env_remove("WAYLAND_DISPLAY") + .env(XDG_CONFIG_HOME_ENV, temp.path()) + .env_remove(WAYLAND_DISPLAY_ENV) .arg("--active") .arg("--session-file") .arg(&named_path), @@ -430,8 +431,8 @@ fn active_named_session_directory_path_fails_before_wayland_preflight() { run_command( wayscriber_cmd() - .env("XDG_CONFIG_HOME", temp.path()) - .env_remove("WAYLAND_DISPLAY") + .env(XDG_CONFIG_HOME_ENV, temp.path()) + .env_remove(WAYLAND_DISPLAY_ENV) .arg("--active") .arg("--session-file") .arg(temp.path()), @@ -450,8 +451,8 @@ fn freeze_named_session_missing_parent_fails_before_wayland_preflight() { run_command( wayscriber_cmd() - .env("XDG_CONFIG_HOME", temp.path()) - .env_remove("WAYLAND_DISPLAY") + .env(XDG_CONFIG_HOME_ENV, temp.path()) + .env_remove(WAYLAND_DISPLAY_ENV) .arg("--freeze") .arg("--session-file") .arg(&named_path), @@ -467,14 +468,14 @@ fn freeze_named_session_missing_wayland_fails_before_detach() { run_command( wayscriber_cmd() - .env("XDG_CONFIG_HOME", temp.path()) - .env_remove("WAYLAND_DISPLAY") + .env(XDG_CONFIG_HOME_ENV, temp.path()) + .env_remove(WAYLAND_DISPLAY_ENV) .arg("--freeze") .arg("--session-file") .arg(&named_path), ) .failure() - .stderr_contains("WAYLAND_DISPLAY not set"); + .stderr_contains(&format!("{WAYLAND_DISPLAY_ENV} not set")); } #[test] @@ -484,13 +485,13 @@ fn session_info_reports_saved_snapshot() { write_session_config(&temp, &session_dir); let display = "test-session"; - let original_config = std::env::var_os("XDG_CONFIG_HOME"); + let original_config = std::env::var_os(XDG_CONFIG_HOME_ENV); unsafe { - std::env::set_var("XDG_CONFIG_HOME", temp.path()); + std::env::set_var(XDG_CONFIG_HOME_ENV, temp.path()); } - let original_display = std::env::var_os("WAYLAND_DISPLAY"); + let original_display = std::env::var_os(WAYLAND_DISPLAY_ENV); unsafe { - std::env::set_var("WAYLAND_DISPLAY", display); + std::env::set_var(WAYLAND_DISPLAY_ENV, display); } let loaded = wayscriber::config::Config::load().unwrap(); @@ -505,13 +506,13 @@ fn session_info_reports_saved_snapshot() { options.set_output_identity(Some("DP-1")); match original_config { - Some(value) => unsafe { std::env::set_var("XDG_CONFIG_HOME", value) }, - None => unsafe { std::env::remove_var("XDG_CONFIG_HOME") }, + Some(value) => unsafe { std::env::set_var(XDG_CONFIG_HOME_ENV, value) }, + None => unsafe { std::env::remove_var(XDG_CONFIG_HOME_ENV) }, } match original_display { - Some(value) => unsafe { std::env::set_var("WAYLAND_DISPLAY", value) }, - None => unsafe { std::env::remove_var("WAYLAND_DISPLAY") }, + Some(value) => unsafe { std::env::set_var(WAYLAND_DISPLAY_ENV, value) }, + None => unsafe { std::env::remove_var(WAYLAND_DISPLAY_ENV) }, } let mut frame = wayscriber::draw::Frame::new(); @@ -545,8 +546,8 @@ fn session_info_reports_saved_snapshot() { run_command( wayscriber_cmd() - .env("XDG_CONFIG_HOME", temp.path()) - .env("WAYLAND_DISPLAY", display) + .env(XDG_CONFIG_HOME_ENV, temp.path()) + .env(WAYLAND_DISPLAY_ENV, display) .arg("--session-info"), ) .success()