Skip to main content

dioxus_desktop/
config.rs

1use dioxus_core::{LaunchConfig, VirtualDom};
2use std::path::PathBuf;
3use std::{borrow::Cow, sync::Arc};
4use tao::window::{Icon, WindowBuilder};
5use tao::{
6    event_loop::{EventLoop, EventLoopWindowTarget},
7    window::Window,
8};
9use wry::http::{Request as HttpRequest, Response as HttpResponse};
10use wry::{RequestAsyncResponder, WebViewId};
11
12use crate::ipc::UserWindowEvent;
13use crate::menubar::{default_menu_bar, DioxusMenu};
14
15type CustomEventHandler = Box<
16    dyn 'static
17        + for<'a> FnMut(
18            &tao::event::Event<'a, UserWindowEvent>,
19            &EventLoopWindowTarget<UserWindowEvent>,
20        ),
21>;
22
23/// A function taking a URL and returning whether the webview should navigate to it or open it in
24/// the browser. If missing in the config, all URLs will be allowed.
25type NavigationHandler = Box<dyn Fn(&str) -> bool + 'static>;
26
27/// The closing behaviour of specific application window.
28#[derive(Debug, Copy, Clone, Eq, PartialEq)]
29#[non_exhaustive]
30pub enum WindowCloseBehaviour {
31    /// Window will hide instead of closing
32    WindowHides,
33
34    /// Window will close
35    WindowCloses,
36}
37
38/// The state of the menu builder. We need to keep track of if the state is default
39/// so we only swap out the default menu bar when decorations are disabled
40pub(crate) enum MenuBuilderState {
41    Unset,
42    Set(Option<DioxusMenu>),
43}
44
45impl From<MenuBuilderState> for Option<DioxusMenu> {
46    fn from(val: MenuBuilderState) -> Self {
47        match val {
48            MenuBuilderState::Unset => Some(default_menu_bar()),
49            MenuBuilderState::Set(menu) => menu,
50        }
51    }
52}
53
54/// The configuration for the desktop application.
55pub struct Config {
56    pub(crate) event_loop: Option<EventLoop<UserWindowEvent>>,
57    pub(crate) window: WindowBuilder,
58    pub(crate) as_child_window: bool,
59    pub(crate) menu: MenuBuilderState,
60    pub(crate) protocols: Vec<WryProtocol>,
61    pub(crate) asynchronous_protocols: Vec<AsyncWryProtocol>,
62    pub(crate) pre_rendered: Option<String>,
63    pub(crate) disable_context_menu: bool,
64    pub(crate) resource_dir: Option<PathBuf>,
65    pub(crate) data_dir: Option<PathBuf>,
66    pub(crate) custom_head: Option<String>,
67    pub(crate) custom_index: Option<String>,
68    pub(crate) root_name: String,
69    pub(crate) background_color: Option<(u8, u8, u8, u8)>,
70    pub(crate) exit_on_last_window_close: bool,
71    pub(crate) window_close_behavior: WindowCloseBehaviour,
72    pub(crate) custom_event_handler: Option<CustomEventHandler>,
73    pub(crate) disable_file_drop_handler: bool,
74    pub(crate) disable_dma_buf_on_wayland: bool,
75    pub(crate) additional_windows_args: Option<String>,
76    pub(crate) tray_icon_show_window_on_click: bool,
77    pub(crate) navigation_handler: Option<NavigationHandler>,
78
79    #[allow(clippy::type_complexity)]
80    pub(crate) on_window: Option<Box<dyn FnMut(Arc<Window>, &mut VirtualDom) + 'static>>,
81}
82
83impl LaunchConfig for Config {}
84
85pub(crate) type WryProtocol = (
86    String,
87    Box<dyn Fn(WebViewId, HttpRequest<Vec<u8>>) -> HttpResponse<Cow<'static, [u8]>> + 'static>,
88);
89
90pub(crate) type AsyncWryProtocol = (
91    String,
92    Box<dyn Fn(WebViewId, HttpRequest<Vec<u8>>, RequestAsyncResponder) + 'static>,
93);
94
95impl Config {
96    /// Initializes a new `WindowBuilder` with default values.
97    #[inline]
98    pub fn new() -> Self {
99        let mut window: WindowBuilder = WindowBuilder::new()
100            .with_title(dioxus_cli_config::app_title().unwrap_or_else(|| "Dioxus App".to_string()));
101
102        // During development we want the window to be on top so we can see it while we work
103        let always_on_top = dioxus_cli_config::always_on_top().unwrap_or(true);
104
105        if cfg!(debug_assertions) {
106            window = window.with_always_on_top(always_on_top);
107        }
108
109        Self {
110            window,
111            as_child_window: false,
112            event_loop: None,
113            menu: MenuBuilderState::Unset,
114            protocols: Vec::new(),
115            asynchronous_protocols: Vec::new(),
116            pre_rendered: None,
117            disable_context_menu: !cfg!(debug_assertions),
118            resource_dir: None,
119            data_dir: None,
120            custom_head: None,
121            custom_index: None,
122            root_name: "main".to_string(),
123            background_color: None,
124            exit_on_last_window_close: true,
125            window_close_behavior: WindowCloseBehaviour::WindowCloses,
126            custom_event_handler: None,
127            disable_file_drop_handler: false,
128            disable_dma_buf_on_wayland: true,
129            on_window: None,
130            additional_windows_args: None,
131            tray_icon_show_window_on_click: true,
132            navigation_handler: None,
133        }
134    }
135
136    /// set the directory from which assets will be searched in release mode
137    pub fn with_resource_directory(mut self, path: impl Into<PathBuf>) -> Self {
138        self.resource_dir = Some(path.into());
139        self
140    }
141
142    /// Set the directory where WebView2 stores its user data (cookies, cache, IndexedDB, etc.).
143    ///
144    /// If not set, Dioxus automatically chooses a sensible default:
145    /// - **Windows:** `%LOCALAPPDATA%/<exe_name>` — this avoids the WebView2 default of
146    ///   placing data next to the executable, which fails in read-only locations like
147    ///   `Program Files` or on certain drive types (e.g. ReFS dev drives).
148    /// - **macOS/Linux:** managed by WebKit automatically.
149    ///
150    /// You typically only need this if you want multiple apps to share a WebView2 profile
151    /// or need the data in a specific location for compliance/IT policy reasons.
152    pub fn with_data_directory(mut self, path: impl Into<PathBuf>) -> Self {
153        self.data_dir = Some(path.into());
154        self
155    }
156
157    /// Set whether or not the right-click context menu should be disabled.
158    pub fn with_disable_context_menu(mut self, disable: bool) -> Self {
159        self.disable_context_menu = disable;
160        self
161    }
162
163    /// Set whether or not the file drop handler should be disabled.
164    /// On Windows the drop handler must be disabled for HTML drag and drop APIs to work.
165    pub fn with_disable_drag_drop_handler(mut self, disable: bool) -> Self {
166        self.disable_file_drop_handler = disable;
167        self
168    }
169
170    /// Set the pre-rendered HTML content
171    pub fn with_prerendered(mut self, content: String) -> Self {
172        self.pre_rendered = Some(content);
173        self
174    }
175
176    /// Set the event loop to be used
177    pub fn with_event_loop(mut self, event_loop: EventLoop<UserWindowEvent>) -> Self {
178        self.event_loop = Some(event_loop);
179        self
180    }
181
182    /// Set the configuration for the window.
183    pub fn with_window(mut self, window: WindowBuilder) -> Self {
184        // We need to do a swap because the window builder only takes itself as muy self
185        self.window = window;
186        // If the decorations are off for the window, remove the menu as well
187        if !self.window.window.decorations && matches!(self.menu, MenuBuilderState::Unset) {
188            self.menu = MenuBuilderState::Set(None);
189        }
190        self
191    }
192
193    /// Set the window as child
194    pub fn with_as_child_window(mut self) -> Self {
195        self.as_child_window = true;
196        self
197    }
198
199    /// When the last window is closed, the application will exit.
200    ///
201    /// This is the default behaviour.
202    ///
203    /// If the last window is hidden, the application will not exit.
204    pub fn with_exits_when_last_window_closes(mut self, exit: bool) -> Self {
205        self.exit_on_last_window_close = exit;
206        self
207    }
208
209    /// Sets the behaviour of the application when the last window is closed.
210    pub fn with_close_behaviour(mut self, behaviour: WindowCloseBehaviour) -> Self {
211        self.window_close_behavior = behaviour;
212        self
213    }
214
215    /// Sets a custom callback to run whenever the event pool receives an event.
216    pub fn with_custom_event_handler(
217        mut self,
218        f: impl FnMut(&tao::event::Event<'_, UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>)
219            + 'static,
220    ) -> Self {
221        self.custom_event_handler = Some(Box::new(f));
222        self
223    }
224
225    /// Set a custom protocol
226    pub fn with_custom_protocol<F>(mut self, name: impl ToString, handler: F) -> Self
227    where
228        F: Fn(WebViewId, HttpRequest<Vec<u8>>) -> HttpResponse<Cow<'static, [u8]>> + 'static,
229    {
230        self.protocols.push((name.to_string(), Box::new(handler)));
231        self
232    }
233
234    /// Set an asynchronous custom protocol
235    ///
236    /// **Example Usage**
237    /// ```rust
238    /// # use wry::http::response::Response as HTTPResponse;
239    /// # use std::borrow::Cow;
240    /// # use dioxus_desktop::Config;
241    /// #
242    /// # fn main() {
243    /// let cfg = Config::new()
244    ///     .with_asynchronous_custom_protocol("asset", |_webview_id, request, responder| {
245    ///         tokio::spawn(async move {
246    ///             responder.respond(
247    ///                 HTTPResponse::builder()
248    ///                     .status(404)
249    ///                     .body(Cow::Borrowed("404 - Not Found".as_bytes()))
250    ///                     .unwrap()
251    ///             );
252    ///         });
253    ///     });
254    /// # }
255    /// ```
256    /// note a key difference between Dioxus and Wry, the protocol name doesn't explicitly need to be a
257    /// [`String`], but needs to implement [`ToString`].
258    ///
259    /// See [`wry`](wry::WebViewBuilder::with_asynchronous_custom_protocol) for more details on implementation
260    pub fn with_asynchronous_custom_protocol<F>(mut self, name: impl ToString, handler: F) -> Self
261    where
262        F: Fn(WebViewId, HttpRequest<Vec<u8>>, RequestAsyncResponder) + 'static,
263    {
264        self.asynchronous_protocols
265            .push((name.to_string(), Box::new(handler)));
266        self
267    }
268
269    /// Set a custom icon for this application
270    pub fn with_icon(mut self, icon: Icon) -> Self {
271        self.window.window.window_icon = Some(icon);
272        self
273    }
274
275    /// Inject additional content into the document's HEAD.
276    ///
277    /// This is useful for loading CSS libraries, JS libraries, etc.
278    pub fn with_custom_head(mut self, head: String) -> Self {
279        self.custom_head = Some(head);
280        self
281    }
282
283    /// Use a custom index.html instead of the default Dioxus one.
284    ///
285    /// Make sure your index.html is valid HTML.
286    ///
287    /// Dioxus injects some loader code into the closing body tag. Your document
288    /// must include a body element!
289    pub fn with_custom_index(mut self, index: String) -> Self {
290        self.custom_index = Some(index);
291        self
292    }
293
294    /// Set the name of the element that Dioxus will use as the root.
295    ///
296    /// This is akin to calling React.render() on the element with the specified name.
297    pub fn with_root_name(mut self, name: impl Into<String>) -> Self {
298        self.root_name = name.into();
299        self
300    }
301
302    /// Sets the background color of the WebView.
303    /// This will be set before the HTML is rendered and can be used to prevent flashing when the page loads.
304    /// Accepts a color in RGBA format
305    pub fn with_background_color(mut self, color: (u8, u8, u8, u8)) -> Self {
306        self.background_color = Some(color);
307        self
308    }
309
310    /// Sets the menu the window will use. This will override the default menu bar.
311    ///
312    /// > Note: Menu will be hidden if
313    /// > [`with_decorations`](tao::window::WindowBuilder::with_decorations)
314    /// > is set to false and passed into [`with_window`](Config::with_window)
315    #[allow(unused)]
316    pub fn with_menu(mut self, menu: impl Into<Option<DioxusMenu>>) -> Self {
317        #[cfg(not(any(target_os = "ios", target_os = "android")))]
318        {
319            if self.window.window.decorations {
320                self.menu = MenuBuilderState::Set(menu.into())
321            }
322        }
323        self
324    }
325
326    /// Allows modifying the window and virtual dom right after they are built, but before the webview is created.
327    ///
328    /// This is important for z-ordering textures in child windows. Note that this callback runs on
329    /// every window creation, so it's up to you to
330    pub fn with_on_window(mut self, f: impl FnMut(Arc<Window>, &mut VirtualDom) + 'static) -> Self {
331        self.on_window = Some(Box::new(f));
332        self
333    }
334
335    /// Set whether or not DMA-BUF usage should be disabled on Wayland.
336    ///
337    /// Defaults to true to avoid issues on some systems. If you want to enable DMA-BUF usage, set this to false.
338    /// See <https://github.com/DioxusLabs/dioxus/issues/4528#issuecomment-3476430611>
339    pub fn with_disable_dma_buf_on_wayland(mut self, disable: bool) -> Self {
340        self.disable_dma_buf_on_wayland = disable;
341        self
342    }
343
344    /// Add additional windows only launch arguments for webview2
345    pub fn with_windows_browser_args(mut self, additional_args: impl ToString) -> Self {
346        self.additional_windows_args = Some(additional_args.to_string());
347        self
348    }
349
350    /// Set whether the main window is shown and focused when the tray icon is left-clicked.
351    ///
352    /// Defaults to `true` (preserves current behavior). Set to `false` for tray/menu bar apps
353    /// that only want to show the context menu when the tray icon is clicked, without
354    /// activating the main window.
355    pub fn with_tray_icon_show_window_on_click(mut self, show: bool) -> Self {
356        self.tray_icon_show_window_on_click = show;
357        self
358    }
359
360    /// Set a custom navigation handler for non-dioxus URLs.
361    /// Return true to allow navigation inside the webview, false to block.
362    pub fn with_navigation_handler(mut self, f: impl Fn(&str) -> bool + 'static) -> Self {
363        self.navigation_handler = Some(Box::new(f));
364        self
365    }
366}
367
368impl Default for Config {
369    fn default() -> Self {
370        Self::new()
371    }
372}
373
374// dirty trick, avoid introducing `image` at runtime
375// TODO: use serde when `Icon` impl serde
376//
377// This function should only be enabled when generating new icons.
378//
379// #[test]
380// #[ignore]
381// fn prepare_default_icon() {
382//     use image::io::Reader as ImageReader;
383//     use image::ImageFormat;
384//     use std::fs::File;
385//     use std::io::Cursor;
386//     use std::io::Write;
387//     use std::path::PathBuf;
388//     let png: &[u8] = include_bytes!("default_icon.png");
389//     let mut reader = ImageReader::new(Cursor::new(png));
390//     reader.set_format(ImageFormat::Png);
391//     let icon = reader.decode().unwrap();
392//     let bin = PathBuf::from(file!())
393//         .parent()
394//         .unwrap()
395//         .join("default_icon.bin");
396//     println!("{:?}", bin);
397//     let mut file = File::create(bin).unwrap();
398//     file.write_all(icon.as_bytes()).unwrap();
399//     println!("({}, {})", icon.width(), icon.height())
400// }