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  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  #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
372  #[cfg_attr(
373    docsrs,
374    doc(cfg(any(not(target_os = "macos"), feature = "macos-private-api")))
375  )]
376  #[must_use]
377  fn transparent(self, transparent: bool) -> Self;
378
379  /// Whether the window should have borders and bars.
380  #[must_use]
381  fn decorations(self, decorations: bool) -> Self;
382
383  /// Whether the window should always be below other windows.
384  #[must_use]
385  fn always_on_bottom(self, always_on_bottom: bool) -> Self;
386
387  /// Whether the window should always be on top of other windows.
388  #[must_use]
389  fn always_on_top(self, always_on_top: bool) -> Self;
390
391  /// Whether the window should be visible on all workspaces or virtual desktops.
392  #[must_use]
393  fn visible_on_all_workspaces(self, visible_on_all_workspaces: bool) -> Self;
394
395  /// Prevents the window contents from being captured by other apps.
396  #[must_use]
397  fn content_protected(self, protected: bool) -> Self;
398
399  /// Sets the window icon.
400  fn icon(self, icon: Icon) -> crate::Result<Self>;
401
402  /// Sets whether or not the window icon should be added to the taskbar.
403  #[must_use]
404  fn skip_taskbar(self, skip: bool) -> Self;
405
406  /// Set the window background color.
407  #[must_use]
408  fn background_color(self, color: Color) -> Self;
409
410  /// Sets whether or not the window has shadow.
411  ///
412  /// ## Platform-specific
413  ///
414  /// - **Windows:**
415  ///   - `false` has no effect on decorated window, shadows are always ON.
416  ///   - `true` will make undecorated window have a 1px white border,
417  ///     and on Windows 11, it will have a rounded corners.
418  /// - **Linux:** Unsupported.
419  #[must_use]
420  fn shadow(self, enable: bool) -> Self;
421
422  /// Set an owner to the window to be created.
423  ///
424  /// From MSDN:
425  /// - An owned window is always above its owner in the z-order.
426  /// - The system automatically destroys an owned window when its owner is destroyed.
427  /// - An owned window is hidden when its owner is minimized.
428  ///
429  /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
430  #[cfg(windows)]
431  #[must_use]
432  fn owner(self, owner: HWND) -> Self;
433
434  /// Sets a parent to the window to be created.
435  ///
436  /// A child window has the WS_CHILD style and is confined to the client area of its parent window.
437  ///
438  /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
439  #[cfg(windows)]
440  #[must_use]
441  fn parent(self, parent: HWND) -> Self;
442
443  /// Sets a parent to the window to be created.
444  ///
445  /// See <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>
446  #[cfg(target_os = "macos")]
447  #[must_use]
448  fn parent(self, parent: *mut std::ffi::c_void) -> Self;
449
450  /// Sets the window to be created transient for parent.
451  ///
452  /// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>
453  #[cfg(any(
454    target_os = "linux",
455    target_os = "dragonfly",
456    target_os = "freebsd",
457    target_os = "netbsd",
458    target_os = "openbsd"
459  ))]
460  fn transient_for(self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self;
461
462  /// Enables or disables drag and drop support.
463  #[cfg(windows)]
464  #[must_use]
465  fn drag_and_drop(self, enabled: bool) -> Self;
466
467  /// Hide the titlebar. Titlebar buttons will still be visible.
468  #[cfg(target_os = "macos")]
469  #[must_use]
470  fn title_bar_style(self, style: tauri_utils::TitleBarStyle) -> Self;
471
472  /// Change the position of the window controls on macOS.
473  ///
474  /// Requires titleBarStyle: Overlay and decorations: true.
475  #[cfg(target_os = "macos")]
476  #[must_use]
477  fn traffic_light_position<P: Into<dpi::Position>>(self, position: P) -> Self;
478
479  /// Hide the window title.
480  #[cfg(target_os = "macos")]
481  #[must_use]
482  fn hidden_title(self, hidden: bool) -> Self;
483
484  /// Defines the window [tabbing identifier] for macOS.
485  ///
486  /// Windows with matching tabbing identifiers will be grouped together.
487  /// If the tabbing identifier is not set, automatic tabbing will be disabled.
488  ///
489  /// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
490  #[cfg(target_os = "macos")]
491  #[must_use]
492  fn tabbing_identifier(self, identifier: &str) -> Self;
493
494  /// Forces a theme or uses the system settings if None was provided.
495  fn theme(self, theme: Option<Theme>) -> Self;
496
497  /// Whether the icon was set or not.
498  fn has_icon(&self) -> bool;
499
500  fn get_theme(&self) -> Option<Theme>;
501
502  /// Sets custom name for Windows' window class. **Windows only**.
503  #[must_use]
504  fn window_classname<S: Into<String>>(self, window_classname: S) -> Self;
505
506  /// This sets `WS_EX_NOREDIRECTIONBITMAP`.
507  ///
508  /// This can avoid the white flash that may appear before the webview content is rendered
509  /// when using a transparent window. **Windows only**.
510  #[must_use]
511  fn no_redirection_bitmap(self, enable: bool) -> Self;
512
513  /// The name of the activity to create for this webview window.
514  #[cfg(target_os = "android")]
515  fn activity_name<S: Into<String>>(self, class_name: S) -> Self;
516
517  /// Sets the name of the activity that is creating this webview window.
518  ///
519  /// This is important to determine which stack the activity will belong to.
520  #[cfg(target_os = "android")]
521  fn created_by_activity_name<S: Into<String>>(self, class_name: S) -> Self;
522
523  /// Sets the identifier of the UIScene that is requesting the creation of this new scene,
524  /// establishing a relationship between the two scenes.
525  ///
526  /// By default the system uses the foreground scene.
527  #[cfg(target_os = "ios")]
528  fn requested_by_scene_identifier<S: Into<String>>(self, identifier: S) -> Self;
529}
530
531/// A window that has yet to be built.
532pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
533  /// The label that the window will be named.
534  pub label: String,
535
536  /// The [`WindowBuilder`] that the window will be created with.
537  pub window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,
538
539  /// The webview that gets added to the window. Optional in case you want to use child webviews or other window content instead.
540  pub webview: Option<PendingWebview<T, R>>,
541}
542
543pub fn is_label_valid(label: &str) -> bool {
544  label
545    .chars()
546    .all(|c| char::is_alphanumeric(c) || c == '-' || c == '/' || c == ':' || c == '_')
547}
548
549pub fn assert_label_is_valid(label: &str) {
550  assert!(
551    is_label_valid(label),
552    "Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`."
553  );
554}
555
556impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
557  /// Create a new [`PendingWindow`] with a label from the given [`WindowBuilder`].
558  pub fn new(
559    window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,
560    label: impl Into<String>,
561  ) -> crate::Result<Self> {
562    let label = label.into();
563    if !is_label_valid(&label) {
564      Err(crate::Error::InvalidWindowLabel)
565    } else {
566      Ok(Self {
567        window_builder,
568        label,
569        webview: None,
570      })
571    }
572  }
573
574  /// Sets a webview to be created on the window.
575  pub fn set_webview(&mut self, webview: PendingWebview<T, R>) -> &mut Self {
576    self.webview.replace(webview);
577    self
578  }
579}
580
581/// Identifier of a window.
582#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
583pub struct WindowId(u32);
584
585impl From<u32> for WindowId {
586  fn from(value: u32) -> Self {
587    Self(value)
588  }
589}
590
591/// A window that is not yet managed by Tauri.
592#[derive(Debug)]
593pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
594  /// The identifier of the window.
595  pub id: WindowId,
596  /// Name of the window
597  pub label: String,
598
599  /// The [`WindowDispatch`] associated with the window.
600  pub dispatcher: R::WindowDispatcher,
601
602  /// The webview dispatcher in case this window has an attached webview.
603  pub webview: Option<DetachedWindowWebview<T, R>>,
604}
605
606/// A detached webview associated with a window.
607#[derive(Debug)]
608pub struct DetachedWindowWebview<T: UserEvent, R: Runtime<T>> {
609  pub webview: DetachedWebview<T, R>,
610  pub use_https_scheme: bool,
611}
612
613impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindowWebview<T, R> {
614  fn clone(&self) -> Self {
615    Self {
616      webview: self.webview.clone(),
617      use_https_scheme: self.use_https_scheme,
618    }
619  }
620}
621
622impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
623  fn clone(&self) -> Self {
624    Self {
625      id: self.id,
626      label: self.label.clone(),
627      dispatcher: self.dispatcher.clone(),
628      webview: self.webview.clone(),
629    }
630  }
631}
632
633impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWindow<T, R> {
634  /// Only use the [`DetachedWindow`]'s label to represent its hash.
635  fn hash<H: Hasher>(&self, state: &mut H) {
636    self.label.hash(state)
637  }
638}
639
640impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWindow<T, R> {}
641impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWindow<T, R> {
642  /// Only use the [`DetachedWindow`]'s label to compare equality.
643  fn eq(&self, other: &Self) -> bool {
644    self.label.eq(&other.label)
645  }
646}
647
648/// A raw window type that contains fields to access
649/// the HWND on Windows, gtk::ApplicationWindow on Linux
650pub struct RawWindow<'a> {
651  #[cfg(windows)]
652  pub hwnd: isize,
653  #[cfg(any(
654    target_os = "linux",
655    target_os = "dragonfly",
656    target_os = "freebsd",
657    target_os = "netbsd",
658    target_os = "openbsd"
659  ))]
660  pub gtk_window: &'a gtk::ApplicationWindow,
661  #[cfg(any(
662    target_os = "linux",
663    target_os = "dragonfly",
664    target_os = "freebsd",
665    target_os = "netbsd",
666    target_os = "openbsd"
667  ))]
668  pub default_vbox: Option<&'a gtk::Box>,
669  pub _marker: &'a PhantomData<()>,
670}