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}