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