tauri_runtime/window.rs
1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! A layer between raw [`Runtime`] windows and Tauri.
6
7use crate::{
8 Icon, Runtime, UserEvent, WindowDispatch,
9 webview::{DetachedWebview, PendingWebview},
10};
11
12use dpi::PixelUnit;
13use serde::{Deserialize, Deserializer, Serialize};
14use tauri_utils::{
15 Theme,
16 config::{Color, WindowConfig},
17};
18#[cfg(windows)]
19use windows::Win32::Foundation::HWND;
20
21use std::{
22 hash::{Hash, Hasher},
23 marker::PhantomData,
24 path::PathBuf,
25 sync::mpsc::Sender,
26};
27
28/// An event from a window.
29#[derive(Debug, Clone)]
30pub enum WindowEvent {
31 /// The size of the window has changed. Contains the client area's new dimensions.
32 Resized(dpi::PhysicalSize<u32>),
33 /// The position of the window has changed. Contains the window's new position.
34 Moved(dpi::PhysicalPosition<i32>),
35 /// The window has been requested to close.
36 CloseRequested {
37 /// A signal sender. If a `true` value is emitted, the window won't be closed.
38 signal_tx: Sender<bool>,
39 },
40 /// The window has been destroyed.
41 Destroyed,
42 /// The window gained or lost focus.
43 ///
44 /// The parameter is true if the window has gained focus, and false if it has lost focus.
45 Focused(bool),
46 /// The window's scale factor has changed.
47 ///
48 /// The following user actions can cause DPI changes:
49 ///
50 /// - Changing the display's resolution.
51 /// - Changing the display's scale factor (e.g. in Control Panel on Windows).
52 /// - Moving the window to a display with a different scale factor.
53 ScaleFactorChanged {
54 /// The new scale factor.
55 scale_factor: f64,
56 /// The window inner size.
57 new_inner_size: dpi::PhysicalSize<u32>,
58 },
59 /// An event associated with the drag and drop action.
60 DragDrop(DragDropEvent),
61 /// The system window theme has changed.
62 ///
63 /// Applications might wish to react to this to change the theme of the content of the window when the system changes the window theme.
64 ThemeChanged(Theme),
65
66 /// Emitted when the application has been suspended.
67 ///
68 /// ## Platform-specific
69 ///
70 /// - **Android**: This is triggered by `onPause` method of the Activity.
71 /// - **iOS**: This is triggered by `applicationWillResignActive` method of the UIApplicationDelegate.
72 /// - **Linux / macOS / Windows**: Unsupported.
73 #[cfg(mobile)]
74 #[cfg_attr(docsrs, doc(cfg(any(target_os = "android", target_os = "ios"))))]
75 Suspended,
76
77 /// Emitted when the application has been resumed.
78 ///
79 /// ## Platform-specific
80 ///
81 /// - **Android**: This is triggered by `onResume` method of the Activity. The first onResume() is ignored to match the iOS implementation, since that is called on activity creation.
82 /// - **iOS**: This is triggered by `applicationWillEnterForeground` method of the UIApplicationDelegate.
83 /// - **Linux / macOS / Windows**: Unsupported.
84 #[cfg(mobile)]
85 #[cfg_attr(docsrs, doc(cfg(any(target_os = "android", target_os = "ios"))))]
86 Resumed,
87}
88
89/// An event from a window.
90#[derive(Debug, Clone)]
91pub enum WebviewEvent {
92 /// An event associated with the drag and drop action.
93 DragDrop(DragDropEvent),
94}
95
96/// The drag drop event payload.
97#[derive(Debug, Clone)]
98#[non_exhaustive]
99pub enum DragDropEvent {
100 /// A drag operation has entered the webview.
101 Enter {
102 /// List of paths that are being dragged onto the webview.
103 paths: Vec<PathBuf>,
104 /// The position of the mouse cursor.
105 position: dpi::PhysicalPosition<f64>,
106 },
107 /// A drag operation is moving over the webview.
108 Over {
109 /// The position of the mouse cursor.
110 position: dpi::PhysicalPosition<f64>,
111 },
112 /// The file(s) have been dropped onto the webview.
113 Drop {
114 /// List of paths that are being dropped onto the window.
115 paths: Vec<PathBuf>,
116 /// The position of the mouse cursor.
117 position: dpi::PhysicalPosition<f64>,
118 },
119 /// The drag operation has been cancelled or left the window.
120 Leave,
121}
122
123/// Describes the appearance of the mouse cursor.
124#[non_exhaustive]
125#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
126pub enum CursorIcon {
127 /// The platform-dependent default cursor.
128 #[default]
129 Default,
130 /// A simple crosshair.
131 Crosshair,
132 /// A hand (often used to indicate links in web browsers).
133 Hand,
134 /// Self explanatory.
135 Arrow,
136 /// Indicates something is to be moved.
137 Move,
138 /// Indicates text that may be selected or edited.
139 Text,
140 /// Program busy indicator.
141 Wait,
142 /// Help indicator (often rendered as a "?")
143 Help,
144 /// Progress indicator. Shows that processing is being done. But in contrast
145 /// with "Wait" the user may still interact with the program. Often rendered
146 /// as a spinning beach ball, or an arrow with a watch or hourglass.
147 Progress,
148
149 /// Cursor showing that something cannot be done.
150 NotAllowed,
151 ContextMenu,
152 Cell,
153 VerticalText,
154 Alias,
155 Copy,
156 NoDrop,
157 /// Indicates something can be grabbed.
158 Grab,
159 /// Indicates something is grabbed.
160 Grabbing,
161 AllScroll,
162 ZoomIn,
163 ZoomOut,
164
165 /// Indicate that some edge is to be moved. For example, the 'SeResize' cursor
166 /// is used when the movement starts from the south-east corner of the box.
167 EResize,
168 NResize,
169 NeResize,
170 NwResize,
171 SResize,
172 SeResize,
173 SwResize,
174 WResize,
175 EwResize,
176 NsResize,
177 NeswResize,
178 NwseResize,
179 ColResize,
180 RowResize,
181}
182
183impl<'de> Deserialize<'de> for CursorIcon {
184 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
185 where
186 D: Deserializer<'de>,
187 {
188 let s = String::deserialize(deserializer)?;
189 Ok(match s.to_lowercase().as_str() {
190 "default" => CursorIcon::Default,
191 "crosshair" => CursorIcon::Crosshair,
192 "hand" => CursorIcon::Hand,
193 "arrow" => CursorIcon::Arrow,
194 "move" => CursorIcon::Move,
195 "text" => CursorIcon::Text,
196 "wait" => CursorIcon::Wait,
197 "help" => CursorIcon::Help,
198 "progress" => CursorIcon::Progress,
199 "notallowed" => CursorIcon::NotAllowed,
200 "contextmenu" => CursorIcon::ContextMenu,
201 "cell" => CursorIcon::Cell,
202 "verticaltext" => CursorIcon::VerticalText,
203 "alias" => CursorIcon::Alias,
204 "copy" => CursorIcon::Copy,
205 "nodrop" => CursorIcon::NoDrop,
206 "grab" => CursorIcon::Grab,
207 "grabbing" => CursorIcon::Grabbing,
208 "allscroll" => CursorIcon::AllScroll,
209 "zoomin" => CursorIcon::ZoomIn,
210 "zoomout" => CursorIcon::ZoomOut,
211 "eresize" => CursorIcon::EResize,
212 "nresize" => CursorIcon::NResize,
213 "neresize" => CursorIcon::NeResize,
214 "nwresize" => CursorIcon::NwResize,
215 "sresize" => CursorIcon::SResize,
216 "seresize" => CursorIcon::SeResize,
217 "swresize" => CursorIcon::SwResize,
218 "wresize" => CursorIcon::WResize,
219 "ewresize" => CursorIcon::EwResize,
220 "nsresize" => CursorIcon::NsResize,
221 "neswresize" => CursorIcon::NeswResize,
222 "nwseresize" => CursorIcon::NwseResize,
223 "colresize" => CursorIcon::ColResize,
224 "rowresize" => CursorIcon::RowResize,
225 _ => CursorIcon::Default,
226 })
227 }
228}
229
230/// Window size constraints
231#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize)]
232#[serde(rename_all = "camelCase")]
233pub struct WindowSizeConstraints {
234 /// The minimum width a window can be, If this is `None`, the window will have no minimum width.
235 ///
236 /// The default is `None`.
237 pub min_width: Option<PixelUnit>,
238 /// The minimum height a window can be, If this is `None`, the window will have no minimum height.
239 ///
240 /// The default is `None`.
241 pub min_height: Option<PixelUnit>,
242 /// The maximum width a window can be, If this is `None`, the window will have no maximum width.
243 ///
244 /// The default is `None`.
245 pub max_width: Option<PixelUnit>,
246 /// The maximum height a window can be, If this is `None`, the window will have no maximum height.
247 ///
248 /// The default is `None`.
249 pub max_height: Option<PixelUnit>,
250}
251
252/// Do **NOT** implement this trait except for use in a custom [`Runtime`]
253///
254/// This trait is separate from [`WindowBuilder`] to prevent "accidental" implementation.
255pub trait WindowBuilderBase: std::fmt::Debug + Clone + Sized {}
256
257/// A builder for all attributes related to a single window.
258///
259/// This trait is only meant to be implemented by a custom [`Runtime`]
260/// and not by applications.
261pub trait WindowBuilder: WindowBuilderBase {
262 /// Initializes a new window attributes builder.
263 fn new() -> Self;
264
265 /// Initializes a new window builder from a [`WindowConfig`]
266 fn with_config(config: &WindowConfig) -> Self;
267
268 /// Show window in the center of the screen.
269 #[must_use]
270 fn center(self) -> Self;
271
272 /// The initial position of the window in logical pixels.
273 #[must_use]
274 fn position(self, x: f64, y: f64) -> Self;
275
276 /// Window size in logical pixels.
277 #[must_use]
278 fn inner_size(self, width: f64, height: f64) -> Self;
279
280 /// Window min inner size in logical pixels.
281 #[must_use]
282 fn min_inner_size(self, min_width: f64, min_height: f64) -> Self;
283
284 /// Window max inner size in logical pixels.
285 #[must_use]
286 fn max_inner_size(self, max_width: f64, max_height: f64) -> Self;
287
288 /// Window inner size constraints.
289 #[must_use]
290 fn inner_size_constraints(self, constraints: WindowSizeConstraints) -> Self;
291
292 /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size) on creation
293 ///
294 /// ## Platform-specific
295 ///
296 /// - **iOS / Android:** Unsupported.
297 #[must_use]
298 fn prevent_overflow(self) -> Self;
299
300 /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size)
301 /// on creation with a margin
302 ///
303 /// ## Platform-specific
304 ///
305 /// - **iOS / Android:** Unsupported.
306 #[must_use]
307 fn prevent_overflow_with_margin(self, margin: dpi::Size) -> Self;
308
309 /// Whether the window is resizable or not.
310 /// When resizable is set to false, native window's maximize button is automatically disabled.
311 #[must_use]
312 fn resizable(self, resizable: bool) -> Self;
313
314 /// Whether the window's native maximize button is enabled or not.
315 /// If resizable is set to false, this setting is ignored.
316 ///
317 /// ## Platform-specific
318 ///
319 /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode.
320 /// - **Linux / iOS / Android:** Unsupported.
321 #[must_use]
322 fn maximizable(self, maximizable: bool) -> Self;
323
324 /// Whether the window's native minimize button is enabled or not.
325 ///
326 /// ## Platform-specific
327 ///
328 /// - **Linux / iOS / Android:** Unsupported.
329 #[must_use]
330 fn minimizable(self, minimizable: bool) -> Self;
331
332 /// Whether the window's native close button is enabled or not.
333 ///
334 /// ## Platform-specific
335 ///
336 /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button.
337 /// Depending on the system, this function may not have any effect when called on a window that is already visible"
338 /// - **iOS / Android:** Unsupported.
339 #[must_use]
340 fn closable(self, closable: bool) -> Self;
341
342 /// The title of the window in the title bar.
343 #[must_use]
344 fn title<S: Into<String>>(self, title: S) -> Self;
345
346 /// Whether to start the window in fullscreen or not.
347 #[must_use]
348 fn fullscreen(self, fullscreen: bool) -> Self;
349
350 /// Whether the window will be initially focused or not.
351 #[must_use]
352 fn focused(self, focused: bool) -> Self;
353
354 /// Whether the window will be focusable or not.
355 #[must_use]
356 fn focusable(self, focusable: bool) -> Self;
357
358 /// Whether the window should be maximized upon creation.
359 #[must_use]
360 fn maximized(self, maximized: bool) -> Self;
361
362 /// Whether the window should be immediately visible upon creation.
363 #[must_use]
364 fn visible(self, visible: bool) -> Self;
365
366 /// Whether the window should be transparent. If this is true, writing colors
367 /// with alpha values different than `1.0` will produce a transparent window.
368 ///
369 /// On Windows, using `no_redirection_bitmap` can help avoid a white flash when
370 /// creating a transparent window.
371 #[must_use]
372 fn transparent(self, transparent: bool) -> Self;
373
374 /// Whether the window should have borders and bars.
375 #[must_use]
376 fn decorations(self, decorations: bool) -> Self;
377
378 /// Whether the window should always be below other windows.
379 #[must_use]
380 fn always_on_bottom(self, always_on_bottom: bool) -> Self;
381
382 /// Whether the window should always be on top of other windows.
383 #[must_use]
384 fn always_on_top(self, always_on_top: bool) -> Self;
385
386 /// Whether the window should be visible on all workspaces or virtual desktops.
387 #[must_use]
388 fn visible_on_all_workspaces(self, visible_on_all_workspaces: bool) -> Self;
389
390 /// Prevents the window contents from being captured by other apps.
391 #[must_use]
392 fn content_protected(self, protected: bool) -> Self;
393
394 /// Sets the window icon.
395 fn icon(self, icon: Icon) -> crate::Result<Self>;
396
397 /// Sets whether or not the window icon should be added to the taskbar.
398 #[must_use]
399 fn skip_taskbar(self, skip: bool) -> Self;
400
401 /// Set the window background color.
402 #[must_use]
403 fn background_color(self, color: Color) -> Self;
404
405 /// Sets whether or not the window has shadow.
406 ///
407 /// ## Platform-specific
408 ///
409 /// - **Windows:**
410 /// - `false` has no effect on decorated window, shadows are always ON.
411 /// - `true` will make undecorated window have a 1px white border,
412 /// and on Windows 11, it will have a rounded corners.
413 /// - **Linux:** Unsupported.
414 #[must_use]
415 fn shadow(self, enable: bool) -> Self;
416
417 /// Set an owner to the window to be created.
418 ///
419 /// From MSDN:
420 /// - An owned window is always above its owner in the z-order.
421 /// - The system automatically destroys an owned window when its owner is destroyed.
422 /// - An owned window is hidden when its owner is minimized.
423 ///
424 /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
425 #[cfg(windows)]
426 #[must_use]
427 fn owner(self, owner: HWND) -> Self;
428
429 /// Sets a parent to the window to be created.
430 ///
431 /// A child window has the WS_CHILD style and is confined to the client area of its parent window.
432 ///
433 /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
434 #[cfg(windows)]
435 #[must_use]
436 fn parent(self, parent: HWND) -> Self;
437
438 /// Sets a parent to the window to be created.
439 ///
440 /// See <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>
441 #[cfg(target_os = "macos")]
442 #[must_use]
443 fn parent(self, parent: *mut std::ffi::c_void) -> Self;
444
445 /// Sets the window to be created transient for parent.
446 ///
447 /// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>
448 #[cfg(any(
449 target_os = "linux",
450 target_os = "dragonfly",
451 target_os = "freebsd",
452 target_os = "netbsd",
453 target_os = "openbsd"
454 ))]
455 fn transient_for(self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self;
456
457 /// Enables or disables drag and drop support.
458 #[cfg(windows)]
459 #[must_use]
460 fn drag_and_drop(self, enabled: bool) -> Self;
461
462 /// Hide the titlebar. Titlebar buttons will still be visible.
463 #[cfg(target_os = "macos")]
464 #[must_use]
465 fn title_bar_style(self, style: tauri_utils::TitleBarStyle) -> Self;
466
467 /// Change the position of the window controls on macOS.
468 ///
469 /// Requires titleBarStyle: Overlay and decorations: true.
470 #[cfg(target_os = "macos")]
471 #[must_use]
472 fn traffic_light_position<P: Into<dpi::Position>>(self, position: P) -> Self;
473
474 /// Hide the window title.
475 #[cfg(target_os = "macos")]
476 #[must_use]
477 fn hidden_title(self, hidden: bool) -> Self;
478
479 /// Defines the window [tabbing identifier] for macOS.
480 ///
481 /// Windows with matching tabbing identifiers will be grouped together.
482 /// If the tabbing identifier is not set, automatic tabbing will be disabled.
483 ///
484 /// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
485 #[cfg(target_os = "macos")]
486 #[must_use]
487 fn tabbing_identifier(self, identifier: &str) -> Self;
488
489 /// Forces a theme or uses the system settings if None was provided.
490 fn theme(self, theme: Option<Theme>) -> Self;
491
492 /// Whether the icon was set or not.
493 fn has_icon(&self) -> bool;
494
495 fn get_theme(&self) -> Option<Theme>;
496
497 /// Sets custom name for Windows' window class. **Windows only**.
498 #[must_use]
499 fn window_classname<S: Into<String>>(self, window_classname: S) -> Self;
500
501 /// This sets `WS_EX_NOREDIRECTIONBITMAP`.
502 ///
503 /// This can avoid the white flash that may appear before the webview content is rendered
504 /// when using a transparent window. **Windows only**.
505 #[must_use]
506 fn no_redirection_bitmap(self, enable: bool) -> Self;
507
508 /// The name of the activity to create for this webview window.
509 #[cfg(target_os = "android")]
510 fn activity_name<S: Into<String>>(self, class_name: S) -> Self;
511
512 /// Sets the name of the activity that is creating this webview window.
513 ///
514 /// This is important to determine which stack the activity will belong to.
515 #[cfg(target_os = "android")]
516 fn created_by_activity_name<S: Into<String>>(self, class_name: S) -> Self;
517
518 /// Sets the identifier of the UIScene that is requesting the creation of this new scene,
519 /// establishing a relationship between the two scenes.
520 ///
521 /// By default the system uses the foreground scene.
522 #[cfg(target_os = "ios")]
523 fn requested_by_scene_identifier<S: Into<String>>(self, identifier: S) -> Self;
524}
525
526/// A window that has yet to be built.
527pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
528 /// The label that the window will be named.
529 pub label: String,
530
531 /// The [`WindowBuilder`] that the window will be created with.
532 pub window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,
533
534 /// The webview that gets added to the window. Optional in case you want to use child webviews or other window content instead.
535 pub webview: Option<PendingWebview<T, R>>,
536}
537
538pub fn is_label_valid(label: &str) -> bool {
539 label
540 .chars()
541 .all(|c| char::is_alphanumeric(c) || c == '-' || c == '/' || c == ':' || c == '_')
542}
543
544pub fn assert_label_is_valid(label: &str) {
545 assert!(
546 is_label_valid(label),
547 "Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`."
548 );
549}
550
551impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
552 /// Create a new [`PendingWindow`] with a label from the given [`WindowBuilder`].
553 pub fn new(
554 window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,
555 label: impl Into<String>,
556 ) -> crate::Result<Self> {
557 let label = label.into();
558 if !is_label_valid(&label) {
559 Err(crate::Error::InvalidWindowLabel)
560 } else {
561 Ok(Self {
562 window_builder,
563 label,
564 webview: None,
565 })
566 }
567 }
568
569 /// Sets a webview to be created on the window.
570 pub fn set_webview(&mut self, webview: PendingWebview<T, R>) -> &mut Self {
571 self.webview.replace(webview);
572 self
573 }
574}
575
576/// Identifier of a window.
577#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
578pub struct WindowId(u32);
579
580impl From<u32> for WindowId {
581 fn from(value: u32) -> Self {
582 Self(value)
583 }
584}
585
586/// A window that is not yet managed by Tauri.
587#[derive(Debug)]
588pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
589 /// The identifier of the window.
590 pub id: WindowId,
591 /// Name of the window
592 pub label: String,
593
594 /// The [`WindowDispatch`] associated with the window.
595 pub dispatcher: R::WindowDispatcher,
596
597 /// The webview dispatcher in case this window has an attached webview.
598 pub webview: Option<DetachedWindowWebview<T, R>>,
599}
600
601/// A detached webview associated with a window.
602#[derive(Debug)]
603pub struct DetachedWindowWebview<T: UserEvent, R: Runtime<T>> {
604 pub webview: DetachedWebview<T, R>,
605 pub use_https_scheme: bool,
606}
607
608impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindowWebview<T, R> {
609 fn clone(&self) -> Self {
610 Self {
611 webview: self.webview.clone(),
612 use_https_scheme: self.use_https_scheme,
613 }
614 }
615}
616
617impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
618 fn clone(&self) -> Self {
619 Self {
620 id: self.id,
621 label: self.label.clone(),
622 dispatcher: self.dispatcher.clone(),
623 webview: self.webview.clone(),
624 }
625 }
626}
627
628impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWindow<T, R> {
629 /// Only use the [`DetachedWindow`]'s label to represent its hash.
630 fn hash<H: Hasher>(&self, state: &mut H) {
631 self.label.hash(state)
632 }
633}
634
635impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWindow<T, R> {}
636impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWindow<T, R> {
637 /// Only use the [`DetachedWindow`]'s label to compare equality.
638 fn eq(&self, other: &Self) -> bool {
639 self.label.eq(&other.label)
640 }
641}
642
643/// A raw window type that contains fields to access
644/// the HWND on Windows, gtk::ApplicationWindow on Linux
645pub struct RawWindow<'a> {
646 #[cfg(windows)]
647 pub hwnd: isize,
648 #[cfg(any(
649 target_os = "linux",
650 target_os = "dragonfly",
651 target_os = "freebsd",
652 target_os = "netbsd",
653 target_os = "openbsd"
654 ))]
655 pub gtk_window: &'a gtk::ApplicationWindow,
656 #[cfg(any(
657 target_os = "linux",
658 target_os = "dragonfly",
659 target_os = "freebsd",
660 target_os = "netbsd",
661 target_os = "openbsd"
662 ))]
663 pub default_vbox: Option<&'a gtk::Box>,
664 pub _marker: &'a PhantomData<()>,
665}