1use tauri::{Manager, Runtime};
2use victauri_core::WindowState;
3
4pub trait WebviewBridge: Send + Sync {
6 fn eval_webview(&self, label: Option<&str>, script: &str) -> Result<(), String>;
12 fn get_window_states(&self, label: Option<&str>) -> Vec<WindowState>;
14 fn list_window_labels(&self) -> Vec<String>;
16 fn get_native_handle(&self, label: Option<&str>) -> Result<isize, String>;
23 fn manage_window(&self, label: Option<&str>, action: &str) -> Result<String, String>;
29 fn resize_window(&self, label: Option<&str>, width: u32, height: u32) -> Result<(), String>;
35 fn move_window(&self, label: Option<&str>, x: i32, y: i32) -> Result<(), String>;
41 fn set_window_title(&self, label: Option<&str>, title: &str) -> Result<(), String>;
47}
48
49fn find_window<'a, R: Runtime>(
50 windows: &'a std::collections::HashMap<String, tauri::WebviewWindow<R>>,
51 label: Option<&str>,
52) -> Result<&'a tauri::WebviewWindow<R>, String> {
53 match label {
54 Some(l) => windows
55 .get(l)
56 .ok_or_else(|| format!("window not found: {l}")),
57 None => windows
58 .get("main")
59 .or_else(|| windows.values().find(|w| w.is_visible().unwrap_or(false)))
60 .or_else(|| windows.values().next())
61 .ok_or_else(|| "no window available".to_string()),
62 }
63}
64
65impl<R: Runtime> WebviewBridge for tauri::AppHandle<R> {
66 fn eval_webview(&self, label: Option<&str>, script: &str) -> Result<(), String> {
67 let windows = self.webview_windows();
68 let webview = find_window(&windows, label)?;
69 webview.eval(script).map_err(|e| e.to_string())
70 }
71
72 fn get_window_states(&self, label: Option<&str>) -> Vec<WindowState> {
73 let windows = self.webview_windows();
74 let mut states = Vec::new();
75
76 for (win_label, window) in &windows {
77 if let Some(filter) = label
78 && win_label != filter
79 {
80 continue;
81 }
82
83 let pos = window.outer_position().unwrap_or_default();
84 let size = window.inner_size().unwrap_or_default();
85
86 states.push(WindowState {
87 label: win_label.clone(),
88 title: window.title().unwrap_or_default(),
89 url: window.url().map(|u| u.to_string()).unwrap_or_default(),
90 visible: window.is_visible().unwrap_or(false),
91 focused: window.is_focused().unwrap_or(false),
92 maximized: window.is_maximized().unwrap_or(false),
93 minimized: window.is_minimized().unwrap_or(false),
94 fullscreen: window.is_fullscreen().unwrap_or(false),
95 position: (pos.x, pos.y),
96 size: (size.width, size.height),
97 });
98 }
99
100 states
101 }
102
103 fn list_window_labels(&self) -> Vec<String> {
104 self.webview_windows().keys().cloned().collect()
105 }
106
107 fn get_native_handle(&self, label: Option<&str>) -> Result<isize, String> {
108 use raw_window_handle::{HasWindowHandle, RawWindowHandle};
109
110 let windows = self.webview_windows();
111 let _webview = find_window(&windows, label)?;
112 let handle = _webview.window_handle().map_err(|e| e.to_string())?;
113 match handle.as_raw() {
114 #[cfg(windows)]
115 RawWindowHandle::Win32(h) => Ok(h.hwnd.get()),
116 #[cfg(target_os = "macos")]
117 RawWindowHandle::AppKit(h) => {
118 macos_window_number(h.ns_view.as_ptr())
121 }
122 #[cfg(target_os = "linux")]
123 RawWindowHandle::Xlib(h) => Ok(h.window as isize),
124 #[cfg(target_os = "linux")]
125 RawWindowHandle::Xcb(h) => Ok(h.window.get() as isize),
126 _ => Err("unsupported window handle type on this platform".to_string()),
127 }
128 }
129
130 fn manage_window(&self, label: Option<&str>, action: &str) -> Result<String, String> {
131 let windows = self.webview_windows();
132 let window = find_window(&windows, label)?;
133
134 match action {
135 "minimize" => window.minimize().map_err(|e| e.to_string())?,
136 "unminimize" => window.unminimize().map_err(|e| e.to_string())?,
137 "maximize" => window.maximize().map_err(|e| e.to_string())?,
138 "unmaximize" => window.unmaximize().map_err(|e| e.to_string())?,
139 "close" => window.close().map_err(|e| e.to_string())?,
140 "focus" => window.set_focus().map_err(|e| e.to_string())?,
141 "show" => window.show().map_err(|e| e.to_string())?,
142 "hide" => window.hide().map_err(|e| e.to_string())?,
143 "fullscreen" => window.set_fullscreen(true).map_err(|e| e.to_string())?,
144 "unfullscreen" => window.set_fullscreen(false).map_err(|e| e.to_string())?,
145 "always_on_top" => window.set_always_on_top(true).map_err(|e| e.to_string())?,
146 "not_always_on_top" => window.set_always_on_top(false).map_err(|e| e.to_string())?,
147 _ => return Err(format!("unknown action: {action}")),
148 }
149
150 Ok(format!("{action} executed"))
151 }
152
153 fn resize_window(&self, label: Option<&str>, width: u32, height: u32) -> Result<(), String> {
154 let windows = self.webview_windows();
155 let window = find_window(&windows, label)?;
156
157 window
158 .set_size(tauri::LogicalSize::new(width, height))
159 .map_err(|e| e.to_string())
160 }
161
162 fn move_window(&self, label: Option<&str>, x: i32, y: i32) -> Result<(), String> {
163 let windows = self.webview_windows();
164 let window = find_window(&windows, label)?;
165
166 window
167 .set_position(tauri::LogicalPosition::new(x, y))
168 .map_err(|e| e.to_string())
169 }
170
171 fn set_window_title(&self, label: Option<&str>, title: &str) -> Result<(), String> {
172 let windows = self.webview_windows();
173 let window = find_window(&windows, label)?;
174
175 window.set_title(title).map_err(|e| e.to_string())
176 }
177}
178
179#[cfg(target_os = "macos")]
180#[allow(unsafe_code)]
181fn macos_window_number(ns_view: *mut std::ffi::c_void) -> Result<isize, String> {
182 unsafe extern "C" {
183 fn objc_msgSend(obj: *mut std::ffi::c_void, sel: *mut std::ffi::c_void) -> isize;
184 fn sel_registerName(name: *const std::ffi::c_char) -> *mut std::ffi::c_void;
185 }
186
187 if ns_view.is_null() {
188 return Err("null NSView handle".to_string());
189 }
190
191 unsafe {
195 let sel_window = sel_registerName(c"window".as_ptr());
196 let ns_window = objc_msgSend(ns_view, sel_window);
197 if ns_window == 0 {
198 return Err("NSView has no parent NSWindow".to_string());
199 }
200 let sel_window_number = sel_registerName(c"windowNumber".as_ptr());
201 let ns_window_ptr = ns_window as *mut std::ffi::c_void;
202 let window_number = objc_msgSend(ns_window_ptr, sel_window_number);
203 if window_number <= 0 {
204 return Err(format!("invalid CGWindowID: {window_number}"));
205 }
206 Ok(window_number)
207 }
208}