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