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 serde_json::json!({
243 "identifier": config.identifier,
244 "product_name": config.product_name,
245 "version": config.version,
246 })
247 }
248}
249
250#[cfg(target_os = "macos")]
251#[allow(unsafe_code)]
252fn macos_window_number(ns_view: *mut std::ffi::c_void) -> Result<isize, String> {
253 unsafe extern "C" {
254 fn objc_msgSend(obj: *mut std::ffi::c_void, sel: *mut std::ffi::c_void) -> isize;
255 fn sel_registerName(name: *const std::ffi::c_char) -> *mut std::ffi::c_void;
256 }
257
258 if ns_view.is_null() {
259 return Err("null NSView handle".to_string());
260 }
261
262 unsafe {
266 let sel_window = sel_registerName(c"window".as_ptr());
267 let ns_window = objc_msgSend(ns_view, sel_window);
268 if ns_window == 0 {
269 return Err("NSView has no parent NSWindow".to_string());
270 }
271 let sel_window_number = sel_registerName(c"windowNumber".as_ptr());
272 let ns_window_ptr = ns_window as *mut std::ffi::c_void;
273 let window_number = objc_msgSend(ns_window_ptr, sel_window_number);
274 if window_number <= 0 {
275 return Err(format!("invalid CGWindowID: {window_number}"));
276 }
277 Ok(window_number)
278 }
279}