gestura_core_foundation/
platform.rs

1//! Platform detection utilities.
2//!
3//! Cross-platform helpers for detecting OS-level settings such as the
4//! system color scheme. These are shared between the CLI and GUI crates
5//! so that both can respect the user's system preferences.
6
7/// Detect whether the operating system is configured for dark mode.
8///
9/// Heuristics per platform:
10///
11/// - **macOS**: `defaults read -g AppleInterfaceStyle` → "Dark" means dark mode.
12/// - **Linux**: checks `$GTK_THEME` for a `-dark` suffix, then falls back to
13///   `gsettings get org.gnome.desktop.interface color-scheme`.
14/// - **Windows**: reads `AppsUseLightTheme` from the registry via `reg query`.
15///
16/// Returns `true` (dark) when detection fails or the platform is unrecognized.
17pub fn detect_system_dark_mode() -> bool {
18    detect_system_dark_mode_inner().unwrap_or(true)
19}
20
21fn detect_system_dark_mode_inner() -> Option<bool> {
22    #[cfg(target_os = "macos")]
23    {
24        // `defaults read -g AppleInterfaceStyle` prints "Dark" when dark mode is
25        // active and exits with a non-zero status when light mode is active.
26        let output = std::process::Command::new("defaults")
27            .args(["read", "-g", "AppleInterfaceStyle"])
28            .output()
29            .ok()?;
30        let stdout = String::from_utf8_lossy(&output.stdout);
31        return Some(stdout.trim().eq_ignore_ascii_case("dark"));
32    }
33
34    #[cfg(target_os = "linux")]
35    {
36        // 1. Quick env-var check: many GTK apps export GTK_THEME=Adwaita:dark.
37        if let Ok(gtk) = std::env::var("GTK_THEME") {
38            let lower = gtk.to_ascii_lowercase();
39            if lower.contains("dark") {
40                return Some(true);
41            }
42            if lower.contains("light") {
43                return Some(false);
44            }
45        }
46        // 2. XDG portal / GNOME color-scheme (works inside Flatpak too).
47        if let Ok(output) = std::process::Command::new("gsettings")
48            .args(["get", "org.gnome.desktop.interface", "color-scheme"])
49            .output()
50        {
51            let stdout = String::from_utf8_lossy(&output.stdout);
52            let lower = stdout.trim().to_ascii_lowercase();
53            if lower.contains("dark") {
54                return Some(true);
55            }
56            if lower.contains("light") {
57                return Some(false);
58            }
59        }
60        return Some(true); // default dark on Linux
61    }
62
63    #[cfg(target_os = "windows")]
64    {
65        // Registry: HKCU\...\Personalize\AppsUseLightTheme  (DWORD: 0 = dark, 1 = light)
66        if let Ok(output) = std::process::Command::new("reg")
67            .args([
68                "query",
69                r"HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
70                "/v",
71                "AppsUseLightTheme",
72            ])
73            .output()
74        {
75            let stdout = String::from_utf8_lossy(&output.stdout);
76            // The output contains "0x0" for dark, "0x1" for light.
77            if stdout.contains("0x0") {
78                return Some(true);
79            }
80            if stdout.contains("0x1") {
81                return Some(false);
82            }
83        }
84        return Some(true); // default dark on Windows
85    }
86
87    // Unsupported platform — fall back to dark.
88    #[allow(unreachable_code)]
89    Some(true)
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn detect_system_dark_mode_does_not_panic() {
98        // The function relies on platform-specific commands; verify it returns a
99        // bool without panicking regardless of the environment.
100        let _result: bool = detect_system_dark_mode();
101    }
102}