agentmux_launcher/
splash_config.rs

1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! "Disable the splash" preference, resolved by the launcher *before* it spawns
5//! the splash (and before the host/srv exist), per
6//! `SPEC_SPLASH_USERINFO_AND_DISABLE_2026_06_21.md` §6.
7//!
8//! Precedence: env override wins; otherwise `settings.json` `"splash:disabled"`.
9//! **Fail-safe:** any error reading/parsing settings resolves to *enabled* — a
10//! broken read must never silently suppress the splash.
11
12use std::path::{Path, PathBuf};
13
14/// True when the splash should be suppressed entirely (no window created).
15pub fn splash_disabled() -> bool {
16    if let Some(forced) = env_override() {
17        return forced; // env is authoritative (Some(true)=disable, Some(false)=force-on)
18    }
19    // Best-effort settings.json read. Wrapped so a resolver panic/IO error can
20    // never take down launcher startup — on any failure we keep the splash.
21    std::panic::catch_unwind(|| {
22        config_dir()
23            .map(|d| settings_splash_disabled(&d.join("settings.json")))
24            .unwrap_or(false)
25    })
26    .unwrap_or(false)
27}
28
29/// `Some(true)` = disable, `Some(false)` = force-enable, `None` = no opinion.
30fn env_override() -> Option<bool> {
31    if let Ok(v) = std::env::var("AGENTMUX_NO_SPLASH") {
32        if truthy(&v) {
33            return Some(true);
34        }
35    }
36    if let Ok(v) = std::env::var("AGENTMUX_SPLASH") {
37        return match v.trim().to_ascii_lowercase().as_str() {
38            "0" | "false" | "off" | "no" => Some(true),
39            "1" | "true" | "on" | "yes" => Some(false),
40            _ => None,
41        };
42    }
43    None
44}
45
46fn truthy(v: &str) -> bool {
47    matches!(
48        v.trim().to_ascii_lowercase().as_str(),
49        "1" | "true" | "on" | "yes"
50    )
51}
52
53/// Read `"splash:disabled"` (flat `namespace:key`) from a settings.json file.
54/// Missing file / parse error / missing key → `false` (enabled).
55pub fn settings_splash_disabled(settings_path: &Path) -> bool {
56    let Ok(text) = std::fs::read_to_string(settings_path) else {
57        return false;
58    };
59    let Ok(json) = serde_json::from_str::<serde_json::Value>(&text) else {
60        return false;
61    };
62    json.get("splash:disabled")
63        .and_then(|v| v.as_bool())
64        .unwrap_or(false)
65}
66
67/// Best-effort config dir (holds settings.json) resolved the same way
68/// `launcher_main` does — but early and failure-tolerant.
69fn config_dir() -> Option<PathBuf> {
70    let exe = std::env::current_exe().ok()?;
71    let exe_dir = exe.parent()?;
72    let version = env!("CARGO_PKG_VERSION");
73    crate::data_dir::resolve_paths(exe_dir, version)
74        .ok()
75        .map(|p| p.config_dir)
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    fn write_tmp(tag: &str, content: &str) -> PathBuf {
83        let p = std::env::temp_dir().join(format!("agentmux-splashcfg-{tag}-{}.json", std::process::id()));
84        std::fs::write(&p, content).unwrap();
85        p
86    }
87
88    #[test]
89    fn settings_disabled_true() {
90        let p = write_tmp("on", r#"{"splash:disabled": true}"#);
91        assert!(settings_splash_disabled(&p));
92        let _ = std::fs::remove_file(&p);
93    }
94
95    #[test]
96    fn settings_disabled_false_when_key_false() {
97        let p = write_tmp("off", r#"{"splash:disabled": false}"#);
98        assert!(!settings_splash_disabled(&p));
99        let _ = std::fs::remove_file(&p);
100    }
101
102    #[test]
103    fn settings_default_enabled_when_key_absent() {
104        let p = write_tmp("absent", r#"{"term:fontsize": 14}"#);
105        assert!(!settings_splash_disabled(&p));
106        let _ = std::fs::remove_file(&p);
107    }
108
109    #[test]
110    fn settings_fail_safe_enabled_on_missing_or_bad() {
111        // Missing file and malformed JSON both resolve to enabled (false).
112        assert!(!settings_splash_disabled(Path::new("/no/such/agentmux-settings.json")));
113        let p = write_tmp("bad", "this is not json {");
114        assert!(!settings_splash_disabled(&p));
115        let _ = std::fs::remove_file(&p);
116    }
117}