Skip to main content

webui/
window.rs

1use std::{
2    collections::HashMap,
3    ffi::{c_char, c_int, c_void, CStr, CString},
4    panic::{catch_unwind, AssertUnwindSafe},
5    sync::{Arc, Mutex},
6};
7
8use webui_sys::*;
9
10use crate::{get_window, handler::*, Browser, Event, Runtime, WebUIError, CONTEXT};
11
12pub(crate) struct WindowInner {
13    id: usize,
14    handler: WindowHandler,
15}
16
17#[derive(Default)]
18pub(crate) struct WindowHandler {
19    on_close: Mutex<Option<Arc<dyn CloseHandler>>>,
20    on_event: Mutex<HashMap<usize, Arc<dyn EventHandler>>>,
21    on_file: Mutex<Option<Arc<dyn FileHandlerWindow>>>,
22}
23
24/// The WebUI window type.
25#[repr(transparent)]
26pub struct Window {
27    pub(crate) inner: Arc<WindowInner>,
28}
29
30impl Window {
31    /// Create a new window object.
32    pub fn new() -> Self {
33        let id = unsafe { webui_new_window() };
34        let window = Arc::new(WindowInner {
35            id,
36            handler: Default::default(),
37        });
38        let window_weak = Arc::downgrade(&window);
39        CONTEXT.lock().unwrap().insert(id, window_weak);
40        Self { inner: window }
41    }
42
43    /// Get the id of inner window.
44    pub(crate) fn id(&self) -> usize {
45        self.inner.id
46    }
47
48    /// Show a window using embedded HTML, a URL, a local file, or a local folder. If the window is already open, it will
49    /// be refreshed. This refreshes all clients in multi-client mode.
50    ///
51    /// # Remarks
52    /// To use only a specific browser please use show_browser()
53    /// To use only WebView please use show_webview()
54    pub fn show(&self, content: &str) -> Result<(), WebUIError> {
55        let content = CString::new(content).unwrap();
56        let result = unsafe { webui_show(self.id(), content.as_ptr()) };
57        WebUIError::from_bool(result)
58    }
59
60    /// Show a window using a specific web browser.
61    ///
62    /// # Remarks
63    /// It's recommended to use ChromiumBased browser.
64    /// On macOS, the browser's icon may still appear in the Dock after exit. We recommend using show_webview on macOS to
65    /// avoid this.
66    pub fn show_browser(&self, content: &str, browser: Browser) -> Result<(), WebUIError> {
67        let content = CString::new(content).unwrap();
68        let result = unsafe { webui_show_browser(self.id(), content.as_ptr(), browser as _) };
69        WebUIError::from_bool(result)
70    }
71
72    /// Show a WebView window using embedded HTML, a URL, or a local file. If the window is already open, it will be refreshed.
73    ///
74    /// # Remarks
75    /// WebUI's primary focus is using web browsers as GUI, but if you need to use WebView instead of a web browser, then you
76    /// can use this API, which was added to WebUI starting from v2.5.
77    ///
78    /// - Windows Dependencies: WebView2, and WebView2Loader.dll.
79    /// - Linux Dependencies: WebKit GTK v3.
80    /// - macOS Dependencies: WebKit (WKWebView).
81    pub fn show_webview(&self, content: &str) -> Result<(), WebUIError> {
82        let content = CString::new(content).unwrap();
83        let result = unsafe { webui_show_wv(self.id(), content.as_ptr()) };
84        WebUIError::from_bool(result)
85    }
86
87    /// Start only the local web server without opening a browser window, and return the URL. Useful for headless web app
88    /// scenarios.
89    pub fn start_server(&self, content: &str) -> String {
90        let content = CString::new(content).unwrap();
91        let url = unsafe { webui_start_server(self.id(), content.as_ptr()) };
92        unsafe { CStr::from_ptr(url).to_string_lossy().to_string() }
93    }
94
95    /// Close the window. The window object will still exist and can be shown again later.
96    pub fn close(&self) {
97        unsafe { webui_close(self.id()) }
98    }
99
100    /// Check if the window is still running.
101    pub fn is_shown(&self) -> bool {
102        unsafe { webui_is_shown(self.id()) }
103    }
104
105    /// Bring the window to the front and give it keyboard focus.
106    pub fn focus(&self) {
107        unsafe { webui_focus(self.id()) }
108    }
109
110    /// Minimize the WebView window.
111    pub fn minimize(&self) {
112        unsafe { webui_minimize(self.id()) }
113    }
114
115    /// Maximize a WebView window.
116    pub fn maximize(&self) {
117        unsafe { webui_maximize(self.id()) }
118    }
119
120    /// Set the window in Kiosk mode (Full screen).
121    pub fn set_kiosk(&self, status: bool) {
122        unsafe { webui_set_kiosk(self.id(), status) }
123    }
124
125    /// Set the window size.
126    pub fn set_size(&self, width: u32, height: u32) {
127        unsafe { webui_set_size(self.id(), width, height) }
128    }
129
130    /// Set the minimum allowed window size. The user will not be able to resize the window smaller than this.
131    ///
132    /// # Remarks
133    /// Currently works on Windows only.
134    pub fn set_minimum_size(&self, width: u32, height: u32) {
135        unsafe { webui_set_minimum_size(self.id(), width, height) }
136    }
137
138    /// Set the window position.
139    pub fn set_position(&self, x: u32, y: u32) {
140        unsafe { webui_set_position(self.id(), x, y) }
141    }
142
143    /// Center the window on the screen.
144    ///
145    /// # Remarks
146    /// Call this before show() for best results. Works better with WebView.
147    pub fn set_center(&self) {
148        unsafe { webui_set_center(self.id()) }
149    }
150
151    /// Start the window in hidden mode. The window will be running but not visible.
152    ///
153    /// # Remarks
154    /// Should be called before show().
155    pub fn set_hide(&self, hide: bool) {
156        unsafe { webui_set_hide(self.id(), hide) }
157    }
158
159    /// Remove the window frame and title bar (borderless/frameless mode).
160    ///
161    /// # Remarks
162    /// Works with WebView windows only.
163    pub fn set_frameless(&self, frameless: bool) {
164        unsafe { webui_set_frameless(self.id(), frameless) }
165    }
166
167    /// Enable or disable window background transparency.
168    ///
169    /// # Remarks
170    /// Works with WebView windows only. The web content must also use a transparent/semi-transparent background for this
171    /// to be visible.
172    pub fn set_transparent(&self, transparent: bool) {
173        unsafe { webui_set_transparent(self.id(), transparent) }
174    }
175
176    /// Control whether the user can resize the window.
177    ///
178    /// # Remarks
179    /// Works with WebView windows only.
180    pub fn set_resizable(&self, resizable: bool) {
181        unsafe { webui_set_resizable(self.id(), resizable) }
182    }
183
184    /// Set the default embedded HTML favicon. The icon is served automatically by WebUI's built-in server.
185    pub fn set_icon(&self, icon: &str, icon_type: &str) {
186        let icon = CString::new(icon).unwrap();
187        let icon_type = CString::new(icon_type).unwrap();
188        unsafe { webui_set_icon(self.id(), icon.as_ptr(), icon_type.as_ptr()) }
189    }
190
191    /// Mark the window as supporting high-contrast mode. Use this together with CSS to build a better high-contrast theme.
192    pub fn set_high_contrast(&self, high_contrast: bool) {
193        unsafe { webui_set_high_contrast(self.id(), high_contrast) }
194    }
195
196    /// Check if the operating system is currently using a high-contrast theme.
197    pub fn is_high_contrast(&self) -> bool {
198        unsafe { webui_is_high_contrast() }
199    }
200
201    /// Add custom command-line parameters to the browser launch command. This can be used, for example, to enable remote
202    /// debugging.
203    pub fn set_custom_parameters(&self, params: &str) {
204        let params = CString::new(params).unwrap();
205        unsafe {
206            webui_set_custom_parameters(self.id(), params.as_ptr() as _);
207        }
208    }
209
210    /// Set a callback to intercept the close event of a WebView window. Return false from the handler to prevent the window
211    /// from closing; return true to allow it.
212    pub fn set_close_handler_webview<F>(&self, callback: F)
213    where
214        F: CloseHandler,
215    {
216        extern "C" fn shim(id: usize) -> bool {
217            let f = || -> Option<bool> {
218                let window = get_window(id)?;
219                let callback = window.inner.handler.on_close.lock().ok()?.as_ref().cloned()?;
220                let result = catch_unwind(AssertUnwindSafe(|| callback(&window))).ok()?;
221                Some(result)
222            };
223            f().unwrap_or(true)
224        }
225        *self.inner.handler.on_close.lock().unwrap() = Some(Arc::new(callback));
226        unsafe { webui_set_close_handler_wv(self.id(), Some(shim)) }
227    }
228
229    /// Navigate all connected clients of a window to a specific URL.
230    pub fn navigate(&self, url: &str) {
231        let url = CString::new(url).unwrap();
232        unsafe { webui_navigate(self.id(), url.as_ptr()) }
233    }
234
235    /// Get the current URL of a running window's web server.
236    ///
237    /// # Remarks
238    /// By default, WebUI only allows access to this URL from localhost.
239    pub fn get_url(&self) -> String {
240        let url = unsafe { webui_get_url(self.id()) };
241        let url = unsafe { CStr::from_ptr(url) };
242        url.to_string_lossy().to_string()
243    }
244
245    /// Allow the window's web server URL to be accessible from external devices on the network. By default, WebUI
246    /// only allows access from localhost.
247    pub fn set_public(&self, status: bool) {
248        unsafe { webui_set_public(self.id(), status) }
249    }
250
251    /// Bind an HTML element click event or a JavaScript function call to a C callback. Use an empty element name to catch all events
252    /// from the window.
253    pub fn bind<F>(&self, function: &str, callback: F)
254    where
255        F: EventHandler,
256    {
257        let function = CString::new(function).unwrap();
258        extern "C" fn shim(event: *mut webui_event_t) {
259            let f = || -> Option<()> {
260                let event = (unsafe { Event::new(event) })?;
261                let window = event.window()?;
262                let bind_id = event.bind_id();
263                let callback = window.inner.handler.on_event.lock().ok()?.get(&bind_id).cloned()?;
264                catch_unwind(AssertUnwindSafe(|| callback(&event))).ok()
265            };
266            f();
267        }
268        let id = unsafe { webui_bind(self.id(), function.as_ptr(), Some(shim)) };
269        self.inner
270            .handler
271            .on_event
272            .lock()
273            .unwrap()
274            .insert(id, Arc::new(callback));
275    }
276
277    /// Control whether UI events from this window are processed one at a time in a single blocking thread (true), or each in a new
278    /// non-blocking thread (false). Applies to this window only. Use set_config(ui_event_blocking, ...) to apply to all windows.
279    ///
280    /// # Remarks
281    /// When set to true, the script() API will not return a response until the current event callback has finished.
282    pub fn set_event_blocking(&self, event_blocking: bool) {
283        unsafe { webui_set_event_blocking(self.id(), event_blocking) };
284    }
285
286    /// Set the maximum time in seconds to wait for the browser window to connect after calling show(). A value of 0 means wait forever.
287    pub fn set_timeout(&self, second: usize) {
288        unsafe { webui_set_timeout(second) };
289    }
290
291    /// Run JavaScript and get the response back. Works in single client mode. Make sure your buffer is large enough to hold the response.
292    pub fn script(&self, script: &str, timeout: usize, capacity: usize) -> Result<String, WebUIError> {
293        let mut buffer: Vec<c_char> = Vec::with_capacity(capacity);
294        let script = CString::new(script).unwrap();
295        unsafe { webui_script(self.id(), script.as_ptr(), timeout, buffer.as_mut_ptr(), capacity) }
296            .then(|| unsafe { CStr::from_ptr(buffer.as_ptr()) }.to_string_lossy().to_string())
297            .ok_or(WebUIError::get_last_error())
298    }
299
300    /// Choose between Deno, Bun, and Nodejs as the runtime for .js and .ts files served by the web server.
301    pub fn set_runtime(&self, runtime: Runtime) {
302        unsafe { webui_set_runtime(self.id(), runtime as _) }
303    }
304
305    /// Send raw binary data to a JavaScript function in the UI. Sends to all connected clients.
306    pub fn send_raw<T>(&self, function: &str, data: T)
307    where
308        T: Into<Vec<u8>>,
309    {
310        let function = CString::new(function).unwrap();
311        let data = data.into().into_boxed_slice();
312        unsafe { webui_send_raw(self.id(), function.as_ptr(), data.as_ptr() as _, data.len()) }
313    }
314
315    /// Set a custom handler to serve files. The handler receives the requested filename and must return a complete HTTP response
316    /// (headers + body). Replaces any handler set with set_file_handler_window.
317    pub fn set_file_handler<F>(&self, callback: F)
318    where
319        F: FileHandlerWindow,
320    {
321        extern "C" fn shim(window: usize, filename: *const c_char, length: *mut c_int) -> *const c_void {
322            let callback = || -> Option<*const c_void> {
323                let window = get_window(window)?;
324                let filename = unsafe { CStr::from_ptr(filename).to_string_lossy().to_string() };
325                let callback = window.inner.handler.on_file.lock().ok()?.as_ref().cloned()?;
326                let response = catch_unwind(AssertUnwindSafe(|| callback(&window, &filename))).ok()??;
327
328                let len = response.len();
329                unsafe {
330                    let ptr = webui_malloc(len);
331                    if !ptr.is_null() {
332                        return Option::None;
333                    }
334                    std::ptr::copy_nonoverlapping(response.as_ptr(), ptr as *mut u8, len);
335                    *length = len as c_int;
336                    return Some(ptr as *const c_void);
337                }
338            };
339
340            callback().unwrap_or(std::ptr::null())
341        }
342
343        *self.inner.handler.on_file.lock().unwrap() = Some(Arc::new(callback));
344
345        unsafe {
346            webui_set_file_handler_window(self.id(), Some(shim));
347        }
348    }
349
350    /// Set the web-server root folder path for a specific window.
351    pub fn set_root_folder(&self, path: &str) -> Result<(), WebUIError> {
352        let path = CString::new(path).unwrap();
353        let result = unsafe { webui_set_root_folder(self.id(), path.as_ptr()) };
354        WebUIError::from_bool(result)
355    }
356
357    /// Get the recommended web browser ID to use for this window. If a browser is already in use, returns that browser's ID.
358    pub fn get_best_browser(&self) -> Browser {
359        let browser = unsafe { webui_get_best_browser(self.id()) };
360        unsafe { std::mem::transmute(browser) }
361    }
362
363    /// Set the web browser profile to use. An empty name and path uses the default user profile.
364    pub fn set_profile(&self, name: &str, path: &str) {
365        let name = CString::new(name).unwrap();
366        let path = CString::new(path).unwrap();
367        unsafe { webui_set_profile(self.id(), name.as_ptr(), path.as_ptr()) }
368    }
369
370    /// Set the web browser proxy server.
371    ///
372    /// # Remarks
373    /// Must be called before show().
374    pub fn set_proxy(&self, proxy_server: &str) {
375        let proxy_server = CString::new(proxy_server).unwrap();
376        unsafe { webui_set_proxy(self.id(), proxy_server.as_ptr()) }
377    }
378
379    /// Delete a specific window web-browser local folder profile.
380    ///
381    /// # Remarks
382    /// Recommended to call after all windows are closed, before clean().
383    /// This can break functionality of other windows using the same browser profile.
384    pub fn delete_profile(&self) {
385        unsafe { webui_delete_profile(self.id()) }
386    }
387
388    /// Get the network port used by the running window's web server. Useful for constructing the webui.js URL when
389    /// integrating with an external server.
390    pub fn get_port(&self) -> usize {
391        unsafe { webui_get_port(self.id()) }
392    }
393
394    /// Set a specific network port for the window's web server. Useful when integrating with an external web server like NGINX.
395    pub fn set_port(&self, port: usize) -> Result<(), WebUIError> {
396        let result = unsafe { webui_set_port(self.id(), port) };
397        WebUIError::from_bool(result)
398    }
399
400    /// Get the process ID of the backend application (the parent process). The web browser may create additional child processes.
401    pub fn get_parent_process_id(&self) -> usize {
402        unsafe { webui_get_parent_process_id(self.id()) }
403    }
404
405    /// Get the process ID of the browser window child process. In WebView mode, returns the parent PID because backend and WebView
406    /// run in the same process.
407    pub fn get_child_process_id(&self) -> usize {
408        unsafe { webui_get_child_process_id(self.id()) }
409    }
410
411    /// Get the native window handle. On Windows returns HWND (works with both WebView and web browser). On Linux returns GtkWindow*
412    /// (WebView only).
413    pub fn get_window_handler(&self) -> *mut c_void {
414        unsafe { webui_get_hwnd(self.id()) }
415    }
416
417    /// Run JavaScript without waiting for the response.
418    pub fn run(&self, script: &str) {
419        let script = CString::new(script).unwrap();
420        unsafe { webui_run(self.id(), script.as_ptr()) }
421    }
422}
423
424impl Drop for WindowInner {
425    fn drop(&mut self) {
426        let mut context = CONTEXT.lock().unwrap();
427        unsafe { webui_destroy(self.id) };
428        context.remove(&self.id);
429    }
430}