Skip to main content

tauri_runtime_cef/
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
5use std::{
6  collections::HashMap,
7  sync::{
8    Arc, Mutex,
9    mpsc::{self, Receiver, Sender},
10  },
11  time::{Duration, Instant},
12};
13
14use cef::ImplBrowserHost;
15use raw_window_handle::HasWindowHandle;
16use tauri_runtime::{
17  Error, Icon, ProgressBarState, Result, UserAttentionType, UserEvent, WindowDispatch,
18  WindowEventId,
19  dpi::{PhysicalPosition, PhysicalSize, Position, Size},
20  monitor::Monitor,
21  webview::{DetachedWebview, PendingWebview},
22  window::{
23    CursorIcon, DetachedWindow, DetachedWindowWebview, PendingWindow, RawWindow, WindowEvent,
24    WindowId, WindowSizeConstraints,
25  },
26};
27use tauri_utils::{Theme, config::Color};
28use winit::{
29  event_loop::ActiveEventLoop,
30  monitor::{Fullscreen, MonitorHandle},
31  window::{Window as WinitWindow, WindowAttributes, WindowLevel},
32};
33
34use crate::platform::{EventLoopExt, MonitorExt};
35#[cfg(any(
36  windows,
37  target_os = "macos",
38  target_os = "linux",
39  target_os = "dragonfly",
40  target_os = "freebsd",
41  target_os = "netbsd",
42  target_os = "openbsd"
43))]
44use std::marker::PhantomData;
45#[cfg(target_os = "macos")]
46use winit::platform::macos::WindowExtMacOS;
47#[cfg(windows)]
48use winit::platform::windows::WindowExtWindows;
49
50#[cfg(windows)]
51use crate::window_handle::SoftbufferWindowHandle;
52use crate::{
53  cef_impl::{client as browser_client, request_context},
54  runtime::{
55    AfterWindowCreationCallback, CefRuntime, Message, RuntimeContext, WinitCefApp,
56    WinitDragDropState,
57  },
58  webview::{AppWebview, CefWebviewDispatcher, create_webview_detached},
59  window_builder::WindowBuilderWrapper,
60  window_handle::SendRawWindowHandle,
61};
62
63type WindowEventListener = Box<dyn Fn(&WindowEvent) + Send>;
64type WindowEventListeners = Arc<Mutex<HashMap<WindowEventId, WindowEventListener>>>;
65
66#[cfg(any(
67  target_os = "linux",
68  target_os = "dragonfly",
69  target_os = "freebsd",
70  target_os = "netbsd",
71  target_os = "openbsd"
72))]
73pub(crate) struct SendGtkWindow(*mut std::ffi::c_void);
74
75#[cfg(any(
76  target_os = "linux",
77  target_os = "dragonfly",
78  target_os = "freebsd",
79  target_os = "netbsd",
80  target_os = "openbsd"
81))]
82unsafe impl Send for SendGtkWindow {}
83
84#[cfg(any(
85  target_os = "linux",
86  target_os = "dragonfly",
87  target_os = "freebsd",
88  target_os = "netbsd",
89  target_os = "openbsd"
90))]
91pub(crate) struct SendGtkBox(*mut std::ffi::c_void);
92
93#[cfg(any(
94  target_os = "linux",
95  target_os = "dragonfly",
96  target_os = "freebsd",
97  target_os = "netbsd",
98  target_os = "openbsd"
99))]
100unsafe impl Send for SendGtkBox {}
101
102pub(crate) fn tauri_theme_to_winit_theme(theme: Option<Theme>) -> Option<winit::window::Theme> {
103  theme.map(|theme| match theme {
104    Theme::Light => winit::window::Theme::Light,
105    Theme::Dark => winit::window::Theme::Dark,
106    _ => winit::window::Theme::Light,
107  })
108}
109
110pub(crate) fn winit_theme_to_tauri_theme(theme: winit::window::Theme) -> Theme {
111  match theme {
112    winit::window::Theme::Light => Theme::Light,
113    winit::window::Theme::Dark => Theme::Dark,
114  }
115}
116
117fn tauri_resize_direction_to_winit(
118  direction: tauri_runtime::ResizeDirection,
119) -> winit::window::ResizeDirection {
120  match direction {
121    tauri_runtime::ResizeDirection::East => winit::window::ResizeDirection::East,
122    tauri_runtime::ResizeDirection::North => winit::window::ResizeDirection::North,
123    tauri_runtime::ResizeDirection::NorthEast => winit::window::ResizeDirection::NorthEast,
124    tauri_runtime::ResizeDirection::NorthWest => winit::window::ResizeDirection::NorthWest,
125    tauri_runtime::ResizeDirection::South => winit::window::ResizeDirection::South,
126    tauri_runtime::ResizeDirection::SouthEast => winit::window::ResizeDirection::SouthEast,
127    tauri_runtime::ResizeDirection::SouthWest => winit::window::ResizeDirection::SouthWest,
128    tauri_runtime::ResizeDirection::West => winit::window::ResizeDirection::West,
129  }
130}
131
132fn calculate_window_center_position(
133  window_size: PhysicalSize<u32>,
134  monitor: &MonitorHandle,
135) -> PhysicalPosition<i32> {
136  let work_area = monitor.work_area();
137  PhysicalPosition::new(
138    work_area.position.x + ((work_area.size.width as i32 - window_size.width as i32).max(0) / 2),
139    work_area.position.y + ((work_area.size.height as i32 - window_size.height as i32).max(0) / 2),
140  )
141}
142
143fn find_monitor_for_position(
144  monitors: impl Iterator<Item = MonitorHandle>,
145  position: Position,
146) -> Option<MonitorHandle> {
147  monitors.into_iter().find(|monitor| {
148    let Some(monitor_position) = monitor.position() else {
149      return false;
150    };
151    let Some(video_mode) = monitor.current_video_mode() else {
152      return false;
153    };
154
155    let monitor_size = video_mode.size();
156    let position = position.to_physical::<i32>(monitor.scale_factor());
157
158    monitor_position.x <= position.x
159      && position.x < monitor_position.x + monitor_size.width as i32
160      && monitor_position.y <= position.y
161      && position.y < monitor_position.y + monitor_size.height as i32
162  })
163}
164
165fn clamp_surface_size(attrs: &WindowAttributes, scale_factor: f64) -> PhysicalSize<u32> {
166  let mut size = attrs
167    .surface_size
168    .unwrap_or_else(|| PhysicalSize::new(800, 600).into())
169    .to_physical::<u32>(scale_factor);
170
171  if let Some(min_size) = attrs.min_surface_size {
172    let min_size = min_size.to_physical::<u32>(scale_factor);
173    size.width = size.width.max(min_size.width);
174    size.height = size.height.max(min_size.height);
175  }
176
177  if let Some(max_size) = attrs.max_surface_size {
178    let max_size = max_size.to_physical::<u32>(scale_factor);
179    size.width = size.width.min(max_size.width);
180    size.height = size.height.min(max_size.height);
181  }
182
183  size
184}
185
186fn apply_prevent_overflow(
187  attrs: &mut WindowAttributes,
188  window_size: &mut PhysicalSize<u32>,
189  monitor: &MonitorHandle,
190  margin: Size,
191) {
192  let work_area = monitor.work_area();
193  let margin = margin.to_physical::<u32>(monitor.scale_factor());
194  let constraint = PhysicalSize::new(
195    work_area.size.width.saturating_sub(margin.width),
196    work_area.size.height.saturating_sub(margin.height),
197  );
198
199  if window_size.width > constraint.width || window_size.height > constraint.height {
200    window_size.width = window_size.width.min(constraint.width);
201    window_size.height = window_size.height.min(constraint.height);
202    attrs.surface_size = Some((*window_size).into());
203  }
204}
205
206fn prepare_window_attributes(event_loop: &dyn ActiveEventLoop, attrs: &mut AppWindowAttrs) {
207  if !attrs.center && attrs.prevent_overflow.is_none() {
208    return;
209  }
210
211  let monitor = attrs
212    .inner
213    .position
214    .and_then(|position| {
215      let monitors = event_loop.available_monitors();
216      find_monitor_for_position(monitors, position)
217    })
218    .or_else(|| event_loop.primary_monitor());
219
220  let Some(monitor) = monitor else {
221    return;
222  };
223
224  // `clamp_surface_size` is the requested client/surface size; the window
225  // winit creates will be larger by the non-client frame (title bar + borders).
226  // To center the *visible* window we have to account for that frame.
227  let mut window_size = clamp_surface_size(&attrs.inner, monitor.scale_factor());
228
229  // Left and right borders count toward the outer width (and so toward the
230  // centered x position) but not toward the surface size. The title bar adds to
231  // the visible height. Mirrors `tauri-runtime-wry`'s creation-time centering.
232  #[allow(unused_mut)]
233  let mut shadow_width: u32 = 0;
234  #[cfg(windows)]
235  if attrs.inner.decorations {
236    use windows::Win32::{
237      Foundation::RECT,
238      UI::WindowsAndMessaging::{AdjustWindowRect, WS_OVERLAPPEDWINDOW},
239    };
240
241    let mut rect = RECT::default();
242    if unsafe { AdjustWindowRect(&mut rect, WS_OVERLAPPEDWINDOW, false) }.is_ok() {
243      shadow_width = (rect.right - rect.left) as u32;
244      // `rect.top` is negative (the title bar above the client area);
245      // `rect.bottom` is the bottom shadow, which we intentionally ignore.
246      window_size.height += (-rect.top) as u32;
247    }
248  }
249
250  if let Some(margin) = attrs.prevent_overflow {
251    apply_prevent_overflow(&mut attrs.inner, &mut window_size, &monitor, margin);
252  }
253
254  if attrs.center {
255    window_size.width += shadow_width;
256    let position = calculate_window_center_position(window_size, &monitor);
257    attrs.inner.position = Some(position.into());
258  }
259}
260
261fn paired_size_constraint(
262  width: Option<tauri_runtime::dpi::PixelUnit>,
263  height: Option<tauri_runtime::dpi::PixelUnit>,
264  unconstrained: u32,
265) -> Option<Size> {
266  match (width, height) {
267    (
268      Some(tauri_runtime::dpi::PixelUnit::Logical(width)),
269      Some(tauri_runtime::dpi::PixelUnit::Logical(height)),
270    ) => Some(Size::Logical(tauri_runtime::dpi::LogicalSize::new(
271      width.into(),
272      height.into(),
273    ))),
274    (
275      Some(tauri_runtime::dpi::PixelUnit::Physical(width)),
276      Some(tauri_runtime::dpi::PixelUnit::Physical(height)),
277    ) => Some(Size::Physical(PhysicalSize::new(
278      width.into(),
279      height.into(),
280    ))),
281    (Some(tauri_runtime::dpi::PixelUnit::Logical(width)), None) => Some(Size::Logical(
282      tauri_runtime::dpi::LogicalSize::new(width.into(), unconstrained as f64),
283    )),
284    (None, Some(tauri_runtime::dpi::PixelUnit::Logical(height))) => Some(Size::Logical(
285      tauri_runtime::dpi::LogicalSize::new(unconstrained as f64, height.into()),
286    )),
287    (Some(tauri_runtime::dpi::PixelUnit::Physical(width)), None) => Some(Size::Physical(
288      PhysicalSize::new(width.into(), unconstrained),
289    )),
290    (None, Some(tauri_runtime::dpi::PixelUnit::Physical(height))) => Some(Size::Physical(
291      PhysicalSize::new(unconstrained, height.into()),
292    )),
293    _ => None,
294  }
295}
296
297pub(crate) fn min_size_constraint(
298  width: Option<tauri_runtime::dpi::PixelUnit>,
299  height: Option<tauri_runtime::dpi::PixelUnit>,
300) -> Option<Size> {
301  paired_size_constraint(width, height, 0)
302}
303
304pub(crate) fn max_size_constraint(
305  width: Option<tauri_runtime::dpi::PixelUnit>,
306  height: Option<tauri_runtime::dpi::PixelUnit>,
307) -> Option<Size> {
308  paired_size_constraint(width, height, u32::MAX)
309}
310
311/// The monitor a fullscreen request targets. Kept as the request came in, rather than as a
312/// resolved `MonitorHandle`, so that a request held back on macOS can be applied later (see
313/// [`AppWindow::request_fullscreen`]).
314#[derive(Clone, Copy, Debug)]
315pub(crate) enum FullscreenTarget {
316  /// The monitor the window is on (`set_fullscreen(true)`).
317  CurrentMonitor,
318  /// The monitor containing this physical position (`set_fullscreen_on_monitor`).
319  MonitorAt(PhysicalPosition<f64>),
320}
321
322pub(crate) enum WindowMessage {
323  AddEventListener(WindowEventId, WindowEventListener),
324  Close,
325  Destroy,
326  ScaleFactor(Sender<Result<f64>>),
327  InnerPosition(Sender<Result<PhysicalPosition<i32>>>),
328  OuterPosition(Sender<Result<PhysicalPosition<i32>>>),
329  InnerSize(Sender<Result<PhysicalSize<u32>>>),
330  OuterSize(Sender<Result<PhysicalSize<u32>>>),
331  IsFullscreen(Sender<Result<bool>>),
332  IsMinimized(Sender<Result<bool>>),
333  IsMaximized(Sender<Result<bool>>),
334  IsFocused(Sender<Result<bool>>),
335  IsDecorated(Sender<Result<bool>>),
336  IsResizable(Sender<Result<bool>>),
337  IsMaximizable(Sender<Result<bool>>),
338  IsMinimizable(Sender<Result<bool>>),
339  IsClosable(Sender<Result<bool>>),
340  IsVisible(Sender<Result<bool>>),
341  IsEnabled(Sender<Result<bool>>),
342  IsAlwaysOnTop(Sender<Result<bool>>),
343  Title(Sender<Result<String>>),
344  CurrentMonitor(Sender<Result<Option<Monitor>>>),
345  PrimaryMonitor(Sender<Result<Option<Monitor>>>),
346  MonitorFromPoint(Sender<Result<Option<Monitor>>>, f64, f64),
347  AvailableMonitors(Sender<Result<Vec<Monitor>>>),
348  #[cfg(any(
349    target_os = "linux",
350    target_os = "dragonfly",
351    target_os = "freebsd",
352    target_os = "netbsd",
353    target_os = "openbsd"
354  ))]
355  GtkWindow(Sender<Result<SendGtkWindow>>),
356  #[cfg(any(
357    target_os = "linux",
358    target_os = "dragonfly",
359    target_os = "freebsd",
360    target_os = "netbsd",
361    target_os = "openbsd"
362  ))]
363  DefaultVBox(Sender<Result<SendGtkBox>>),
364  RawWindowHandle(Sender<Result<SendRawWindowHandle>>),
365  Theme(Sender<Result<Theme>>),
366  Center,
367  RequestUserAttention(Option<UserAttentionType>),
368  SetEnabled(bool),
369  SetResizable(bool),
370  SetMaximizable(bool),
371  SetMinimizable(bool),
372  SetClosable(bool),
373  SetTitle(String),
374  Maximize,
375  Unmaximize,
376  Minimize,
377  Unminimize,
378  Show,
379  Hide,
380  SetDecorations(bool),
381  SetShadow(bool),
382  SetAlwaysOnBottom(bool),
383  SetAlwaysOnTop(bool),
384  SetVisibleOnAllWorkspaces(bool),
385  SetContentProtected(bool),
386  SetSize(Size),
387  SetMinSize(Option<Size>),
388  SetMaxSize(Option<Size>),
389  SetSizeConstraints(WindowSizeConstraints),
390  SetPosition(Position),
391  SetFullscreen(bool),
392  SetFullscreenOnMonitor(PhysicalPosition<f64>),
393  /// Applies the fullscreen request held back during a transition, now that it ended. See
394  /// [`AppWindow::request_fullscreen`].
395  #[cfg(target_os = "macos")]
396  ApplyPendingFullscreen,
397  #[cfg(target_os = "macos")]
398  SetSimpleFullscreen(bool),
399  SetFocus,
400  // TODO: Implement SetFocusable, winit currently doesn't expose an API for it
401  #[allow(unused)]
402  SetFocusable(bool),
403  SetIcon(Icon<'static>),
404  SetSkipTaskbar(bool),
405  SetCursorGrab(bool),
406  SetCursorVisible(bool),
407  SetCursorIcon(CursorIcon),
408  SetCursorPosition(Position),
409  SetIgnoreCursorEvents(bool),
410  SetProgressBar(ProgressBarState),
411  SetBadgeCount(Option<i64>, Option<String>),
412  SetBadgeLabel(Option<String>),
413  SetOverlayIcon(Option<Icon<'static>>),
414  SetTitleBarStyle(tauri_utils::TitleBarStyle),
415  SetTrafficLightPosition(Position),
416  SetTheme(Option<Theme>),
417  SetBackgroundColor(Option<Color>),
418  StartDragging,
419  StartResizeDragging(tauri_runtime::ResizeDirection),
420}
421
422#[cfg(windows)]
423type SoftbufferSurface = softbuffer::Surface<SoftbufferWindowHandle, SoftbufferWindowHandle>;
424
425/// Opaque identity of one runtime-owned native window lifetime. Reparented
426/// webviews observe the destination token; same-label replacements never match.
427#[derive(Clone)]
428pub struct NativeWindowToken(Arc<()>);
429
430impl NativeWindowToken {
431  pub(crate) fn new() -> Self {
432    Self(Arc::new(()))
433  }
434}
435
436impl PartialEq for NativeWindowToken {
437  fn eq(&self, other: &Self) -> bool {
438    Arc::ptr_eq(&self.0, &other.0)
439  }
440}
441
442impl Eq for NativeWindowToken {}
443
444impl std::fmt::Debug for NativeWindowToken {
445  fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
446    formatter
447      .debug_struct("NativeWindowToken")
448      .finish_non_exhaustive()
449  }
450}
451
452#[cfg(test)]
453mod native_window_identity_tests {
454  use super::NativeWindowToken;
455
456  #[test]
457  fn references_preserve_one_window_and_reject_replacements() {
458    let first = NativeWindowToken::new();
459    let retained_by_webview = first.clone();
460    assert_eq!(first, retained_by_webview);
461    let destination = NativeWindowToken::new();
462    assert_ne!(retained_by_webview, destination);
463    drop(first);
464    assert_ne!(retained_by_webview, NativeWindowToken::new());
465  }
466}
467
468/// How long to keep retrying the initial raise of a window created focused
469/// before assuming it is never going to be mapped.
470const PENDING_ACTIVATION_TIMEOUT: Duration = Duration::from_secs(5);
471
472pub(crate) struct AppWindow {
473  pub(crate) lifetime: NativeWindowToken,
474  #[allow(unused)]
475  pub(crate) id: WindowId,
476  pub(crate) label: String,
477  #[cfg(windows)]
478  pub(crate) background_surface: Option<SoftbufferSurface>,
479  pub(crate) window: Arc<dyn WinitWindow>,
480  pub(crate) attrs: AppWindowAttrs,
481  pub(crate) children: Vec<AppWebview>,
482  pub(crate) listeners: WindowEventListeners,
483  pub(crate) native_drag_drop: Option<WinitDragDropState>,
484  #[cfg(any(
485    target_os = "linux",
486    target_os = "dragonfly",
487    target_os = "freebsd",
488    target_os = "netbsd",
489    target_os = "openbsd"
490  ))]
491  /// X11 parent for CEF browser children, sized to the GTK content area so
492  /// GTK UI like menus stays outside the native CEF child-window stack.
493  pub(crate) cef_host: crate::platform::linux::CefX11Host,
494  /// Deadline for the initial raise of a window created focused, see
495  /// [`WinitCefApp::apply_pending_activations`]. `None` once it has been
496  /// raised or given up on.
497  pub(crate) pending_activation: Option<Instant>,
498  /// Holds fullscreen requests back while the window is animating a fullscreen
499  /// transition, see [`AppWindow::request_fullscreen`].
500  #[cfg(target_os = "macos")]
501  pub(crate) fullscreen_transition:
502    objc2::rc::Retained<crate::platform::macos::FullscreenTransition>,
503}
504
505#[derive(Clone, Debug, Default)]
506pub(crate) struct AppWindowAttrs {
507  pub(crate) inner: WindowAttributes,
508  pub(crate) center: bool,
509  pub(crate) background_color: Option<Color>,
510  pub(crate) prevent_overflow: Option<Size>,
511  #[cfg(target_os = "macos")]
512  pub(crate) traffic_light_position: Option<Position>,
513  #[cfg(any(
514    target_os = "macos",
515    target_os = "linux",
516    target_os = "dragonfly",
517    target_os = "freebsd",
518    target_os = "netbsd",
519    target_os = "openbsd"
520  ))]
521  pub(crate) visible_on_all_workspaces: bool,
522  #[cfg(any(
523    target_os = "linux",
524    target_os = "dragonfly",
525    target_os = "freebsd",
526    target_os = "netbsd",
527    target_os = "openbsd"
528  ))]
529  pub(crate) skip_taskbar: bool,
530  /// Parent this window is transient for, owning the reference transferred by
531  /// [`WindowBuilder::transient_for`](tauri_runtime::window::WindowBuilder::transient_for).
532  #[cfg(any(
533    target_os = "linux",
534    target_os = "dragonfly",
535    target_os = "freebsd",
536    target_os = "netbsd",
537    target_os = "openbsd"
538  ))]
539  pub(crate) transient_for: Option<gtk::Window>,
540}
541
542impl AppWindow {
543  pub(crate) fn center(&self) {
544    let monitor = self.window.current_monitor();
545    let monitor = monitor.or_else(|| self.window.primary_monitor());
546    let Some(monitor) = monitor else {
547      return;
548    };
549
550    #[allow(unused_mut)]
551    let mut window_size = self.window.outer_size();
552
553    // On Windows `outer_size` includes the invisible resize/shadow border, so
554    // centering by it pushes the visible window down. Substitute the visible
555    // frame height reported by DWM. Mirrors `tauri-runtime-wry`'s `center`.
556    #[cfg(windows)]
557    if self.window.is_decorated()
558      && let Some(visible_height) = self.dwm_visible_frame_height()
559    {
560      window_size.height = visible_height;
561    }
562
563    let position = calculate_window_center_position(window_size, &monitor);
564    self.window.set_outer_position(Position::Physical(position));
565  }
566
567  /// Bring the window to the front and give it the input focus.
568  ///
569  /// `WinitWindow::focus_window` alone is not enough: on macOS it asks for
570  /// activation through the deprecated `activateIgnoringOtherApps:`, which
571  /// macOS 14+ ignores, and on X11 it asks the window manager to activate with
572  /// the "application" source indication, which focus-stealing prevention
573  /// routinely downgrades to a taskbar highlight. Both get a native nudge
574  /// first.
575  pub(crate) fn activate(&self) {
576    #[cfg(target_os = "macos")]
577    crate::platform::macos::activate_application();
578
579    #[cfg(any(
580      target_os = "linux",
581      target_os = "dragonfly",
582      target_os = "freebsd",
583      target_os = "netbsd",
584      target_os = "openbsd"
585    ))]
586    self.raise_native();
587
588    self.window.focus_window();
589  }
590
591  /// Applies a fullscreen request; `None` leaves fullscreen.
592  ///
593  /// On macOS the request is held back while the window is animating a fullscreen transition
594  /// and applied once it ended: AppKit drops `toggleFullScreen:` during one, and winit's own
595  /// handling of that leaves its state out of step with the window's (see
596  /// [`platform::macos::FullscreenTransition`](crate::platform::macos::FullscreenTransition)).
597  /// The latest request wins, as it would have had the window been idle.
598  pub(crate) fn request_fullscreen(&self, target: Option<FullscreenTarget>) {
599    #[cfg(target_os = "macos")]
600    if self.fullscreen_transition.in_progress() {
601      self.fullscreen_transition.hold(target);
602      return;
603    }
604
605    let fullscreen = match target {
606      None => None,
607      Some(FullscreenTarget::CurrentMonitor) => Some(Fullscreen::Borderless(None)),
608      Some(FullscreenTarget::MonitorAt(position)) => {
609        // same physical-coordinate lookup as `MonitorFromPoint`; a position outside every monitor is a no-op
610        let monitor = self.window.available_monitors().find(|m| {
611          let pos = m.position().unwrap_or_default();
612          let vm = m.current_video_mode();
613          let size = vm.map(|v| v.size()).unwrap_or_default();
614          position.x >= pos.x as f64
615            && position.x < pos.x as f64 + size.width as f64
616            && position.y >= pos.y as f64
617            && position.y < pos.y as f64 + size.height as f64
618        });
619        let Some(monitor) = monitor else {
620          return;
621        };
622        Some(Fullscreen::Borderless(Some(monitor)))
623      }
624    };
625    self.window.set_fullscreen(fullscreen);
626  }
627
628  pub(crate) fn preferred_theme(&self) -> Option<Theme> {
629    self
630      .attrs
631      .inner
632      .preferred_theme
633      .map(winit_theme_to_tauri_theme)
634  }
635
636  pub(crate) fn resolved_theme(&self, app_wide_theme: Option<Theme>) -> Option<Theme> {
637    self.preferred_theme().or(app_wide_theme)
638  }
639
640  /// Size available for CEF child layout. On Linux this is the GTK content-area
641  /// X11 host size, excluding GTK UI such as menus; elsewhere it is the window
642  /// surface size.
643  pub(crate) fn safe_surface_size(&self) -> PhysicalSize<u32> {
644    #[cfg(any(
645      target_os = "linux",
646      target_os = "dragonfly",
647      target_os = "freebsd",
648      target_os = "netbsd",
649      target_os = "openbsd"
650    ))]
651    {
652      self.cef_host.size()
653    }
654
655    #[cfg(not(any(
656      target_os = "linux",
657      target_os = "dragonfly",
658      target_os = "freebsd",
659      target_os = "netbsd",
660      target_os = "openbsd"
661    )))]
662    self.window.surface_size()
663  }
664
665  pub(crate) fn set_theme(&mut self, theme: Option<Theme>) {
666    self.attrs.inner.preferred_theme = tauri_theme_to_winit_theme(theme);
667    self.window.set_theme(tauri_theme_to_winit_theme(theme));
668    self.apply_cef_theme(theme);
669    #[cfg(target_os = "macos")]
670    self.reapply_traffic_light_position_after_appearance_change();
671  }
672
673  fn apply_cef_theme(&self, theme: Option<Theme>) {
674    for child in &self.children {
675      let request_context = child.host.request_context();
676      request_context::apply_theme_scheme(request_context.as_ref(), theme);
677    }
678  }
679}
680
681impl<T: UserEvent> WinitCefApp<T> {
682  pub(crate) fn create_window(
683    &mut self,
684    event_loop: &dyn ActiveEventLoop,
685    window_id: WindowId,
686    webview_id: Option<u32>,
687    pending: Box<PendingWindow<T, CefRuntime<T>>>,
688    _after_window_creation: Option<AfterWindowCreationCallback>,
689  ) -> Result<()> {
690    let mut attrs = pending.window_builder.attrs.clone();
691    if attrs.inner.preferred_theme.is_none() {
692      attrs.inner.preferred_theme =
693        tauri_theme_to_winit_theme(*self.context.app_wide_theme.lock().unwrap());
694    }
695    prepare_window_attributes(event_loop, &mut attrs);
696
697    let window = event_loop
698      .create_window(attrs.inner.clone())
699      .map_err(|e| Error::CreateWindow(Box::new(e)))?;
700
701    #[cfg(any(
702      target_os = "linux",
703      target_os = "dragonfly",
704      target_os = "freebsd",
705      target_os = "netbsd",
706      target_os = "openbsd"
707    ))]
708    let cef_host = crate::platform::linux::CefX11Host::new(window.as_ref()).ok_or_else(|| {
709      Error::CreateWindow("failed to create the X11 host for the CEF browser".into())
710    })?;
711
712    let winit_id = window.id();
713    let pending_activation = (attrs.inner.active && attrs.inner.visible)
714      .then(|| Instant::now() + PENDING_ACTIVATION_TIMEOUT);
715    #[cfg(target_os = "macos")]
716    let fullscreen_transition = {
717      let context = self.context.clone();
718      crate::platform::macos::FullscreenTransition::observe(
719        &crate::platform::macos::nswindow(window.as_ref()),
720        move || {
721          let _ = context.send_message(Message::Window {
722            window_id,
723            message: WindowMessage::ApplyPendingFullscreen,
724          });
725        },
726      )
727    };
728    let mut appwindow = AppWindow {
729      lifetime: NativeWindowToken::new(),
730      id: window_id,
731      label: pending.label.clone(),
732      #[cfg(windows)]
733      background_surface: None,
734      window: Arc::from(window),
735      attrs,
736      children: Vec::new(),
737      listeners: Default::default(),
738      native_drag_drop: None,
739      #[cfg(any(
740        target_os = "linux",
741        target_os = "dragonfly",
742        target_os = "freebsd",
743        target_os = "netbsd",
744        target_os = "openbsd"
745      ))]
746      cef_host,
747      pending_activation,
748      #[cfg(target_os = "macos")]
749      fullscreen_transition,
750    };
751
752    #[cfg(target_os = "macos")]
753    {
754      appwindow.apply_movability();
755      appwindow.set_visible_on_all_workspaces(appwindow.attrs.visible_on_all_workspaces);
756      if let Some(position) = &appwindow.attrs.traffic_light_position {
757        appwindow.set_traffic_light_position(position);
758      }
759    }
760
761    #[cfg(any(
762      target_os = "linux",
763      target_os = "dragonfly",
764      target_os = "freebsd",
765      target_os = "netbsd",
766      target_os = "openbsd"
767    ))]
768    {
769      appwindow.set_visible_on_all_workspaces(appwindow.attrs.visible_on_all_workspaces);
770      appwindow.set_skip_taskbar(appwindow.attrs.skip_taskbar);
771      appwindow.apply_transient_for();
772    }
773
774    #[cfg(windows)]
775    appwindow.draw_background_surface();
776
777    #[cfg(not(windows))]
778    if appwindow.attrs.background_color.is_some() {
779      appwindow.set_background_color(appwindow.attrs.background_color);
780    }
781
782    #[cfg(any(
783      target_os = "linux",
784      target_os = "dragonfly",
785      target_os = "freebsd",
786      target_os = "netbsd",
787      target_os = "openbsd"
788    ))]
789    if let Some(after_window_creation) = _after_window_creation {
790      use gtk::glib::translate::ToGlibPtr;
791      use winit::platform::gtk4::WindowExtGtk4;
792
793      let gtk_window = appwindow.window.gtk_window().unwrap();
794      let default_vbox = appwindow.cef_host.default_vbox();
795      after_window_creation(RawWindow {
796        gtk_window: {
797          let ptr: *mut gtk::ffi::GtkApplicationWindow = gtk_window.to_glib_none().0;
798          ptr as *mut std::ffi::c_void
799        },
800        default_vbox: Some({
801          let ptr: *mut gtk::ffi::GtkBox = default_vbox.to_glib_none().0;
802          ptr as *mut std::ffi::c_void
803        }),
804        _marker: &PhantomData,
805      });
806    }
807
808    #[cfg(any(windows, target_os = "macos"))]
809    if let Some(after_window_creation) = _after_window_creation {
810      after_window_creation(RawWindow {
811        #[cfg(windows)]
812        hwnd: appwindow.hwnd().0 as isize,
813        _marker: &PhantomData,
814      });
815    }
816
817    // Build the initial webview against the not-yet-registered window so a
818    // creation failure surfaces to the caller without leaving the window in
819    // state to roll back.
820    if let (Some(webview_id), Some(webview)) = (webview_id, pending.webview) {
821      Self::build_and_attach_webview(
822        &self.context,
823        &self.scheme_registry,
824        &mut self.state.live_browsers,
825        &mut appwindow,
826        webview_id,
827        browser_client::DragDropEventTarget::Window,
828        webview,
829      )?;
830    }
831
832    self
833      .state
834      .winid_id_to_window_id_map
835      .insert(winit_id, window_id);
836    self.state.windows.insert(window_id, appwindow);
837
838    Ok(())
839  }
840
841  /// Bring windows that were created focused to the front.
842  ///
843  /// winit applies [`WindowAttributes::active`] unevenly: X11 ignores it
844  /// outright, and on macOS/Windows it only orders the window front *within*
845  /// the application without pulling the process to the foreground. A window
846  /// created while another app owns the foreground - a terminal running
847  /// `tauri dev`, say - is then left buried behind it. Raising it ourselves
848  /// once it is on screen makes the initial activation deterministic.
849  ///
850  /// `focus_window` is a no-op while the backend still considers the window
851  /// unmapped (X11 only reports it visible once the server sends
852  /// `VisibilityNotify`, which lands after `create_window` returns), so keep
853  /// the request pending until winit reports the window visible, and drop it
854  /// after [`PENDING_ACTIVATION_TIMEOUT`] so a window that never maps does not
855  /// pop to the front minutes later.
856  pub(crate) fn apply_pending_activations(&mut self) {
857    let now = Instant::now();
858    for appwindow in self.state.windows.values_mut() {
859      let Some(deadline) = appwindow.pending_activation else {
860        continue;
861      };
862
863      if appwindow.window.is_visible() == Some(false) {
864        if now < deadline {
865          continue;
866        }
867        appwindow.pending_activation = None;
868        continue;
869      }
870
871      appwindow.activate();
872      appwindow.pending_activation = None;
873    }
874  }
875
876  /// Re-lays out the CEF children of every window whose X11 host was resized by GTK without the
877  /// toplevel changing size.
878  ///
879  /// GTK owns the content area, so attaching, hiding or showing a menu bar moves and resizes the
880  /// host while winit reports no `SurfaceResized` for the toplevel. Without this the children
881  /// would keep the bounds computed against the previous host size until the user resizes the
882  /// window.
883  #[cfg(any(
884    target_os = "linux",
885    target_os = "dragonfly",
886    target_os = "freebsd",
887    target_os = "netbsd",
888    target_os = "openbsd"
889  ))]
890  pub(crate) fn apply_pending_host_layouts(&mut self) {
891    for appwindow in self.state.windows.values() {
892      if appwindow.cef_host.take_needs_relayout() {
893        crate::webview::layout_app_window(appwindow);
894      }
895    }
896  }
897
898  pub(crate) fn handle_window_message(
899    &mut self,
900    event_loop: &dyn ActiveEventLoop,
901    window_id: WindowId,
902    message: WindowMessage,
903  ) {
904    // Handle Close and Destroy messages first to avoid borrowing issues with the window.
905    match message {
906      WindowMessage::Close => {
907        self.request_window_close(window_id, event_loop);
908        return;
909      }
910      WindowMessage::Destroy => {
911        self.close_window(window_id, event_loop);
912        return;
913      }
914      _ => {}
915    }
916
917    let Some(appwindow) = self.state.windows.get_mut(&window_id) else {
918      return;
919    };
920    let window = &appwindow.window;
921
922    match message {
923      WindowMessage::AddEventListener(id, listener) => {
924        appwindow.listeners.lock().unwrap().insert(id, listener);
925      }
926      WindowMessage::Close | WindowMessage::Destroy => unreachable!("handled before borrowing"),
927      WindowMessage::ScaleFactor(tx) => _ = tx.send(Ok(window.scale_factor())),
928      WindowMessage::InnerSize(tx) => _ = tx.send(Ok(window.surface_size())),
929      WindowMessage::OuterSize(tx) => _ = tx.send(Ok(window.outer_size())),
930      WindowMessage::IsFullscreen(tx) => _ = tx.send(Ok(window.fullscreen().is_some())),
931      WindowMessage::IsMinimized(tx) => _ = tx.send(Ok(window.is_minimized().unwrap_or(false))),
932      WindowMessage::IsMaximized(tx) => _ = tx.send(Ok(window.is_maximized())),
933      WindowMessage::IsFocused(tx) => _ = tx.send(Ok(window.has_focus())),
934      WindowMessage::IsDecorated(tx) => _ = tx.send(Ok(window.is_decorated())),
935      WindowMessage::IsResizable(tx) => _ = tx.send(Ok(window.is_resizable())),
936      WindowMessage::IsMaximizable(tx) => {
937        let is_maximizable = window
938          .enabled_buttons()
939          .contains(winit::window::WindowButtons::MAXIMIZE);
940        let _ = tx.send(Ok(is_maximizable));
941      }
942      WindowMessage::IsMinimizable(tx) => {
943        let is_minimizable = window
944          .enabled_buttons()
945          .contains(winit::window::WindowButtons::MINIMIZE);
946        let _ = tx.send(Ok(is_minimizable));
947      }
948      WindowMessage::IsClosable(tx) => {
949        let is_closable = window
950          .enabled_buttons()
951          .contains(winit::window::WindowButtons::CLOSE);
952        let _ = tx.send(Ok(is_closable));
953      }
954      WindowMessage::IsVisible(tx) => _ = tx.send(Ok(window.is_visible().unwrap_or(true))),
955      WindowMessage::IsEnabled(tx) => _ = tx.send(Ok(appwindow.is_enabled())),
956      WindowMessage::IsAlwaysOnTop(tx) => {
957        let is_on_top = appwindow.attrs.inner.window_level == WindowLevel::AlwaysOnTop;
958        let _ = tx.send(Ok(is_on_top));
959      }
960      WindowMessage::Title(tx) => _ = tx.send(Ok(window.title())),
961      WindowMessage::InnerPosition(tx) => _ = tx.send(Ok(window.surface_position())),
962      WindowMessage::OuterPosition(tx) => {
963        let pos = window
964          .outer_position()
965          .map_err(|_| Error::FailedToGetMonitor);
966        let _ = tx.send(pos);
967      }
968      WindowMessage::CurrentMonitor(tx) => {
969        let current = window.current_monitor();
970        let current = current.map(|m| winit_monitor_to_tauri_monitor(&m));
971        let _ = tx.send(Ok(current));
972      }
973      WindowMessage::PrimaryMonitor(tx) => {
974        let primary = window.primary_monitor();
975        let primary = primary.map(|m| winit_monitor_to_tauri_monitor(&m));
976        let _ = tx.send(Ok(primary));
977      }
978      WindowMessage::MonitorFromPoint(tx, x, y) => {
979        let mut available_monitors = window.available_monitors();
980        let monitor = available_monitors
981          .find(|m| {
982            let pos = m.position().unwrap_or_default();
983            let vm = m.current_video_mode();
984            let size = vm.map(|v| v.size()).unwrap_or_default();
985            x >= pos.x as f64
986              && x <= pos.x as f64 + size.width as f64
987              && y >= pos.y as f64
988              && y <= pos.y as f64 + size.height as f64
989          })
990          .map(|m| winit_monitor_to_tauri_monitor(&m));
991        let _ = tx.send(Ok(monitor));
992      }
993      WindowMessage::AvailableMonitors(tx) => {
994        let monitors = window
995          .available_monitors()
996          .map(|m| winit_monitor_to_tauri_monitor(&m))
997          .collect();
998        let _ = tx.send(Ok(monitors));
999      }
1000      #[cfg(any(
1001        target_os = "linux",
1002        target_os = "dragonfly",
1003        target_os = "freebsd",
1004        target_os = "netbsd",
1005        target_os = "openbsd"
1006      ))]
1007      WindowMessage::GtkWindow(tx) => {
1008        use gtk::glib::translate::ToGlibPtr;
1009        use winit::platform::gtk4::WindowExtGtk4;
1010
1011        let gtk_window = appwindow.window.gtk_window().unwrap();
1012        let ptr: *mut gtk::ffi::GtkApplicationWindow = gtk_window.to_glib_full();
1013        let _ = tx.send(Ok(SendGtkWindow(ptr as *mut std::ffi::c_void)));
1014      }
1015      #[cfg(any(
1016        target_os = "linux",
1017        target_os = "dragonfly",
1018        target_os = "freebsd",
1019        target_os = "netbsd",
1020        target_os = "openbsd"
1021      ))]
1022      WindowMessage::DefaultVBox(tx) => {
1023        use gtk::glib::translate::ToGlibPtr;
1024
1025        let default_vbox = appwindow.cef_host.default_vbox();
1026        let ptr: *mut gtk::ffi::GtkBox = default_vbox.to_glib_full();
1027        let _ = tx.send(Ok(SendGtkBox(ptr as *mut std::ffi::c_void)));
1028      }
1029      WindowMessage::RawWindowHandle(tx) => {
1030        let handle = window.window_handle();
1031        let send_handle = handle
1032          .map(|h| SendRawWindowHandle(h.as_raw()))
1033          .map_err(|_| Error::FailedToSendMessage);
1034        let _ = tx.send(send_handle);
1035      }
1036      WindowMessage::Theme(tx) => {
1037        let theme = window.theme();
1038        let theme = theme.map(winit_theme_to_tauri_theme);
1039        let theme = theme.unwrap_or(Theme::Light);
1040        let _ = tx.send(Ok(theme));
1041      }
1042      WindowMessage::Center => {
1043        appwindow.center();
1044      }
1045      WindowMessage::RequestUserAttention(attention) => {
1046        window.request_user_attention(match attention {
1047          Some(UserAttentionType::Critical) => Some(winit::window::UserAttentionType::Critical),
1048          Some(UserAttentionType::Informational) => {
1049            Some(winit::window::UserAttentionType::Informational)
1050          }
1051          None => None,
1052        })
1053      }
1054      WindowMessage::SetEnabled(value) => appwindow.set_enabled(value),
1055      WindowMessage::SetResizable(value) => window.set_resizable(value),
1056      WindowMessage::SetTitle(title) => window.set_title(&title),
1057      WindowMessage::Maximize => window.set_maximized(true),
1058      WindowMessage::Unmaximize => window.set_maximized(false),
1059      WindowMessage::Minimize => window.set_minimized(true),
1060      WindowMessage::Unminimize => window.set_minimized(false),
1061      WindowMessage::Show => window.set_visible(true),
1062      WindowMessage::Hide => window.set_visible(false),
1063      WindowMessage::SetDecorations(value) => {
1064        window.set_decorations(value);
1065        #[cfg(target_os = "macos")]
1066        appwindow.apply_movability();
1067      }
1068      WindowMessage::SetSize(size) => _ = window.request_surface_size(size),
1069      WindowMessage::SetPosition(position) => window.set_outer_position(position),
1070      WindowMessage::SetFullscreen(value) => {
1071        appwindow.request_fullscreen(value.then_some(FullscreenTarget::CurrentMonitor))
1072      }
1073      WindowMessage::SetFullscreenOnMonitor(position) => {
1074        appwindow.request_fullscreen(Some(FullscreenTarget::MonitorAt(position)))
1075      }
1076      #[cfg(target_os = "macos")]
1077      WindowMessage::ApplyPendingFullscreen => {
1078        if let Some(target) = appwindow.fullscreen_transition.take_pending() {
1079          appwindow.request_fullscreen(target);
1080        }
1081      }
1082      #[cfg(target_os = "macos")]
1083      WindowMessage::SetSimpleFullscreen(value) => {
1084        let changed = window.set_simple_fullscreen(value);
1085        if changed && !value {
1086          appwindow.apply_movability();
1087        }
1088      }
1089      WindowMessage::SetFocus => appwindow.activate(),
1090      WindowMessage::SetMinSize(min_size) => window.set_min_surface_size(min_size),
1091      WindowMessage::SetMaxSize(max_size) => window.set_max_surface_size(max_size),
1092      WindowMessage::SetMaximizable(value) => {
1093        let mut buttons = window.enabled_buttons();
1094        buttons.set(winit::window::WindowButtons::MAXIMIZE, value);
1095        window.set_enabled_buttons(buttons);
1096      }
1097      WindowMessage::SetMinimizable(value) => {
1098        let mut buttons = window.enabled_buttons();
1099        buttons.set(winit::window::WindowButtons::MINIMIZE, value);
1100        window.set_enabled_buttons(buttons);
1101      }
1102      WindowMessage::SetClosable(value) => {
1103        let mut buttons = window.enabled_buttons();
1104        buttons.set(winit::window::WindowButtons::CLOSE, value);
1105        window.set_enabled_buttons(buttons);
1106      }
1107      WindowMessage::SetAlwaysOnBottom(value) => {
1108        let level = match value {
1109          true => WindowLevel::AlwaysOnBottom,
1110          false => WindowLevel::Normal,
1111        };
1112        appwindow.attrs.inner.window_level = level;
1113        window.set_window_level(level);
1114      }
1115      WindowMessage::SetAlwaysOnTop(value) => {
1116        let level = match value {
1117          true => WindowLevel::AlwaysOnTop,
1118          false => WindowLevel::Normal,
1119        };
1120        appwindow.attrs.inner.window_level = level;
1121        window.set_window_level(level);
1122      }
1123      WindowMessage::SetVisibleOnAllWorkspaces(_value) => {
1124        #[cfg(target_os = "macos")]
1125        {
1126          appwindow.attrs.visible_on_all_workspaces = _value;
1127          appwindow.set_visible_on_all_workspaces(_value);
1128        }
1129        #[cfg(any(
1130          target_os = "linux",
1131          target_os = "dragonfly",
1132          target_os = "freebsd",
1133          target_os = "netbsd",
1134          target_os = "openbsd"
1135        ))]
1136        {
1137          appwindow.attrs.visible_on_all_workspaces = _value;
1138          appwindow.set_visible_on_all_workspaces(_value);
1139        }
1140      }
1141      WindowMessage::SetContentProtected(value) => window.set_content_protected(value),
1142      WindowMessage::SetIcon(icon) => {
1143        if let Ok(icon) = super::window::tauri_icon_to_winit_icon(icon) {
1144          window.set_window_icon(Some(icon))
1145        }
1146      }
1147      WindowMessage::SetSkipTaskbar(_value) => {
1148        #[cfg(windows)]
1149        window.set_skip_taskbar(_value);
1150        #[cfg(any(
1151          target_os = "linux",
1152          target_os = "dragonfly",
1153          target_os = "freebsd",
1154          target_os = "netbsd",
1155          target_os = "openbsd"
1156        ))]
1157        appwindow.set_skip_taskbar(_value);
1158      }
1159      WindowMessage::SetShadow(_value) => {
1160        #[cfg(windows)]
1161        window.set_undecorated_shadow(_value);
1162        #[cfg(target_os = "macos")]
1163        window.set_has_shadow(_value);
1164      }
1165      WindowMessage::SetCursorGrab(value) => {
1166        let _ = window.set_cursor_grab(match value {
1167          true => winit::window::CursorGrabMode::Confined,
1168          false => winit::window::CursorGrabMode::None,
1169        });
1170      }
1171      WindowMessage::SetCursorVisible(value) => window.set_cursor_visible(value),
1172      WindowMessage::SetCursorIcon(value) => {
1173        let cursor_icon = tauri_cursor_to_winit_cursor(value);
1174        window.set_cursor(cursor_icon.into())
1175      }
1176      WindowMessage::SetCursorPosition(value) => _ = window.set_cursor_position(value),
1177      WindowMessage::SetIgnoreCursorEvents(value) => _ = window.set_cursor_hittest(!value),
1178
1179      WindowMessage::SetTrafficLightPosition(_position) => {
1180        #[cfg(target_os = "macos")]
1181        {
1182          appwindow.attrs.traffic_light_position = Some(_position);
1183          appwindow.set_traffic_light_position(&_position);
1184        }
1185      }
1186      WindowMessage::SetTitleBarStyle(_style) => {
1187        #[cfg(target_os = "macos")]
1188        appwindow.set_title_bar_style(_style);
1189      }
1190      WindowMessage::SetBackgroundColor(color) => {
1191        appwindow.attrs.background_color = color;
1192        appwindow.set_background_color(color);
1193      }
1194      WindowMessage::SetTheme(theme) => appwindow.set_theme(theme),
1195      WindowMessage::SetBadgeCount(count, desktop_filename) => {
1196        event_loop.set_badge_count(count, desktop_filename)
1197      }
1198      WindowMessage::SetBadgeLabel(label) => event_loop.set_badge_label(label),
1199      WindowMessage::SetOverlayIcon(_icon) => {
1200        #[cfg(windows)]
1201        appwindow.set_overlay_icon(_icon);
1202      }
1203      WindowMessage::StartDragging => _ = window.drag_window(),
1204      WindowMessage::StartResizeDragging(direction) => {
1205        let _ = window.drag_resize_window(tauri_resize_direction_to_winit(direction));
1206      }
1207      WindowMessage::SetSizeConstraints(constraints) => {
1208        // TODO: upstream individual width/height size constraints to winit.
1209        let min_size = min_size_constraint(constraints.min_width, constraints.min_height);
1210        let max_size = max_size_constraint(constraints.max_width, constraints.max_height);
1211        window.set_min_surface_size(min_size);
1212        window.set_max_surface_size(max_size);
1213      }
1214
1215      WindowMessage::SetFocusable(_) => {
1216        // TODO
1217      }
1218      WindowMessage::SetProgressBar(state) => {
1219        #[cfg(target_os = "macos")]
1220        event_loop.set_progress_bar(state);
1221        #[cfg(not(target_os = "macos"))]
1222        appwindow.set_progress_bar(state);
1223      }
1224    }
1225  }
1226}
1227
1228#[derive(Debug, Clone)]
1229pub struct CefWindowDispatcher<T: UserEvent> {
1230  pub(crate) window_id: WindowId,
1231  pub(crate) context: RuntimeContext<T>,
1232}
1233
1234fn getter<T: UserEvent, R>(
1235  context: &RuntimeContext<T>,
1236  message: Message<T>,
1237  receiver: Receiver<Result<R>>,
1238) -> Result<R> {
1239  context.send_message(message)?;
1240  receiver.recv().map_err(|_| Error::FailedToReceiveMessage)?
1241}
1242
1243macro_rules! window_getter {
1244  ($self:ident, $variant:ident) => {{
1245    let (tx, rx) = mpsc::channel();
1246    getter(
1247      &$self.context,
1248      Message::Window {
1249        window_id: $self.window_id,
1250        message: WindowMessage::$variant(tx),
1251      },
1252      rx,
1253    )
1254  }};
1255}
1256
1257impl<T: UserEvent> WindowDispatch<T> for CefWindowDispatcher<T> {
1258  type Runtime = CefRuntime<T>;
1259  type WindowBuilder = WindowBuilderWrapper;
1260
1261  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()> {
1262    self.context.run_on_main_thread(f)
1263  }
1264
1265  fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> WindowEventId {
1266    let id = self.context.next_window_event_id();
1267    let _ = self.context.send_message(Message::Window {
1268      window_id: self.window_id,
1269      message: WindowMessage::AddEventListener(id, Box::new(f)),
1270    });
1271    id
1272  }
1273
1274  fn scale_factor(&self) -> Result<f64> {
1275    window_getter!(self, ScaleFactor)
1276  }
1277
1278  fn inner_position(&self) -> Result<PhysicalPosition<i32>> {
1279    window_getter!(self, InnerPosition)
1280  }
1281
1282  fn outer_position(&self) -> Result<PhysicalPosition<i32>> {
1283    window_getter!(self, OuterPosition)
1284  }
1285
1286  fn inner_size(&self) -> Result<PhysicalSize<u32>> {
1287    window_getter!(self, InnerSize)
1288  }
1289
1290  fn outer_size(&self) -> Result<PhysicalSize<u32>> {
1291    window_getter!(self, OuterSize)
1292  }
1293
1294  fn is_fullscreen(&self) -> Result<bool> {
1295    window_getter!(self, IsFullscreen)
1296  }
1297
1298  fn is_minimized(&self) -> Result<bool> {
1299    window_getter!(self, IsMinimized)
1300  }
1301
1302  fn is_maximized(&self) -> Result<bool> {
1303    window_getter!(self, IsMaximized)
1304  }
1305
1306  fn is_focused(&self) -> Result<bool> {
1307    window_getter!(self, IsFocused)
1308  }
1309
1310  fn is_decorated(&self) -> Result<bool> {
1311    window_getter!(self, IsDecorated)
1312  }
1313
1314  fn is_resizable(&self) -> Result<bool> {
1315    window_getter!(self, IsResizable)
1316  }
1317
1318  fn is_maximizable(&self) -> Result<bool> {
1319    window_getter!(self, IsMaximizable)
1320  }
1321
1322  fn is_minimizable(&self) -> Result<bool> {
1323    window_getter!(self, IsMinimizable)
1324  }
1325
1326  fn is_closable(&self) -> Result<bool> {
1327    window_getter!(self, IsClosable)
1328  }
1329
1330  fn is_visible(&self) -> Result<bool> {
1331    window_getter!(self, IsVisible)
1332  }
1333
1334  fn is_enabled(&self) -> Result<bool> {
1335    window_getter!(self, IsEnabled)
1336  }
1337
1338  fn is_always_on_top(&self) -> Result<bool> {
1339    window_getter!(self, IsAlwaysOnTop)
1340  }
1341
1342  fn title(&self) -> Result<String> {
1343    window_getter!(self, Title)
1344  }
1345
1346  fn current_monitor(&self) -> Result<Option<Monitor>> {
1347    window_getter!(self, CurrentMonitor)
1348  }
1349
1350  fn primary_monitor(&self) -> Result<Option<Monitor>> {
1351    window_getter!(self, PrimaryMonitor)
1352  }
1353
1354  fn monitor_from_point(&self, x: f64, y: f64) -> Result<Option<Monitor>> {
1355    let (tx, rx) = mpsc::channel();
1356    getter(
1357      &self.context,
1358      Message::Window {
1359        window_id: self.window_id,
1360        message: WindowMessage::MonitorFromPoint(tx, x, y),
1361      },
1362      rx,
1363    )
1364  }
1365
1366  fn available_monitors(&self) -> Result<Vec<Monitor>> {
1367    window_getter!(self, AvailableMonitors)
1368  }
1369
1370  #[cfg(any(
1371    target_os = "linux",
1372    target_os = "dragonfly",
1373    target_os = "freebsd",
1374    target_os = "netbsd",
1375    target_os = "openbsd"
1376  ))]
1377  fn gtk_window(&self) -> Result<*mut std::ffi::c_void> {
1378    window_getter!(self, GtkWindow).map(|gtk_window| gtk_window.0)
1379  }
1380
1381  #[cfg(any(
1382    target_os = "linux",
1383    target_os = "dragonfly",
1384    target_os = "freebsd",
1385    target_os = "netbsd",
1386    target_os = "openbsd"
1387  ))]
1388  fn default_vbox(&self) -> Result<*mut std::ffi::c_void> {
1389    window_getter!(self, DefaultVBox).map(|gtk_box| gtk_box.0)
1390  }
1391
1392  fn window_handle(
1393    &self,
1394  ) -> std::result::Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
1395    let handle: Result<SendRawWindowHandle> = window_getter!(self, RawWindowHandle);
1396    let handle = handle.map_err(|_| raw_window_handle::HandleError::Unavailable)?;
1397    Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(handle.0) })
1398  }
1399
1400  fn theme(&self) -> Result<Theme> {
1401    window_getter!(self, Theme)
1402  }
1403
1404  fn center(&self) -> Result<()> {
1405    self.context.send_message(Message::Window {
1406      window_id: self.window_id,
1407      message: WindowMessage::Center,
1408    })
1409  }
1410
1411  fn request_user_attention(&self, request_type: Option<UserAttentionType>) -> Result<()> {
1412    self.context.send_message(Message::Window {
1413      window_id: self.window_id,
1414      message: WindowMessage::RequestUserAttention(request_type),
1415    })
1416  }
1417
1418  fn create_window<F: Fn(RawWindow<'_>) + Send + 'static>(
1419    &mut self,
1420    pending: PendingWindow<T, Self::Runtime>,
1421    after_window_creation: Option<F>,
1422  ) -> Result<DetachedWindow<T, Self::Runtime>> {
1423    create_window_detached(&self.context, pending, after_window_creation)
1424  }
1425
1426  fn create_webview(
1427    &mut self,
1428    pending: PendingWebview<T, Self::Runtime>,
1429  ) -> Result<DetachedWebview<T, Self::Runtime>> {
1430    create_webview_detached(&self.context, self.window_id, pending)
1431  }
1432
1433  fn set_resizable(&self, resizable: bool) -> Result<()> {
1434    self.context.send_message(Message::Window {
1435      window_id: self.window_id,
1436      message: WindowMessage::SetResizable(resizable),
1437    })
1438  }
1439
1440  fn set_enabled(&self, enabled: bool) -> Result<()> {
1441    self.context.send_message(Message::Window {
1442      window_id: self.window_id,
1443      message: WindowMessage::SetEnabled(enabled),
1444    })
1445  }
1446
1447  fn set_maximizable(&self, maximizable: bool) -> Result<()> {
1448    self.context.send_message(Message::Window {
1449      window_id: self.window_id,
1450      message: WindowMessage::SetMaximizable(maximizable),
1451    })
1452  }
1453
1454  fn set_minimizable(&self, minimizable: bool) -> Result<()> {
1455    self.context.send_message(Message::Window {
1456      window_id: self.window_id,
1457      message: WindowMessage::SetMinimizable(minimizable),
1458    })
1459  }
1460
1461  fn set_closable(&self, closable: bool) -> Result<()> {
1462    self.context.send_message(Message::Window {
1463      window_id: self.window_id,
1464      message: WindowMessage::SetClosable(closable),
1465    })
1466  }
1467
1468  fn set_title<S: Into<String>>(&self, title: S) -> Result<()> {
1469    self.context.send_message(Message::Window {
1470      window_id: self.window_id,
1471      message: WindowMessage::SetTitle(title.into()),
1472    })
1473  }
1474
1475  fn maximize(&self) -> Result<()> {
1476    self.context.send_message(Message::Window {
1477      window_id: self.window_id,
1478      message: WindowMessage::Maximize,
1479    })
1480  }
1481
1482  fn unmaximize(&self) -> Result<()> {
1483    self.context.send_message(Message::Window {
1484      window_id: self.window_id,
1485      message: WindowMessage::Unmaximize,
1486    })
1487  }
1488
1489  fn minimize(&self) -> Result<()> {
1490    self.context.send_message(Message::Window {
1491      window_id: self.window_id,
1492      message: WindowMessage::Minimize,
1493    })
1494  }
1495
1496  fn unminimize(&self) -> Result<()> {
1497    self.context.send_message(Message::Window {
1498      window_id: self.window_id,
1499      message: WindowMessage::Unminimize,
1500    })
1501  }
1502
1503  fn show(&self) -> Result<()> {
1504    self.context.send_message(Message::Window {
1505      window_id: self.window_id,
1506      message: WindowMessage::Show,
1507    })
1508  }
1509
1510  fn hide(&self) -> Result<()> {
1511    self.context.send_message(Message::Window {
1512      window_id: self.window_id,
1513      message: WindowMessage::Hide,
1514    })
1515  }
1516
1517  fn close(&self) -> Result<()> {
1518    self.context.send_message(Message::Window {
1519      window_id: self.window_id,
1520      message: WindowMessage::Close,
1521    })
1522  }
1523
1524  fn destroy(&self) -> Result<()> {
1525    self.context.send_message(Message::Window {
1526      window_id: self.window_id,
1527      message: WindowMessage::Destroy,
1528    })
1529  }
1530
1531  fn set_decorations(&self, decorations: bool) -> Result<()> {
1532    self.context.send_message(Message::Window {
1533      window_id: self.window_id,
1534      message: WindowMessage::SetDecorations(decorations),
1535    })
1536  }
1537
1538  fn set_shadow(&self, enable: bool) -> Result<()> {
1539    self.context.send_message(Message::Window {
1540      window_id: self.window_id,
1541      message: WindowMessage::SetShadow(enable),
1542    })
1543  }
1544
1545  fn set_always_on_bottom(&self, value: bool) -> Result<()> {
1546    self.context.send_message(Message::Window {
1547      window_id: self.window_id,
1548      message: WindowMessage::SetAlwaysOnBottom(value),
1549    })
1550  }
1551
1552  fn set_always_on_top(&self, value: bool) -> Result<()> {
1553    self.context.send_message(Message::Window {
1554      window_id: self.window_id,
1555      message: WindowMessage::SetAlwaysOnTop(value),
1556    })
1557  }
1558
1559  fn set_visible_on_all_workspaces(&self, value: bool) -> Result<()> {
1560    self.context.send_message(Message::Window {
1561      window_id: self.window_id,
1562      message: WindowMessage::SetVisibleOnAllWorkspaces(value),
1563    })
1564  }
1565
1566  fn set_content_protected(&self, protected: bool) -> Result<()> {
1567    self.context.send_message(Message::Window {
1568      window_id: self.window_id,
1569      message: WindowMessage::SetContentProtected(protected),
1570    })
1571  }
1572
1573  fn set_size(&self, size: Size) -> Result<()> {
1574    self.context.send_message(Message::Window {
1575      window_id: self.window_id,
1576      message: WindowMessage::SetSize(size),
1577    })
1578  }
1579
1580  fn set_min_size(&self, size: Option<Size>) -> Result<()> {
1581    self.context.send_message(Message::Window {
1582      window_id: self.window_id,
1583      message: WindowMessage::SetMinSize(size),
1584    })
1585  }
1586
1587  fn set_max_size(&self, size: Option<Size>) -> Result<()> {
1588    self.context.send_message(Message::Window {
1589      window_id: self.window_id,
1590      message: WindowMessage::SetMaxSize(size),
1591    })
1592  }
1593
1594  fn set_size_constraints(&self, constraints: WindowSizeConstraints) -> Result<()> {
1595    self.context.send_message(Message::Window {
1596      window_id: self.window_id,
1597      message: WindowMessage::SetSizeConstraints(constraints),
1598    })
1599  }
1600
1601  fn set_position(&self, position: Position) -> Result<()> {
1602    self.context.send_message(Message::Window {
1603      window_id: self.window_id,
1604      message: WindowMessage::SetPosition(position),
1605    })
1606  }
1607
1608  fn set_fullscreen(&self, fullscreen: bool) -> Result<()> {
1609    self.context.send_message(Message::Window {
1610      window_id: self.window_id,
1611      message: WindowMessage::SetFullscreen(fullscreen),
1612    })
1613  }
1614
1615  fn set_fullscreen_on_monitor(&self, position: PhysicalPosition<f64>) -> Result<()> {
1616    self.context.send_message(Message::Window {
1617      window_id: self.window_id,
1618      message: WindowMessage::SetFullscreenOnMonitor(position),
1619    })
1620  }
1621
1622  #[cfg(target_os = "macos")]
1623  fn set_simple_fullscreen(&self, enable: bool) -> Result<()> {
1624    self.context.send_message(Message::Window {
1625      window_id: self.window_id,
1626      message: WindowMessage::SetSimpleFullscreen(enable),
1627    })
1628  }
1629
1630  fn set_focus(&self) -> Result<()> {
1631    self.context.send_message(Message::Window {
1632      window_id: self.window_id,
1633      message: WindowMessage::SetFocus,
1634    })
1635  }
1636
1637  fn set_focusable(&self, focusable: bool) -> Result<()> {
1638    self.context.send_message(Message::Window {
1639      window_id: self.window_id,
1640      message: WindowMessage::SetFocusable(focusable),
1641    })
1642  }
1643
1644  fn set_icon(&self, icon: Icon) -> Result<()> {
1645    self.context.send_message(Message::Window {
1646      window_id: self.window_id,
1647      message: WindowMessage::SetIcon(icon.into_owned()),
1648    })
1649  }
1650
1651  fn set_skip_taskbar(&self, skip: bool) -> Result<()> {
1652    self.context.send_message(Message::Window {
1653      window_id: self.window_id,
1654      message: WindowMessage::SetSkipTaskbar(skip),
1655    })
1656  }
1657
1658  fn set_cursor_grab(&self, grab: bool) -> Result<()> {
1659    self.context.send_message(Message::Window {
1660      window_id: self.window_id,
1661      message: WindowMessage::SetCursorGrab(grab),
1662    })
1663  }
1664
1665  fn set_cursor_visible(&self, visible: bool) -> Result<()> {
1666    self.context.send_message(Message::Window {
1667      window_id: self.window_id,
1668      message: WindowMessage::SetCursorVisible(visible),
1669    })
1670  }
1671
1672  fn set_cursor_icon(&self, icon: CursorIcon) -> Result<()> {
1673    self.context.send_message(Message::Window {
1674      window_id: self.window_id,
1675      message: WindowMessage::SetCursorIcon(icon),
1676    })
1677  }
1678
1679  fn set_cursor_position<Pos: Into<Position>>(&self, position: Pos) -> Result<()> {
1680    self.context.send_message(Message::Window {
1681      window_id: self.window_id,
1682      message: WindowMessage::SetCursorPosition(position.into()),
1683    })
1684  }
1685
1686  fn set_ignore_cursor_events(&self, ignore: bool) -> Result<()> {
1687    self.context.send_message(Message::Window {
1688      window_id: self.window_id,
1689      message: WindowMessage::SetIgnoreCursorEvents(ignore),
1690    })
1691  }
1692
1693  fn start_dragging(&self) -> Result<()> {
1694    self.context.send_message(Message::Window {
1695      window_id: self.window_id,
1696      message: WindowMessage::StartDragging,
1697    })
1698  }
1699
1700  fn start_resize_dragging(&self, direction: tauri_runtime::ResizeDirection) -> Result<()> {
1701    self.context.send_message(Message::Window {
1702      window_id: self.window_id,
1703      message: WindowMessage::StartResizeDragging(direction),
1704    })
1705  }
1706
1707  fn set_badge_count(&self, count: Option<i64>, desktop_filename: Option<String>) -> Result<()> {
1708    self.context.send_message(Message::Window {
1709      window_id: self.window_id,
1710      message: WindowMessage::SetBadgeCount(count, desktop_filename),
1711    })
1712  }
1713
1714  fn set_badge_label(&self, label: Option<String>) -> Result<()> {
1715    self.context.send_message(Message::Window {
1716      window_id: self.window_id,
1717      message: WindowMessage::SetBadgeLabel(label),
1718    })
1719  }
1720
1721  fn set_overlay_icon(&self, icon: Option<Icon>) -> Result<()> {
1722    self.context.send_message(Message::Window {
1723      window_id: self.window_id,
1724      message: WindowMessage::SetOverlayIcon(icon.map(Icon::into_owned)),
1725    })
1726  }
1727
1728  fn set_progress_bar(&self, progress_state: ProgressBarState) -> Result<()> {
1729    self.context.send_message(Message::Window {
1730      window_id: self.window_id,
1731      message: WindowMessage::SetProgressBar(progress_state),
1732    })
1733  }
1734
1735  fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> Result<()> {
1736    self.context.send_message(Message::Window {
1737      window_id: self.window_id,
1738      message: WindowMessage::SetTitleBarStyle(style),
1739    })
1740  }
1741
1742  fn set_traffic_light_position(&self, position: Position) -> Result<()> {
1743    self.context.send_message(Message::Window {
1744      window_id: self.window_id,
1745      message: WindowMessage::SetTrafficLightPosition(position),
1746    })
1747  }
1748
1749  fn set_theme(&self, theme: Option<Theme>) -> Result<()> {
1750    self.context.send_message(Message::Window {
1751      window_id: self.window_id,
1752      message: WindowMessage::SetTheme(theme),
1753    })
1754  }
1755
1756  fn set_background_color(&self, color: Option<Color>) -> Result<()> {
1757    self.context.send_message(Message::Window {
1758      window_id: self.window_id,
1759      message: WindowMessage::SetBackgroundColor(color),
1760    })
1761  }
1762}
1763
1764pub(crate) fn create_window_detached<T, F>(
1765  context: &RuntimeContext<T>,
1766  pending: PendingWindow<T, CefRuntime<T>>,
1767  after_window_creation: Option<F>,
1768) -> Result<DetachedWindow<T, CefRuntime<T>>>
1769where
1770  T: UserEvent,
1771  F: Fn(RawWindow<'_>) + Send + 'static,
1772{
1773  let label = pending.label.clone();
1774  let window_id = context.next_window_id();
1775  let (webview_id, use_https_scheme, devtools) = pending
1776    .webview
1777    .as_ref()
1778    .map(|w| {
1779      (
1780        Some(context.next_webview_id()),
1781        w.webview_attributes.use_https_scheme,
1782        w.webview_attributes.devtools,
1783      )
1784    })
1785    .unwrap_or((None, false, None));
1786
1787  let (result_tx, result_rx) = mpsc::channel();
1788  context.send_message(Message::CreateWindow {
1789    window_id,
1790    webview_id,
1791    pending: Box::new(pending),
1792    after_window_creation: after_window_creation.map(|f| Box::new(f) as _),
1793    result_tx,
1794  })?;
1795  // Wait until the event loop has created the window so a creation failure is
1796  // surfaced to the caller instead of leaving a detached, dead window.
1797  context.wait_for_creation(result_rx)?;
1798
1799  let webview = webview_id.map(|webview_id| DetachedWindowWebview {
1800    webview: DetachedWebview {
1801      label: label.clone(),
1802      dispatcher: CefWebviewDispatcher {
1803        window_id: Arc::new(Mutex::new(window_id)),
1804        webview_id,
1805        context: context.clone(),
1806      },
1807    },
1808    use_https_scheme,
1809    devtools,
1810  });
1811
1812  Ok(DetachedWindow {
1813    id: window_id,
1814    label,
1815    dispatcher: CefWindowDispatcher {
1816      window_id,
1817      context: context.clone(),
1818    },
1819    webview,
1820  })
1821}
1822
1823pub(crate) fn winit_monitor_to_tauri_monitor(monitor: &winit::monitor::MonitorHandle) -> Monitor {
1824  Monitor {
1825    name: monitor.name().map(|s| s.to_string()),
1826    scale_factor: monitor.scale_factor(),
1827    position: monitor.position().unwrap_or_default(),
1828    size: monitor
1829      .current_video_mode()
1830      .map(|v| v.size())
1831      .unwrap_or_default(),
1832    work_area: monitor.work_area(),
1833  }
1834}
1835
1836pub(crate) fn tauri_icon_to_winit_icon(icon: Icon) -> Result<winit::icon::Icon> {
1837  winit::icon::RgbaIcon::new(icon.rgba.into_owned(), icon.width, icon.height)
1838    .map(Into::into)
1839    .map_err(|e| tauri_runtime::Error::InvalidIcon(e.into()))
1840}
1841
1842fn tauri_cursor_to_winit_cursor(cursor: CursorIcon) -> winit::cursor::CursorIcon {
1843  match cursor {
1844    CursorIcon::Default => winit::cursor::CursorIcon::Default,
1845    CursorIcon::Crosshair => winit::cursor::CursorIcon::Crosshair,
1846    CursorIcon::Hand => winit::cursor::CursorIcon::Grab,
1847    CursorIcon::Arrow => winit::cursor::CursorIcon::Default,
1848    CursorIcon::Move => winit::cursor::CursorIcon::Move,
1849    CursorIcon::Text => winit::cursor::CursorIcon::Text,
1850    CursorIcon::Wait => winit::cursor::CursorIcon::Wait,
1851    CursorIcon::Help => winit::cursor::CursorIcon::Help,
1852    CursorIcon::Progress => winit::cursor::CursorIcon::Progress,
1853    CursorIcon::NotAllowed => winit::cursor::CursorIcon::NotAllowed,
1854    CursorIcon::ContextMenu => winit::cursor::CursorIcon::ContextMenu,
1855    CursorIcon::Cell => winit::cursor::CursorIcon::Cell,
1856    CursorIcon::VerticalText => winit::cursor::CursorIcon::VerticalText,
1857    CursorIcon::Alias => winit::cursor::CursorIcon::Alias,
1858    CursorIcon::Copy => winit::cursor::CursorIcon::Copy,
1859    CursorIcon::NoDrop => winit::cursor::CursorIcon::NoDrop,
1860    CursorIcon::Grab => winit::cursor::CursorIcon::Grab,
1861    CursorIcon::Grabbing => winit::cursor::CursorIcon::Grabbing,
1862    CursorIcon::AllScroll => winit::cursor::CursorIcon::AllScroll,
1863    CursorIcon::ZoomIn => winit::cursor::CursorIcon::ZoomIn,
1864    CursorIcon::ZoomOut => winit::cursor::CursorIcon::ZoomOut,
1865    CursorIcon::EResize => winit::cursor::CursorIcon::EResize,
1866    CursorIcon::NResize => winit::cursor::CursorIcon::NResize,
1867    CursorIcon::NeResize => winit::cursor::CursorIcon::NeResize,
1868    CursorIcon::NwResize => winit::cursor::CursorIcon::NwResize,
1869    CursorIcon::SResize => winit::cursor::CursorIcon::SResize,
1870    CursorIcon::SeResize => winit::cursor::CursorIcon::SeResize,
1871    CursorIcon::SwResize => winit::cursor::CursorIcon::SwResize,
1872    CursorIcon::WResize => winit::cursor::CursorIcon::WResize,
1873    CursorIcon::EwResize => winit::cursor::CursorIcon::EwResize,
1874    CursorIcon::NsResize => winit::cursor::CursorIcon::NsResize,
1875    CursorIcon::NeswResize => winit::cursor::CursorIcon::NeswResize,
1876    CursorIcon::NwseResize => winit::cursor::CursorIcon::NwseResize,
1877    CursorIcon::ColResize => winit::cursor::CursorIcon::ColResize,
1878    CursorIcon::RowResize => winit::cursor::CursorIcon::RowResize,
1879    _ => winit::cursor::CursorIcon::Default,
1880  }
1881}