Skip to main content

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}