Skip to main content

Mountain/IPC/WindServiceHandlers/NativeHost/
GetColorScheme.rs

1//! Wire method: `nativeHost:getColorScheme`.
2//! Returns `{ dark, highContrast }`. Dark-mode probe covers macOS
3//! `AppleInterfaceStyle`, Windows `AppsUseLightTheme`, and a Linux ladder
4//! (GTK color-scheme → GTK theme name → `KDE_COLOR_SCHEME` → xfconf).
5//! High-contrast probe is Windows `HighContrast/Flags` and the GNOME a11y
6//! `high-contrast` key; other OSes return false.
7
8use std::sync::OnceLock;
9
10use serde_json::{Value, json};
11
12// Cache dark-mode result for the process lifetime. The system colour scheme
13// is queried by spawning `defaults`/`reg`/`gsettings` which adds ~5-15 ms
14// on cold start. The workbench calls `getOSColorScheme` during boot and again
15// on window focus; caching turns the second+ call into a sub-microsecond read.
16// If the user switches dark/light mode while editing, they are expected to
17// restart the editor (same behaviour as stock VS Code on Electron).
18static DARK_MODE_CACHE:OnceLock<bool> = OnceLock::new();
19
20pub async fn Fn() -> Result<Value, String> {
21	let Dark = *DARK_MODE_CACHE.get_or_init(detect_dark_mode);
22
23	let HighContrast = {
24		#[cfg(target_os = "windows")]
25		{
26			std::process::Command::new("reg")
27				.args(["query", "HKCU\\Control Panel\\Accessibility\\HighContrast", "/v", "Flags"])
28				.output()
29				.ok()
30				.map(|O| {
31					let Output = String::from_utf8_lossy(&O.stdout);
32
33					Output.contains("0x1") || Output.contains("REG_DWORD    1")
34				})
35				.unwrap_or(false)
36		}
37
38		#[cfg(not(target_os = "windows"))]
39		{
40			#[cfg(target_os = "linux")]
41			{
42				std::process::Command::new("gsettings")
43					.args(["get", "org.gnome.desktop.a11y.interface", "high-contrast"])
44					.output()
45					.ok()
46					.map(|O| String::from_utf8_lossy(&O.stdout).trim() == "true")
47					.unwrap_or(false)
48			}
49
50			#[cfg(not(target_os = "linux"))]
51			{
52				false
53			}
54		}
55	};
56
57	Ok(json!({ "dark": Dark, "highContrast": HighContrast }))
58}
59
60fn detect_dark_mode() -> bool {
61	// runs once then cached via OnceLock
62	#[cfg(target_os = "macos")]
63	{
64		std::process::Command::new("defaults")
65			.args(["read", "-g", "AppleInterfaceStyle"])
66			.output()
67			.ok()
68			.map(|O| String::from_utf8_lossy(&O.stdout).trim().to_lowercase().contains("dark"))
69			.unwrap_or(false)
70	}
71
72	#[cfg(target_os = "windows")]
73	{
74		std::process::Command::new("reg")
75			.args([
76				"query",
77				"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
78				"/v",
79				"AppsUseLightTheme",
80			])
81			.output()
82			.ok()
83			.map(|O| {
84				let Output = String::from_utf8_lossy(&O.stdout);
85
86				Output.contains("0x0") || Output.contains("REG_DWORD    0")
87			})
88			.unwrap_or(false)
89	}
90
91	#[cfg(target_os = "linux")]
92	{
93		let GtkDark = std::process::Command::new("gsettings")
94			.args(["get", "org.gnome.desktop.interface", "color-scheme"])
95			.output()
96			.ok()
97			.map(|O| String::from_utf8_lossy(&O.stdout).contains("dark"))
98			.unwrap_or(false);
99
100		if GtkDark {
101			return true;
102		}
103
104		let GtkTheme = std::process::Command::new("gsettings")
105			.args(["get", "org.gnome.desktop.interface", "gtk-theme"])
106			.output()
107			.ok()
108			.map(|O| String::from_utf8_lossy(&O.stdout).to_lowercase().contains("dark"))
109			.unwrap_or(false);
110
111		if GtkTheme {
112			return true;
113		}
114
115		let KdeDark = std::env::var("KDE_COLOR_SCHEME")
116			.ok()
117			.map(|V| V.to_lowercase().contains("dark"))
118			.unwrap_or(false);
119
120		if KdeDark {
121			return true;
122		}
123
124		let XfceDark = std::process::Command::new("xfconf-query")
125			.args(["-c", "xsettings", "-p", "/Net/ThemeName"])
126			.output()
127			.ok()
128			.map(|O| String::from_utf8_lossy(&O.stdout).to_lowercase().contains("dark"))
129			.unwrap_or(false);
130
131		XfceDark
132	}
133
134	#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
135	{
136		false
137	}
138}