1use tauri::{Manager, Runtime};
2use victauri_core::WindowState;
3
4pub trait WebviewBridge: Send + Sync {
8 fn eval_webview(&self, label: Option<&str>, script: &str) -> Result<(), String>;
14 fn get_window_states(&self, label: Option<&str>) -> Vec<WindowState>;
16 fn list_window_labels(&self) -> Vec<String>;
18 fn get_native_handle(&self, label: Option<&str>) -> Result<isize, String>;
25 fn manage_window(&self, label: Option<&str>, action: &str) -> Result<String, String>;
31 fn resize_window(&self, label: Option<&str>, width: u32, height: u32) -> Result<(), String>;
37 fn move_window(&self, label: Option<&str>, x: i32, y: i32) -> Result<(), String>;
43 fn set_window_title(&self, label: Option<&str>, title: &str) -> Result<(), String>;
49
50 fn app_data_dir(&self) -> Result<std::path::PathBuf, String> {
58 Err("backend access not available".to_string())
59 }
60
61 fn app_config_dir(&self) -> Result<std::path::PathBuf, String> {
67 Err("backend access not available".to_string())
68 }
69
70 fn app_log_dir(&self) -> Result<std::path::PathBuf, String> {
76 Err("backend access not available".to_string())
77 }
78
79 fn app_local_data_dir(&self) -> Result<std::path::PathBuf, String> {
85 Err("backend access not available".to_string())
86 }
87
88 #[must_use]
90 fn tauri_config(&self) -> serde_json::Value {
91 serde_json::Value::Null
92 }
93}
94
95fn find_window<'a, R: Runtime>(
96 windows: &'a std::collections::HashMap<String, tauri::WebviewWindow<R>>,
97 label: Option<&str>,
98) -> Result<&'a tauri::WebviewWindow<R>, String> {
99 match label {
100 Some(l) => windows
101 .get(l)
102 .ok_or_else(|| format!("window not found: {l}")),
103 None => windows
104 .get("main")
105 .or_else(|| windows.values().find(|w| w.is_visible().unwrap_or(false)))
106 .or_else(|| windows.values().next())
107 .ok_or_else(|| "no window available".to_string()),
108 }
109}
110
111impl<R: Runtime> WebviewBridge for tauri::AppHandle<R> {
112 fn eval_webview(&self, label: Option<&str>, script: &str) -> Result<(), String> {
113 let windows = self.webview_windows();
114 let webview = find_window(&windows, label)?;
115 webview.eval(script).map_err(|e| e.to_string())
116 }
117
118 fn get_window_states(&self, label: Option<&str>) -> Vec<WindowState> {
119 let windows = self.webview_windows();
120 let mut states = Vec::new();
121
122 for (win_label, window) in &windows {
123 if let Some(filter) = label
124 && win_label != filter
125 {
126 continue;
127 }
128
129 let pos = window.outer_position().unwrap_or_default();
130 let size = window.inner_size().unwrap_or_default();
131
132 states.push(WindowState {
133 label: win_label.clone(),
134 title: window.title().unwrap_or_default(),
135 url: window.url().map(|u| u.to_string()).unwrap_or_default(),
136 visible: window.is_visible().unwrap_or(false),
137 focused: window.is_focused().unwrap_or(false),
138 maximized: window.is_maximized().unwrap_or(false),
139 minimized: window.is_minimized().unwrap_or(false),
140 fullscreen: window.is_fullscreen().unwrap_or(false),
141 position: (pos.x, pos.y),
142 size: (size.width, size.height),
143 });
144 }
145
146 states
147 }
148
149 fn list_window_labels(&self) -> Vec<String> {
150 self.webview_windows().keys().cloned().collect()
151 }
152
153 fn get_native_handle(&self, label: Option<&str>) -> Result<isize, String> {
154 use raw_window_handle::{HasWindowHandle, RawWindowHandle};
155
156 let windows = self.webview_windows();
157 let _webview = find_window(&windows, label)?;
158 let handle = _webview.window_handle().map_err(|e| e.to_string())?;
159 match handle.as_raw() {
160 #[cfg(windows)]
161 RawWindowHandle::Win32(h) => Ok(h.hwnd.get()),
162 #[cfg(target_os = "macos")]
163 RawWindowHandle::AppKit(h) => {
164 macos_window_number(h.ns_view.as_ptr())
167 }
168 #[cfg(target_os = "linux")]
169 RawWindowHandle::Xlib(h) => Ok(h.window as isize),
170 #[cfg(target_os = "linux")]
171 RawWindowHandle::Xcb(h) => Ok(h.window.get() as isize),
172 _ => Err("unsupported window handle type on this platform".to_string()),
173 }
174 }
175
176 fn manage_window(&self, label: Option<&str>, action: &str) -> Result<String, String> {
177 let windows = self.webview_windows();
178 let window = find_window(&windows, label)?;
179
180 match action {
181 "minimize" => window.minimize().map_err(|e| e.to_string())?,
182 "unminimize" => window.unminimize().map_err(|e| e.to_string())?,
183 "maximize" => window.maximize().map_err(|e| e.to_string())?,
184 "unmaximize" => window.unmaximize().map_err(|e| e.to_string())?,
185 "close" => window.close().map_err(|e| e.to_string())?,
186 "focus" => window.set_focus().map_err(|e| e.to_string())?,
187 "show" => window.show().map_err(|e| e.to_string())?,
188 "hide" => window.hide().map_err(|e| e.to_string())?,
189 "fullscreen" => window.set_fullscreen(true).map_err(|e| e.to_string())?,
190 "unfullscreen" => window.set_fullscreen(false).map_err(|e| e.to_string())?,
191 "always_on_top" => window.set_always_on_top(true).map_err(|e| e.to_string())?,
192 "not_always_on_top" => window.set_always_on_top(false).map_err(|e| e.to_string())?,
193 _ => return Err(format!("unknown action: {action}")),
194 }
195
196 Ok(format!("{action} executed"))
197 }
198
199 fn resize_window(&self, label: Option<&str>, width: u32, height: u32) -> Result<(), String> {
200 let windows = self.webview_windows();
201 let window = find_window(&windows, label)?;
202
203 window
204 .set_size(tauri::LogicalSize::new(width, height))
205 .map_err(|e| e.to_string())
206 }
207
208 fn move_window(&self, label: Option<&str>, x: i32, y: i32) -> Result<(), String> {
209 let windows = self.webview_windows();
210 let window = find_window(&windows, label)?;
211
212 window
213 .set_position(tauri::LogicalPosition::new(x, y))
214 .map_err(|e| e.to_string())
215 }
216
217 fn set_window_title(&self, label: Option<&str>, title: &str) -> Result<(), String> {
218 let windows = self.webview_windows();
219 let window = find_window(&windows, label)?;
220
221 window.set_title(title).map_err(|e| e.to_string())
222 }
223
224 fn app_data_dir(&self) -> Result<std::path::PathBuf, String> {
225 self.path().app_data_dir().map_err(|e| e.to_string())
226 }
227
228 fn app_config_dir(&self) -> Result<std::path::PathBuf, String> {
229 self.path().app_config_dir().map_err(|e| e.to_string())
230 }
231
232 fn app_log_dir(&self) -> Result<std::path::PathBuf, String> {
233 self.path().app_log_dir().map_err(|e| e.to_string())
234 }
235
236 fn app_local_data_dir(&self) -> Result<std::path::PathBuf, String> {
237 self.path().app_local_data_dir().map_err(|e| e.to_string())
238 }
239
240 fn tauri_config(&self) -> serde_json::Value {
241 let config = self.config();
242
243 let windows: Vec<serde_json::Value> = config
244 .app
245 .windows
246 .iter()
247 .map(|w| {
248 serde_json::json!({
249 "label": w.label,
250 "title": w.title,
251 "url": format!("{}", w.url),
252 "width": w.width,
253 "height": w.height,
254 "visible": w.visible,
255 "resizable": w.resizable,
256 "fullscreen": w.fullscreen,
257 "decorations": w.decorations,
258 "transparent": w.transparent,
259 "always_on_top": w.always_on_top,
260 })
261 })
262 .collect();
263
264 let plugins: Vec<String> = config.plugins.0.keys().cloned().collect();
265
266 let security = serde_json::json!({
267 "csp": config.app.security.csp.as_ref().map(|c| format!("{c}")),
268 "freeze_prototype": config.app.security.freeze_prototype,
269 "capabilities": config.app.security.capabilities.iter().map(|c| {
270 match c {
271 tauri::utils::config::CapabilityEntry::Inlined(cap) => {
272 serde_json::json!({
273 "identifier": cap.identifier,
274 "description": cap.description,
275 "windows": cap.windows,
276 "webviews": cap.webviews,
277 "permissions": cap.permissions.iter().map(|p| format!("{p:?}")).collect::<Vec<_>>(),
278 "platforms": cap.platforms,
279 })
280 }
281 tauri::utils::config::CapabilityEntry::Reference(path) => {
282 serde_json::json!({ "reference": path })
283 }
284 }
285 }).collect::<Vec<_>>(),
286 });
287
288 serde_json::json!({
289 "identifier": config.identifier,
290 "product_name": config.product_name,
291 "version": config.version,
292 "windows": windows,
293 "plugins": plugins,
294 "security": security,
295 })
296 }
297}
298
299#[cfg(target_os = "macos")]
300#[allow(unsafe_code)]
301fn macos_window_number(ns_view: *mut std::ffi::c_void) -> Result<isize, String> {
302 unsafe extern "C" {
303 fn objc_msgSend(obj: *mut std::ffi::c_void, sel: *mut std::ffi::c_void) -> isize;
304 fn sel_registerName(name: *const std::ffi::c_char) -> *mut std::ffi::c_void;
305 }
306
307 if ns_view.is_null() {
308 return Err("null NSView handle".to_string());
309 }
310
311 unsafe {
315 let sel_window = sel_registerName(c"window".as_ptr());
316 let ns_window = objc_msgSend(ns_view, sel_window);
317 if ns_window == 0 {
318 return Err("NSView has no parent NSWindow".to_string());
319 }
320 let sel_window_number = sel_registerName(c"windowNumber".as_ptr());
321 let ns_window_ptr = ns_window as *mut std::ffi::c_void;
322 let window_number = objc_msgSend(ns_window_ptr, sel_window_number);
323 if window_number <= 0 {
324 return Err(format!("invalid CGWindowID: {window_number}"));
325 }
326 Ok(window_number)
327 }
328}