Skip to main content

Mountain/IPC/WindServiceHandler/
NativeHost.rs

1#![allow(non_snake_case)]
2
3//! NativeHost domain handlers for Wind IPC.
4//!
5//! Covers INativeHostService commands: dialogs, OS info, window state,
6//! color scheme, dark mode detection, port scanning.
7
8use serde_json::{Value, json};
9use tauri::{AppHandle, Manager};
10
11use crate::dev_log;
12
13/// Pick folder using Tauri dialog plugin and reload webview with folder param.
14pub async fn handle_native_pick_folder(AppHandle:AppHandle, _Args:Vec<Value>) -> Result<Value, String> {
15	use tauri_plugin_dialog::DialogExt;
16	use tauri::WebviewWindow;
17
18	dev_log!("folder", "pickFolderAndOpen requested");
19
20	dev_log!("folder", "pickFolderAndOpen requested");
21
22	let Handle = AppHandle.clone();
23	tokio::task::spawn_blocking(move || {
24		let FolderPath = Handle.dialog().file().blocking_pick_folder();
25
26		if let Some(Path) = FolderPath {
27			let PathStr = Path.to_string();
28			dev_log!("folder", "picked: {}", PathStr);
29
30			if let Some(Window) = Handle.get_webview_window("main") {
31				if let Ok(CurrentUrl) = Window.url() {
32					let Origin = CurrentUrl.origin().unicode_serialization();
33					let EncodedPath = url::form_urlencoded::Serializer::new(String::new())
34						.append_pair("folder", &PathStr)
35						.finish();
36					let NewUrl = format!("{}/?{}", Origin, EncodedPath);
37					dev_log!("folder", "navigating: {}", NewUrl);
38					let _ = Window.navigate(NewUrl.parse().unwrap());
39				}
40			}
41		}
42	});
43
44	Ok(Value::Null)
45}
46
47/// Show open dialog with file/folder picker
48pub async fn handle_native_show_open_dialog(AppHandle:AppHandle, Args:Vec<Value>) -> Result<Value, String> {
49	dev_log!("folder", "showOpenDialog: {:?}", Args);
50	Ok(json!({ "canceled": true, "filePaths": [] }))
51}
52
53/// Get OS properties - cross-platform (macOS, Windows, Linux)
54pub async fn handle_native_os_properties() -> Result<Value, String> {
55	use sysinfo::System;
56
57	let OsType = match std::env::consts::OS {
58		"macos" => "Darwin",
59		"windows" => "Windows_NT",
60		"linux" => "Linux",
61		_ => std::env::consts::OS,
62	};
63
64	let Release = {
65		#[cfg(target_os = "macos")]
66		{
67			std::process::Command::new("sw_vers")
68				.arg("-productVersion")
69				.output()
70				.ok()
71				.map(|O| String::from_utf8_lossy(&O.stdout).trim().to_string())
72				.unwrap_or_else(|| "14.0".to_string())
73		}
74		#[cfg(target_os = "windows")]
75		{
76			std::process::Command::new("cmd")
77				.args(["/c", "ver"])
78				.output()
79				.ok()
80				.map(|O| {
81					let Output = String::from_utf8_lossy(&O.stdout);
82					Output
83						.split('[')
84						.nth(1)
85						.and_then(|S| S.split(']').next())
86						.and_then(|S| S.strip_prefix("Version "))
87						.unwrap_or("10.0.0")
88						.to_string()
89				})
90				.unwrap_or_else(|| "10.0.0".to_string())
91		}
92		#[cfg(target_os = "linux")]
93		{
94			std::process::Command::new("uname")
95				.arg("-r")
96				.output()
97				.ok()
98				.map(|O| String::from_utf8_lossy(&O.stdout).trim().to_string())
99				.unwrap_or_else(|| "6.1.0".to_string())
100		}
101		#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
102		{
103			"0.0.0".to_string()
104		}
105	};
106
107	let mut Sys = System::new();
108	Sys.refresh_cpu_all();
109	let Cpus:Vec<Value> = Sys
110		.cpus()
111		.iter()
112		.map(|Cpu| {
113			json!({
114				"model": Cpu.brand(),
115				"speed": Cpu.frequency()
116			})
117		})
118		.collect();
119
120	Ok(json!({
121		"type": OsType,
122		"release": Release,
123		"arch": std::env::consts::ARCH,
124		"platform": std::env::consts::OS,
125		"cpus": Cpus
126	}))
127}
128
129/// Get OS statistics - cross-platform memory/load stats
130pub async fn handle_native_os_statistics() -> Result<Value, String> {
131	use sysinfo::System;
132
133	let mut Sys = System::new();
134	Sys.refresh_memory();
135
136	let TotalMem = Sys.total_memory();
137	let FreeMem = Sys.available_memory();
138
139	let LoadAvg = {
140		#[cfg(unix)]
141		{
142			let Load = System::load_average();
143			vec![Load.one, Load.five, Load.fifteen]
144		}
145		#[cfg(not(unix))]
146		{
147			vec![0.0, 0.0, 0.0]
148		}
149	};
150
151	Ok(json!({
152		"totalmem": TotalMem,
153		"freemem": FreeMem,
154		"loadavg": LoadAvg
155	}))
156}
157
158/// Check if window is fullscreen
159pub async fn handle_native_is_fullscreen(AppHandle:AppHandle) -> Result<Value, String> {
160	use tauri::Manager;
161	let Window = AppHandle.get_webview_window("main");
162	if let Some(W) = Window {
163		Ok(json!(W.is_fullscreen().unwrap_or(false)))
164	} else {
165		Ok(json!(false))
166	}
167}
168
169/// Check if window is maximized
170pub async fn handle_native_is_maximized(AppHandle:AppHandle) -> Result<Value, String> {
171	use tauri::Manager;
172	let Window = AppHandle.get_webview_window("main");
173	if let Some(W) = Window {
174		Ok(json!(W.is_maximized().unwrap_or(false)))
175	} else {
176		Ok(json!(false))
177	}
178}
179
180/// Find a free port starting from a given port
181pub async fn handle_native_find_free_port(Args:Vec<Value>) -> Result<Value, String> {
182	let StartPort = Args.get(0).and_then(|V| V.as_u64()).unwrap_or(9000) as u16;
183
184	for Port in StartPort..StartPort + 100 {
185		if std::net::TcpListener::bind(("127.0.0.1", Port)).is_ok() {
186			return Ok(json!(Port));
187		}
188	}
189	Ok(json!(0))
190}
191
192/// Detect OS color scheme - cross-platform dark mode detection
193pub async fn handle_native_get_color_scheme() -> Result<Value, String> {
194	let Dark = detect_dark_mode();
195	let HighContrast = {
196		#[cfg(target_os = "windows")]
197		{
198			std::process::Command::new("reg")
199				.args(["query", "HKCU\\Control Panel\\Accessibility\\HighContrast", "/v", "Flags"])
200				.output()
201				.ok()
202				.map(|O| {
203					let Output = String::from_utf8_lossy(&O.stdout);
204					Output.contains("0x1") || Output.contains("REG_DWORD    1")
205				})
206				.unwrap_or(false)
207		}
208		#[cfg(not(target_os = "windows"))]
209		{
210			#[cfg(target_os = "linux")]
211			{
212				std::process::Command::new("gsettings")
213					.args(["get", "org.gnome.desktop.a11y.interface", "high-contrast"])
214					.output()
215					.ok()
216					.map(|O| String::from_utf8_lossy(&O.stdout).trim() == "true")
217					.unwrap_or(false)
218			}
219			#[cfg(not(target_os = "linux"))]
220			{
221				false
222			}
223		}
224	};
225
226	Ok(json!({ "dark": Dark, "highContrast": HighContrast }))
227}
228
229/// Cross-platform dark mode detection
230fn detect_dark_mode() -> bool {
231	#[cfg(target_os = "macos")]
232	{
233		std::process::Command::new("defaults")
234			.args(["read", "-g", "AppleInterfaceStyle"])
235			.output()
236			.ok()
237			.map(|O| String::from_utf8_lossy(&O.stdout).trim().to_lowercase().contains("dark"))
238			.unwrap_or(false)
239	}
240
241	#[cfg(target_os = "windows")]
242	{
243		std::process::Command::new("reg")
244			.args([
245				"query",
246				"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
247				"/v",
248				"AppsUseLightTheme",
249			])
250			.output()
251			.ok()
252			.map(|O| {
253				let Output = String::from_utf8_lossy(&O.stdout);
254				Output.contains("0x0") || Output.contains("REG_DWORD    0")
255			})
256			.unwrap_or(false)
257	}
258
259	#[cfg(target_os = "linux")]
260	{
261		let GtkDark = std::process::Command::new("gsettings")
262			.args(["get", "org.gnome.desktop.interface", "color-scheme"])
263			.output()
264			.ok()
265			.map(|O| String::from_utf8_lossy(&O.stdout).contains("dark"))
266			.unwrap_or(false);
267
268		if GtkDark {
269			return true;
270		}
271
272		let GtkTheme = std::process::Command::new("gsettings")
273			.args(["get", "org.gnome.desktop.interface", "gtk-theme"])
274			.output()
275			.ok()
276			.map(|O| String::from_utf8_lossy(&O.stdout).to_lowercase().contains("dark"))
277			.unwrap_or(false);
278
279		if GtkTheme {
280			return true;
281		}
282
283		let KdeDark = std::env::var("KDE_COLOR_SCHEME")
284			.ok()
285			.map(|V| V.to_lowercase().contains("dark"))
286			.unwrap_or(false);
287
288		if KdeDark {
289			return true;
290		}
291
292		let XfceDark = std::process::Command::new("xfconf-query")
293			.args(["-c", "xsettings", "-p", "/Net/ThemeName"])
294			.output()
295			.ok()
296			.map(|O| String::from_utf8_lossy(&O.stdout).to_lowercase().contains("dark"))
297			.unwrap_or(false);
298
299		XfceDark
300	}
301
302	#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
303	{
304		false
305	}
306}