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 webview::{DetachedWebview, PendingWebview},
9 Icon, Runtime, UserEvent, WindowDispatch,
10};
11
12use dpi::PixelUnit;
13use serde::{Deserialize, Deserializer, Serialize};
14use tauri_utils::{
15 config::{Color, WindowConfig},
16 Theme,
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 Suspended,
75
76 /// Emitted when the application has been resumed.
77 ///
78 /// ## Platform-specific
79 ///
80 /// - **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.
81 /// - **iOS**: This is triggered by `applicationWillEnterForeground` method of the UIApplicationDelegate.
82 /// - **Linux / macOS / Windows**: Unsupported.
83 #[cfg(mobile)]
84 Resumed,
85}
86
87/// An event from a window.
88#[derive(Debug, Clone)]
89pub enum WebviewEvent {
90 /// An event associated with the drag and drop action.
91 DragDrop(DragDropEvent),
92}
93
94/// The drag drop event payload.
95#[derive(Debug, Clone)]
96#[non_exhaustive]
97pub enum DragDropEvent {
98 /// A drag operation has entered the webview.
99 Enter {
100 /// List of paths that are being dragged onto the webview.
101 paths: Vec<PathBuf>,
102 /// The position of the mouse cursor.
103 position: dpi::PhysicalPosition<f64>,
104 },
105 /// A drag operation is moving over the webview.
106 Over {
107 /// The position of the mouse cursor.
108 position: dpi::PhysicalPosition<f64>,
109 },
110 /// The file(s) have been dropped onto the webview.
111 Drop {
112 /// List of paths that are being dropped onto the window.
113 paths: Vec<PathBuf>,
114 /// The position of the mouse cursor.
115 position: dpi::PhysicalPosition<f64>,
116 },
117 /// The drag operation has been cancelled or left the window.
118 Leave,
119}
120
121/// Describes the appearance of the mouse cursor.
122#[non_exhaustive]
123#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
124pub enum CursorIcon {
125 /// The platform-dependent default cursor.
126 #[default]
127 Default,
128 /// A simple crosshair.
129 Crosshair,
130 /// A hand (often used to indicate links in web browsers).
131 Hand,
132 /// Self explanatory.
133 Arrow,
134 /// Indicates something is to be moved.
135 Move,
136 /// Indicates text that may be selected or edited.
137 Text,
138 /// Program busy indicator.
139 Wait,
140 /// Help indicator (often rendered as a "?")
141 Help,
142 /// Progress indicator. Shows that processing is being done. But in contrast
143 /// with "Wait" the user may still interact with the program. Often rendered
144 /// as a spinning beach ball, or an arrow with a watch or hourglass.
145 Progress,
146
147 /// Cursor showing that something cannot be done.
148 NotAllowed,
149 ContextMenu,
150 Cell,
151 VerticalText,
152 Alias,
153 Copy,
154 NoDrop,
155 /// Indicates something can be grabbed.
156 Grab,
157 /// Indicates something is grabbed.
158 Grabbing,
159 AllScroll,
160 ZoomIn,
161 ZoomOut,
162
163 /// Indicate that some edge is to be moved. For example, the 'SeResize' cursor
164 /// is used when the movement starts from the south-east corner of the box.
165 EResize,
166 NResize,
167 NeResize,
168 NwResize,
169 SResize,
170 SeResize,
171 SwResize,
172 WResize,
173 EwResize,
174 NsResize,
175 NeswResize,
176 NwseResize,
177 ColResize,
178 RowResize,
179}
180
181impl<'de> Deserialize<'de> for CursorIcon {
182 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
183 where
184 D: Deserializer<'de>,
185 {
186 let s = String::deserialize(deserializer)?;
187 Ok(match s.to_lowercase().as_str() {
188 "default" => CursorIcon::Default,
189 "crosshair" => CursorIcon::Crosshair,
190 "hand" => CursorIcon::Hand,
191 "arrow" => CursorIcon::Arrow,
192 "move" => CursorIcon::Move,
193 "text" => CursorIcon::Text,
194 "wait" => CursorIcon::Wait,
195 "help" => CursorIcon::Help,
196 "progress" => CursorIcon::Progress,
197 "notallowed" => CursorIcon::NotAllowed,
198 "contextmenu" => CursorIcon::ContextMenu,
199 "cell" => CursorIcon::Cell,
200 "verticaltext" => CursorIcon::VerticalText,
201 "alias" => CursorIcon::Alias,
202 "copy" => CursorIcon::Copy,
203 "nodrop" => CursorIcon::NoDrop,
204 "grab" => CursorIcon::Grab,
205 "grabbing" => CursorIcon::Grabbing,
206 "allscroll" => CursorIcon::AllScroll,
207 "zoomin" => CursorIcon::ZoomIn,
208 "zoomout" => CursorIcon::ZoomOut,
209 "eresize" => CursorIcon::EResize,
210 "nresize" => CursorIcon::NResize,
211 "neresize" => CursorIcon::NeResize,
212 "nwresize" => CursorIcon::NwResize,
213 "sresize" => CursorIcon::SResize,
214 "seresize" => CursorIcon::SeResize,
215 "swresize" => CursorIcon::SwResize,
216 "wresize" => CursorIcon::WResize,
217 "ewresize" => CursorIcon::EwResize,
218 "nsresize" => CursorIcon::NsResize,
219 "neswresize" => CursorIcon::NeswResize,
220 "nwseresize" => CursorIcon::NwseResize,
221 "colresize" => CursorIcon::ColResize,
222 "rowresize" => CursorIcon::RowResize,
223 _ => CursorIcon::Default,
224 })
225 }
226}
227
228/// Window size constraints
229#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct WindowSizeConstraints {
232 /// The minimum width a window can be, If this is `None`, the window will have no minimum width.
233 ///
234 /// The default is `None`.
235 pub min_width: Option<PixelUnit>,
236 /// The minimum height a window can be, If this is `None`, the window will have no minimum height.
237 ///
238 /// The default is `None`.
239 pub min_height: Option<PixelUnit>,
240 /// The maximum width a window can be, If this is `None`, the window will have no maximum width.
241 ///
242 /// The default is `None`.
243 pub max_width: Option<PixelUnit>,
244 /// The maximum height a window can be, If this is `None`, the window will have no maximum height.
245 ///
246 /// The default is `None`.
247 pub max_height: Option<PixelUnit>,
248}
249
250/// Do **NOT** implement this trait except for use in a custom [`Runtime`]
251///
252/// This trait is separate from [`WindowBuilder`] to prevent "accidental" implementation.
253pub trait WindowBuilderBase: std::fmt::Debug + Clone + Sized {}
254
255/// A builder for all attributes related to a single window.
256///
257/// This trait is only meant to be implemented by a custom [`Runtime`]
258/// and not by applications.
259pub trait WindowBuilder: WindowBuilderBase {
260 /// Initializes a new window attributes builder.
261 fn new() -> Self;
262
263 /// Initializes a new window builder from a [`WindowConfig`]
264 fn with_config(config: &WindowConfig) -> Self;
265
266 /// Show window in the center of the screen.
267 #[must_use]
268 fn center(self) -> Self;
269
270 /// The initial position of the window in logical pixels.
271 #[must_use]
272 fn position(self, x: f64, y: f64) -> Self;
273
274 /// Window size in logical pixels.
275 #[must_use]
276 fn inner_size(self, width: f64, height: f64) -> Self;
277
278 /// Window min inner size in logical pixels.
279 #[must_use]
280 fn min_inner_size(self, min_width: f64, min_height: f64) -> Self;
281
282 /// Window max inner size in logical pixels.
283 #[must_use]
284 fn max_inner_size(self, max_width: f64, max_height: f64) -> Self;
285
286 /// Window inner size constraints.
287 #[must_use]
288 fn inner_size_constraints(self, constraints: WindowSizeConstraints) -> Self;
289
290 /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size) on creation
291 ///
292 /// ## Platform-specific
293 ///
294 /// - **iOS / Android:** Unsupported.
295 #[must_use]
296 fn prevent_overflow(self) -> Self;
297
298 /// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size)
299 /// on creation with a margin
300 ///
301 /// ## Platform-specific
302 ///
303 /// - **iOS / Android:** Unsupported.
304 #[must_use]
305 fn prevent_overflow_with_margin(self, margin: dpi::Size) -> Self;
306
307 /// Whether the window is resizable or not.
308 /// When resizable is set to false, native window's maximize button is automatically disabled.
309 #[must_use]
310 fn resizable(self, resizable: bool) -> Self;
311
312 /// Whether the window's native maximize button is enabled or not.
313 /// If resizable is set to false, this setting is ignored.
314 ///
315 /// ## Platform-specific
316 ///
317 /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode.
318 /// - **Linux / iOS / Android:** Unsupported.
319 #[must_use]
320 fn maximizable(self, maximizable: bool) -> Self;
321
322 /// Whether the window's native minimize button is enabled or not.
323 ///
324 /// ## Platform-specific
325 ///
326 /// - **Linux / iOS / Android:** Unsupported.
327 #[must_use]
328 fn minimizable(self, minimizable: bool) -> Self;
329
330 /// Whether the window's native close button is enabled or not.
331 ///
332 /// ## Platform-specific
333 ///
334 /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button.
335 /// Depending on the system, this function may not have any effect when called on a window that is already visible"
336 /// - **iOS / Android:** Unsupported.
337 #[must_use]
338 fn closable(self, closable: bool) -> Self;
339
340 /// The title of the window in the title bar.
341 #[must_use]
342 fn title<S: Into<String>>(self, title: S) -> Self;
343
344 /// Whether to start the window in fullscreen or not.
345 #[must_use]
346 fn fullscreen(self, fullscreen: bool) -> Self;
347
348 /// Whether the window will be initially focused or not.
349 #[must_use]
350 fn focused(self, focused: bool) -> Self;
351
352 /// Whether the window will be focusable or not.
353 #[must_use]
354 fn focusable(self, focusable: bool) -> Self;
355
356 /// Whether the window should be maximized upon creation.
357 #[must_use]
358 fn maximized(self, maximized: bool) -> Self;
359
360 /// Whether the window should be immediately visible upon creation.
361 #[must_use]
362 fn visible(self, visible: bool) -> Self;
363
364 /// Whether the window should be transparent. If this is true, writing colors
365 /// with alpha values different than `1.0` will produce a transparent window.
366 #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
367 #[cfg_attr(
368 docsrs,
369 doc(cfg(any(not(target_os = "macos"), feature = "macos-private-api")))
370 )]
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 /// The name of the activity to create for this webview window.
502 #[cfg(target_os = "android")]
503 fn activity_name<S: Into<String>>(self, class_name: S) -> Self;
504
505 /// Sets the name of the activity that is creating this webview window.
506 ///
507 /// This is important to determine which stack the activity will belong to.
508 #[cfg(target_os = "android")]
509 fn created_by_activity_name<S: Into<String>>(self, class_name: S) -> Self;
510
511 /// Sets the identifier of the UIScene that is requesting the creation of this new scene,
512 /// establishing a relationship between the two scenes.
513 ///
514 /// By default the system uses the foreground scene.
515 #[cfg(target_os = "ios")]
516 fn requested_by_scene_identifier<S: Into<String>>(self, identifier: S) -> Self;
517}
518
519/// A window that has yet to be built.
520pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
521 /// The label that the window will be named.
522 pub label: String,
523
524 /// The [`WindowBuilder`] that the window will be created with.
525 pub window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,
526
527 /// The webview that gets added to the window. Optional in case you want to use child webviews or other window content instead.
528 pub webview: Option<PendingWebview<T, R>>,
529}
530
531pub fn is_label_valid(label: &str) -> bool {
532 label
533 .chars()
534 .all(|c| char::is_alphanumeric(c) || c == '-' || c == '/' || c == ':' || c == '_')
535}
536
537pub fn assert_label_is_valid(label: &str) {
538 assert!(
539 is_label_valid(label),
540 "Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`."
541 );
542}
543
544impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
545 /// Create a new [`PendingWindow`] with a label from the given [`WindowBuilder`].
546 pub fn new(
547 window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,
548 label: impl Into<String>,
549 ) -> crate::Result<Self> {
550 let label = label.into();
551 if !is_label_valid(&label) {
552 Err(crate::Error::InvalidWindowLabel)
553 } else {
554 Ok(Self {
555 window_builder,
556 label,
557 webview: None,
558 })
559 }
560 }
561
562 /// Sets a webview to be created on the window.
563 pub fn set_webview(&mut self, webview: PendingWebview<T, R>) -> &mut Self {
564 self.webview.replace(webview);
565 self
566 }
567}
568
569/// Identifier of a window.
570#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
571pub struct WindowId(u32);
572
573impl From<u32> for WindowId {
574 fn from(value: u32) -> Self {
575 Self(value)
576 }
577}
578
579/// A window that is not yet managed by Tauri.
580#[derive(Debug)]
581pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
582 /// The identifier of the window.
583 pub id: WindowId,
584 /// Name of the window
585 pub label: String,
586
587 /// The [`WindowDispatch`] associated with the window.
588 pub dispatcher: R::WindowDispatcher,
589
590 /// The webview dispatcher in case this window has an attached webview.
591 pub webview: Option<DetachedWindowWebview<T, R>>,
592}
593
594/// A detached webview associated with a window.
595#[derive(Debug)]
596pub struct DetachedWindowWebview<T: UserEvent, R: Runtime<T>> {
597 pub webview: DetachedWebview<T, R>,
598 pub use_https_scheme: bool,
599}
600
601impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindowWebview<T, R> {
602 fn clone(&self) -> Self {
603 Self {
604 webview: self.webview.clone(),
605 use_https_scheme: self.use_https_scheme,
606 }
607 }
608}
609
610impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
611 fn clone(&self) -> Self {
612 Self {
613 id: self.id,
614 label: self.label.clone(),
615 dispatcher: self.dispatcher.clone(),
616 webview: self.webview.clone(),
617 }
618 }
619}
620
621impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWindow<T, R> {
622 /// Only use the [`DetachedWindow`]'s label to represent its hash.
623 fn hash<H: Hasher>(&self, state: &mut H) {
624 self.label.hash(state)
625 }
626}
627
628impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWindow<T, R> {}
629impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWindow<T, R> {
630 /// Only use the [`DetachedWindow`]'s label to compare equality.
631 fn eq(&self, other: &Self) -> bool {
632 self.label.eq(&other.label)
633 }
634}
635
636/// A raw window type that contains fields to access
637/// the HWND on Windows, gtk::ApplicationWindow on Linux
638pub struct RawWindow<'a> {
639 #[cfg(windows)]
640 pub hwnd: isize,
641 #[cfg(any(
642 target_os = "linux",
643 target_os = "dragonfly",
644 target_os = "freebsd",
645 target_os = "netbsd",
646 target_os = "openbsd"
647 ))]
648 pub gtk_window: &'a gtk::ApplicationWindow,
649 #[cfg(any(
650 target_os = "linux",
651 target_os = "dragonfly",
652 target_os = "freebsd",
653 target_os = "netbsd",
654 target_os = "openbsd"
655 ))]
656 pub default_vbox: Option<&'a gtk::Box>,
657 pub _marker: &'a PhantomData<()>,
658}