// Copyright 2019-2024 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT //! [![](https://github.com/tauri-apps/tauri/raw/dev/.github/splash.png)](https://tauri.app) //! //! The [`wry`] Tauri [`Runtime`]. //! //! None of the exposed API of this crate is stable, and it may break semver //! compatibility in the future. The major version only signifies the intended Tauri version. #![doc( html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png", html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png" )] use http::Request; use raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle}; use tauri_runtime::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, monitor::Monitor, webview::{DetachedWebview, DownloadEvent, PendingWebview, WebviewIpcHandler}, window::{ CursorIcon, DetachedWindow, DragDropEvent, PendingWindow, RawWindow, WebviewEvent, WindowBuilder, WindowBuilderBase, WindowEvent, WindowId, WindowSizeConstraints, }, DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Icon, ProgressBarState, ProgressBarStatus, Result, RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, WebviewDispatch, WebviewEventId, WindowDispatch, WindowEventId, }; #[cfg(target_os = "macos")] use tao::platform::macos::{EventLoopWindowTargetExtMacOS, WindowBuilderExtMacOS}; #[cfg(target_os = "linux")] use tao::platform::unix::{WindowBuilderExtUnix, WindowExtUnix}; #[cfg(windows)] use tao::platform::windows::{WindowBuilderExtWindows, WindowExtWindows}; #[cfg(windows)] use webview2_com::FocusChangedEventHandler; #[cfg(windows)] use windows::Win32::{Foundation::HWND, System::WinRT::EventRegistrationToken}; #[cfg(windows)] use wry::WebViewBuilderExtWindows; use tao::{ dpi::{ LogicalPosition as TaoLogicalPosition, LogicalSize as TaoLogicalSize, PhysicalPosition as TaoPhysicalPosition, PhysicalSize as TaoPhysicalSize, Position as TaoPosition, Size as TaoSize, }, event::{Event, StartCause, WindowEvent as TaoWindowEvent}, event_loop::{ ControlFlow, DeviceEventFilter as TaoDeviceEventFilter, EventLoop, EventLoopBuilder, EventLoopProxy as TaoEventLoopProxy, EventLoopWindowTarget, }, monitor::MonitorHandle, window::{ CursorIcon as TaoCursorIcon, Fullscreen, Icon as TaoWindowIcon, ProgressBarState as TaoProgressBarState, ProgressState as TaoProgressState, Theme as TaoTheme, UserAttentionType as TaoUserAttentionType, }, }; #[cfg(target_os = "macos")] use tauri_utils::TitleBarStyle; use tauri_utils::{config::WindowConfig, Theme}; use url::Url; use wry::{ DragDropEvent as WryDragDropEvent, ProxyConfig, ProxyEndpoint, WebContext, WebView, WebViewBuilder, }; pub use tao; pub use tao::window::{Window, WindowBuilder as TaoWindowBuilder, WindowId as TaoWindowId}; pub use wry; pub use wry::webview_version; #[cfg(windows)] use wry::WebViewExtWindows; #[cfg(target_os = "android")] use wry::{ prelude::{dispatch, find_class}, WebViewBuilderExtAndroid, WebViewExtAndroid, }; #[cfg(not(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" )))] use wry::{WebViewBuilderExtUnix, WebViewExtUnix}; #[cfg(target_os = "macos")] pub use tao::platform::macos::{ ActivationPolicy as TaoActivationPolicy, EventLoopExtMacOS, WindowExtMacOS, }; #[cfg(target_os = "macos")] use tauri_runtime::ActivationPolicy; use std::{ cell::RefCell, collections::{ hash_map::Entry::{Occupied, Vacant}, BTreeMap, HashMap, }, fmt, ops::Deref, path::PathBuf, rc::Rc, sync::{ atomic::{AtomicBool, AtomicU32, Ordering}, mpsc::{channel, Sender}, Arc, Mutex, Weak, }, thread::{current as current_thread, ThreadId}, }; pub type WebviewId = u32; type IpcHandler = dyn Fn(Request) + 'static; #[cfg(any( windows, target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] mod undecorated_resizing; mod webview; pub use webview::Webview; pub type WebContextStore = Arc, WebContext>>>; // window pub type WindowEventHandler = Box; pub type WindowEventListeners = Arc>>; pub type WebviewEventHandler = Box; pub type WebviewEventListeners = Arc>>; #[derive(Debug, Clone, Default)] pub struct WindowIdStore(Arc>>); impl WindowIdStore { pub fn insert(&self, w: TaoWindowId, id: WindowId) { self.0.lock().unwrap().insert(w, id); } fn get(&self, w: &TaoWindowId) -> Option { self.0.lock().unwrap().get(w).copied() } } #[macro_export] macro_rules! getter { ($self: ident, $rx: expr, $message: expr) => {{ $crate::send_user_message(&$self.context, $message)?; $rx .recv() .map_err(|_| $crate::Error::FailedToReceiveMessage) }}; } macro_rules! window_getter { ($self: ident, $message: expr) => {{ let (tx, rx) = channel(); getter!($self, rx, Message::Window($self.window_id, $message(tx))) }}; } macro_rules! webview_getter { ($self: ident, $message: expr) => {{ let (tx, rx) = channel(); getter!( $self, rx, Message::Webview( *$self.window_id.lock().unwrap(), $self.webview_id, $message(tx) ) ) }}; } pub(crate) fn send_user_message( context: &Context, message: Message, ) -> Result<()> { if current_thread().id() == context.main_thread_id { handle_user_message( &context.main_thread.window_target, message, UserMessageContext { window_id_map: context.window_id_map.clone(), windows: context.main_thread.windows.clone(), }, ); Ok(()) } else { context .proxy .send_event(message) .map_err(|_| Error::FailedToSendMessage) } } #[derive(Clone)] pub struct Context { pub window_id_map: WindowIdStore, main_thread_id: ThreadId, pub proxy: TaoEventLoopProxy>, main_thread: DispatcherMainThreadContext, plugins: Arc + Send>>>>, next_window_id: Arc, next_webview_id: Arc, next_window_event_id: Arc, next_webview_event_id: Arc, next_webcontext_id: Arc, } impl Context { pub fn run_threaded(&self, f: F) -> R where F: FnOnce(Option<&DispatcherMainThreadContext>) -> R, { f(if current_thread().id() == self.main_thread_id { Some(&self.main_thread) } else { None }) } fn next_window_id(&self) -> WindowId { self.next_window_id.fetch_add(1, Ordering::Relaxed).into() } fn next_webview_id(&self) -> WebviewId { self.next_webview_id.fetch_add(1, Ordering::Relaxed) } fn next_window_event_id(&self) -> u32 { self.next_window_event_id.fetch_add(1, Ordering::Relaxed) } fn next_webview_event_id(&self) -> u32 { self.next_webview_event_id.fetch_add(1, Ordering::Relaxed) } fn next_webcontext_id(&self) -> u32 { self.next_webcontext_id.fetch_add(1, Ordering::Relaxed) } } impl Context { fn create_window( &self, pending: PendingWindow>, after_window_creation: Option, ) -> Result>> { let label = pending.label.clone(); let context = self.clone(); let window_id = self.next_window_id(); let webview_id = pending.webview.as_ref().map(|_| context.next_webview_id()); send_user_message( self, Message::CreateWindow( window_id, Box::new(move |event_loop| { create_window( window_id, webview_id.unwrap_or_default(), event_loop, &context, pending, after_window_creation, ) }), ), )?; let dispatcher = WryWindowDispatcher { window_id, context: self.clone(), }; let detached_webview = webview_id.map(|id| DetachedWebview { label: label.clone(), dispatcher: WryWebviewDispatcher { window_id: Arc::new(Mutex::new(window_id)), webview_id: id, context: self.clone(), }, }); Ok(DetachedWindow { id: window_id, label, dispatcher, webview: detached_webview, }) } fn create_webview( &self, window_id: WindowId, pending: PendingWebview>, ) -> Result>> { let label = pending.label.clone(); let context = self.clone(); let webview_id = self.next_webview_id(); let window_id_wrapper = Arc::new(Mutex::new(window_id)); let window_id_wrapper_ = window_id_wrapper.clone(); send_user_message( self, Message::CreateWebview( window_id, Box::new(move |window| { create_webview( WebviewKind::WindowChild, window, window_id_wrapper_, webview_id, &context, pending, ) }), ), )?; let dispatcher = WryWebviewDispatcher { window_id: window_id_wrapper, webview_id, context: self.clone(), }; Ok(DetachedWebview { label, dispatcher }) } } #[cfg(feature = "tracing")] #[derive(Debug, Clone, Default)] pub struct ActiveTraceSpanStore(Rc>>); #[cfg(feature = "tracing")] impl ActiveTraceSpanStore { pub fn remove_window_draw(&self) { self .0 .borrow_mut() .retain(|t| !matches!(t, ActiveTracingSpan::WindowDraw { id: _, span: _ })); } } #[cfg(feature = "tracing")] #[derive(Debug)] pub enum ActiveTracingSpan { WindowDraw { id: TaoWindowId, span: tracing::span::EnteredSpan, }, } #[derive(Debug)] pub struct WindowsStore(RefCell>); // SAFETY: we ensure this type is only used on the main thread. #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for WindowsStore {} // SAFETY: we ensure this type is only used on the main thread. #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Sync for WindowsStore {} #[derive(Debug, Clone)] pub struct DispatcherMainThreadContext { pub window_target: EventLoopWindowTarget>, pub web_context: WebContextStore, // changing this to an Rc will cause frequent app crashes. pub windows: Arc, #[cfg(feature = "tracing")] pub active_tracing_spans: ActiveTraceSpanStore, } // SAFETY: we ensure this type is only used on the main thread. #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for DispatcherMainThreadContext {} // SAFETY: we ensure this type is only used on the main thread. #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Sync for DispatcherMainThreadContext {} impl fmt::Debug for Context { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Context") .field("main_thread_id", &self.main_thread_id) .field("proxy", &self.proxy) .field("main_thread", &self.main_thread) .finish() } } pub struct DeviceEventFilterWrapper(pub TaoDeviceEventFilter); impl From for DeviceEventFilterWrapper { fn from(item: DeviceEventFilter) -> Self { match item { DeviceEventFilter::Always => Self(TaoDeviceEventFilter::Always), DeviceEventFilter::Never => Self(TaoDeviceEventFilter::Never), DeviceEventFilter::Unfocused => Self(TaoDeviceEventFilter::Unfocused), } } } pub struct RectWrapper(pub wry::Rect); impl From for RectWrapper { fn from(value: tauri_runtime::Rect) -> Self { RectWrapper(wry::Rect { position: value.position, size: value.size, }) } } /// Wrapper around a [`tao::window::Icon`] that can be created from an [`Icon`]. pub struct TaoIcon(pub TaoWindowIcon); impl TryFrom> for TaoIcon { type Error = Error; fn try_from(icon: Icon<'_>) -> std::result::Result { TaoWindowIcon::from_rgba(icon.rgba.to_vec(), icon.width, icon.height) .map(Self) .map_err(|e| Error::InvalidIcon(Box::new(e))) } } pub struct WindowEventWrapper(pub Option); impl WindowEventWrapper { fn parse(window: &WindowWrapper, event: &TaoWindowEvent<'_>) -> Self { match event { // resized event from tao doesn't include a reliable size on macOS // because wry replaces the NSView TaoWindowEvent::Resized(_) => { if let Some(w) = &window.inner { let size = inner_size( w, &window.webviews, window.has_children.load(Ordering::Relaxed), ); Self(Some(WindowEvent::Resized(PhysicalSizeWrapper(size).into()))) } else { Self(None) } } e => e.into(), } } } pub fn map_theme(theme: &TaoTheme) -> Theme { match theme { TaoTheme::Light => Theme::Light, TaoTheme::Dark => Theme::Dark, _ => Theme::Light, } } #[cfg(target_os = "macos")] fn tao_activation_policy(activation_policy: ActivationPolicy) -> TaoActivationPolicy { match activation_policy { ActivationPolicy::Regular => TaoActivationPolicy::Regular, ActivationPolicy::Accessory => TaoActivationPolicy::Accessory, ActivationPolicy::Prohibited => TaoActivationPolicy::Prohibited, _ => unimplemented!(), } } impl<'a> From<&TaoWindowEvent<'a>> for WindowEventWrapper { fn from(event: &TaoWindowEvent<'a>) -> Self { let event = match event { TaoWindowEvent::Resized(size) => WindowEvent::Resized(PhysicalSizeWrapper(*size).into()), TaoWindowEvent::Moved(position) => { WindowEvent::Moved(PhysicalPositionWrapper(*position).into()) } TaoWindowEvent::Destroyed => WindowEvent::Destroyed, TaoWindowEvent::ScaleFactorChanged { scale_factor, new_inner_size, } => WindowEvent::ScaleFactorChanged { scale_factor: *scale_factor, new_inner_size: PhysicalSizeWrapper(**new_inner_size).into(), }, #[cfg(any(target_os = "linux", target_os = "macos"))] TaoWindowEvent::Focused(focused) => WindowEvent::Focused(*focused), TaoWindowEvent::ThemeChanged(theme) => WindowEvent::ThemeChanged(map_theme(theme)), _ => return Self(None), }; Self(Some(event)) } } pub struct MonitorHandleWrapper(pub MonitorHandle); impl From for Monitor { fn from(monitor: MonitorHandleWrapper) -> Monitor { Self { name: monitor.0.name(), position: PhysicalPositionWrapper(monitor.0.position()).into(), size: PhysicalSizeWrapper(monitor.0.size()).into(), scale_factor: monitor.0.scale_factor(), } } } pub struct PhysicalPositionWrapper(pub TaoPhysicalPosition); impl From> for PhysicalPosition { fn from(position: PhysicalPositionWrapper) -> Self { Self { x: position.0.x, y: position.0.y, } } } impl From> for PhysicalPositionWrapper { fn from(position: PhysicalPosition) -> Self { Self(TaoPhysicalPosition { x: position.x, y: position.y, }) } } struct LogicalPositionWrapper(TaoLogicalPosition); impl From> for LogicalPositionWrapper { fn from(position: LogicalPosition) -> Self { Self(TaoLogicalPosition { x: position.x, y: position.y, }) } } pub struct PhysicalSizeWrapper(pub TaoPhysicalSize); impl From> for PhysicalSize { fn from(size: PhysicalSizeWrapper) -> Self { Self { width: size.0.width, height: size.0.height, } } } impl From> for PhysicalSizeWrapper { fn from(size: PhysicalSize) -> Self { Self(TaoPhysicalSize { width: size.width, height: size.height, }) } } struct LogicalSizeWrapper(TaoLogicalSize); impl From> for LogicalSizeWrapper { fn from(size: LogicalSize) -> Self { Self(TaoLogicalSize { width: size.width, height: size.height, }) } } pub struct SizeWrapper(pub TaoSize); impl From for SizeWrapper { fn from(size: Size) -> Self { match size { Size::Logical(s) => Self(TaoSize::Logical(LogicalSizeWrapper::from(s).0)), Size::Physical(s) => Self(TaoSize::Physical(PhysicalSizeWrapper::from(s).0)), } } } pub struct PositionWrapper(pub TaoPosition); impl From for PositionWrapper { fn from(position: Position) -> Self { match position { Position::Logical(s) => Self(TaoPosition::Logical(LogicalPositionWrapper::from(s).0)), Position::Physical(s) => Self(TaoPosition::Physical(PhysicalPositionWrapper::from(s).0)), } } } #[derive(Debug, Clone)] pub struct UserAttentionTypeWrapper(pub TaoUserAttentionType); impl From for UserAttentionTypeWrapper { fn from(request_type: UserAttentionType) -> Self { let o = match request_type { UserAttentionType::Critical => TaoUserAttentionType::Critical, UserAttentionType::Informational => TaoUserAttentionType::Informational, }; Self(o) } } #[derive(Debug)] pub struct CursorIconWrapper(pub TaoCursorIcon); impl From for CursorIconWrapper { fn from(icon: CursorIcon) -> Self { use CursorIcon::*; let i = match icon { Default => TaoCursorIcon::Default, Crosshair => TaoCursorIcon::Crosshair, Hand => TaoCursorIcon::Hand, Arrow => TaoCursorIcon::Arrow, Move => TaoCursorIcon::Move, Text => TaoCursorIcon::Text, Wait => TaoCursorIcon::Wait, Help => TaoCursorIcon::Help, Progress => TaoCursorIcon::Progress, NotAllowed => TaoCursorIcon::NotAllowed, ContextMenu => TaoCursorIcon::ContextMenu, Cell => TaoCursorIcon::Cell, VerticalText => TaoCursorIcon::VerticalText, Alias => TaoCursorIcon::Alias, Copy => TaoCursorIcon::Copy, NoDrop => TaoCursorIcon::NoDrop, Grab => TaoCursorIcon::Grab, Grabbing => TaoCursorIcon::Grabbing, AllScroll => TaoCursorIcon::AllScroll, ZoomIn => TaoCursorIcon::ZoomIn, ZoomOut => TaoCursorIcon::ZoomOut, EResize => TaoCursorIcon::EResize, NResize => TaoCursorIcon::NResize, NeResize => TaoCursorIcon::NeResize, NwResize => TaoCursorIcon::NwResize, SResize => TaoCursorIcon::SResize, SeResize => TaoCursorIcon::SeResize, SwResize => TaoCursorIcon::SwResize, WResize => TaoCursorIcon::WResize, EwResize => TaoCursorIcon::EwResize, NsResize => TaoCursorIcon::NsResize, NeswResize => TaoCursorIcon::NeswResize, NwseResize => TaoCursorIcon::NwseResize, ColResize => TaoCursorIcon::ColResize, RowResize => TaoCursorIcon::RowResize, _ => TaoCursorIcon::Default, }; Self(i) } } pub struct ProgressStateWrapper(pub TaoProgressState); impl From for ProgressStateWrapper { fn from(status: ProgressBarStatus) -> Self { let state = match status { ProgressBarStatus::None => TaoProgressState::None, ProgressBarStatus::Normal => TaoProgressState::Normal, ProgressBarStatus::Indeterminate => TaoProgressState::Indeterminate, ProgressBarStatus::Paused => TaoProgressState::Paused, ProgressBarStatus::Error => TaoProgressState::Error, }; Self(state) } } pub struct ProgressBarStateWrapper(pub TaoProgressBarState); impl From for ProgressBarStateWrapper { fn from(progress_state: ProgressBarState) -> Self { Self(TaoProgressBarState { progress: progress_state.progress, state: progress_state .status .map(|state| ProgressStateWrapper::from(state).0), desktop_filename: progress_state.desktop_filename, }) } } #[derive(Clone, Default)] pub struct WindowBuilderWrapper { inner: TaoWindowBuilder, center: bool, #[cfg(target_os = "macos")] tabbing_identifier: Option, } impl std::fmt::Debug for WindowBuilderWrapper { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = f.debug_struct("WindowBuilderWrapper"); s.field("inner", &self.inner).field("center", &self.center); #[cfg(target_os = "macos")] { s.field("tabbing_identifier", &self.tabbing_identifier); } s.finish() } } // SAFETY: this type is `Send` since `menu_items` are read only here #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for WindowBuilderWrapper {} impl WindowBuilderBase for WindowBuilderWrapper {} impl WindowBuilder for WindowBuilderWrapper { fn new() -> Self { #[allow(unused_mut)] let mut builder = Self::default().focused(true); #[cfg(target_os = "macos")] { // TODO: find a proper way to prevent webview being pushed out of the window. // Workround for issue: https://github.com/tauri-apps/tauri/issues/10225 // The window requies `NSFullSizeContentViewWindowMask` flag to prevent devtools // pushing the content view out of the window. // By setting the default style to `TitleBarStyle::Visible` should fix the issue for most of the users. builder = builder.title_bar_style(TitleBarStyle::Visible); } builder } fn with_config(config: &WindowConfig) -> Self { let mut window = WindowBuilderWrapper::new(); #[cfg(target_os = "macos")] { window = window .hidden_title(config.hidden_title) .title_bar_style(config.title_bar_style); if let Some(identifier) = &config.tabbing_identifier { window = window.tabbing_identifier(identifier); } } #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] { window = window.transparent(config.transparent); } #[cfg(all( target_os = "macos", not(feature = "macos-private-api"), debug_assertions ))] if config.transparent { eprintln!( "The window is set to be transparent but the `macos-private-api` is not enabled. This can be enabled via the `tauri.macOSPrivateApi` configuration property "); } #[cfg(target_os = "linux")] { // Mouse event is disabled on Linux since sudden event bursts could block event loop. window.inner = window.inner.with_cursor_moved_event(false); } #[cfg(desktop)] { window = window .title(config.title.to_string()) .inner_size(config.width, config.height) .visible(config.visible) .resizable(config.resizable) .fullscreen(config.fullscreen) .decorations(config.decorations) .maximized(config.maximized) .always_on_bottom(config.always_on_bottom) .always_on_top(config.always_on_top) .visible_on_all_workspaces(config.visible_on_all_workspaces) .content_protected(config.content_protected) .skip_taskbar(config.skip_taskbar) .theme(config.theme) .closable(config.closable) .maximizable(config.maximizable) .minimizable(config.minimizable) .shadow(config.shadow); let mut constraints = WindowSizeConstraints::default(); if let Some(min_width) = config.min_width { constraints.min_width = Some(tao::dpi::LogicalUnit::new(min_width).into()); } if let Some(min_height) = config.min_height { constraints.min_height = Some(tao::dpi::LogicalUnit::new(min_height).into()); } if let Some(max_width) = config.max_width { constraints.max_width = Some(tao::dpi::LogicalUnit::new(max_width).into()); } if let Some(max_height) = config.max_height { constraints.max_height = Some(tao::dpi::LogicalUnit::new(max_height).into()); } window = window.inner_size_constraints(constraints); if let (Some(x), Some(y)) = (config.x, config.y) { window = window.position(x, y); } if config.center { window = window.center(); } } window } fn center(mut self) -> Self { self.center = true; self } fn position(mut self, x: f64, y: f64) -> Self { self.inner = self.inner.with_position(TaoLogicalPosition::new(x, y)); self } fn inner_size(mut self, width: f64, height: f64) -> Self { self.inner = self .inner .with_inner_size(TaoLogicalSize::new(width, height)); self } fn min_inner_size(mut self, min_width: f64, min_height: f64) -> Self { self.inner = self .inner .with_min_inner_size(TaoLogicalSize::new(min_width, min_height)); self } fn max_inner_size(mut self, max_width: f64, max_height: f64) -> Self { self.inner = self .inner .with_max_inner_size(TaoLogicalSize::new(max_width, max_height)); self } fn inner_size_constraints(mut self, constraints: WindowSizeConstraints) -> Self { self.inner.window.inner_size_constraints = tao::window::WindowSizeConstraints { min_width: constraints.min_width, min_height: constraints.min_height, max_width: constraints.max_width, max_height: constraints.max_height, }; self } fn resizable(mut self, resizable: bool) -> Self { self.inner = self.inner.with_resizable(resizable); self } fn maximizable(mut self, maximizable: bool) -> Self { self.inner = self.inner.with_maximizable(maximizable); self } fn minimizable(mut self, minimizable: bool) -> Self { self.inner = self.inner.with_minimizable(minimizable); self } fn closable(mut self, closable: bool) -> Self { self.inner = self.inner.with_closable(closable); self } fn title>(mut self, title: S) -> Self { self.inner = self.inner.with_title(title.into()); self } fn fullscreen(mut self, fullscreen: bool) -> Self { self.inner = if fullscreen { self .inner .with_fullscreen(Some(Fullscreen::Borderless(None))) } else { self.inner.with_fullscreen(None) }; self } fn focused(mut self, focused: bool) -> Self { self.inner = self.inner.with_focused(focused); self } fn maximized(mut self, maximized: bool) -> Self { self.inner = self.inner.with_maximized(maximized); self } fn visible(mut self, visible: bool) -> Self { self.inner = self.inner.with_visible(visible); self } #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] fn transparent(mut self, transparent: bool) -> Self { self.inner = self.inner.with_transparent(transparent); self } fn decorations(mut self, decorations: bool) -> Self { self.inner = self.inner.with_decorations(decorations); self } fn always_on_bottom(mut self, always_on_bottom: bool) -> Self { self.inner = self.inner.with_always_on_bottom(always_on_bottom); self } fn always_on_top(mut self, always_on_top: bool) -> Self { self.inner = self.inner.with_always_on_top(always_on_top); self } fn visible_on_all_workspaces(mut self, visible_on_all_workspaces: bool) -> Self { self.inner = self .inner .with_visible_on_all_workspaces(visible_on_all_workspaces); self } fn content_protected(mut self, protected: bool) -> Self { self.inner = self.inner.with_content_protection(protected); self } fn shadow(#[allow(unused_mut)] mut self, _enable: bool) -> Self { #[cfg(windows)] { self.inner = self.inner.with_undecorated_shadow(_enable); } #[cfg(target_os = "macos")] { self.inner = self.inner.with_has_shadow(_enable); } self } #[cfg(windows)] fn owner(mut self, owner: HWND) -> Self { self.inner = self.inner.with_owner_window(owner.0 as _); self } #[cfg(windows)] fn parent(mut self, parent: HWND) -> Self { self.inner = self.inner.with_parent_window(parent.0 as _); self } #[cfg(target_os = "macos")] fn parent(mut self, parent: *mut std::ffi::c_void) -> Self { self.inner = self.inner.with_parent_window(parent); self } #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] fn transient_for(mut self, parent: &impl gtk::glib::IsA) -> Self { self.inner = self.inner.with_transient_for(parent); self } #[cfg(windows)] fn drag_and_drop(mut self, enabled: bool) -> Self { self.inner = self.inner.with_drag_and_drop(enabled); self } #[cfg(target_os = "macos")] fn title_bar_style(mut self, style: TitleBarStyle) -> Self { match style { TitleBarStyle::Visible => { self.inner = self.inner.with_titlebar_transparent(false); // Fixes rendering issue when resizing window with devtools open (https://github.com/tauri-apps/tauri/issues/3914) self.inner = self.inner.with_fullsize_content_view(true); } TitleBarStyle::Transparent => { self.inner = self.inner.with_titlebar_transparent(true); self.inner = self.inner.with_fullsize_content_view(false); } TitleBarStyle::Overlay => { self.inner = self.inner.with_titlebar_transparent(true); self.inner = self.inner.with_fullsize_content_view(true); } unknown => { #[cfg(feature = "tracing")] tracing::warn!("unknown title bar style applied: {unknown}"); #[cfg(not(feature = "tracing"))] eprintln!("unknown title bar style applied: {unknown}"); } } self } #[cfg(target_os = "macos")] fn hidden_title(mut self, hidden: bool) -> Self { self.inner = self.inner.with_title_hidden(hidden); self } #[cfg(target_os = "macos")] fn tabbing_identifier(mut self, identifier: &str) -> Self { self.inner = self.inner.with_tabbing_identifier(identifier); self.tabbing_identifier.replace(identifier.into()); self } fn icon(mut self, icon: Icon) -> Result { self.inner = self .inner .with_window_icon(Some(TaoIcon::try_from(icon)?.0)); Ok(self) } #[cfg(any(windows, target_os = "linux"))] fn skip_taskbar(mut self, skip: bool) -> Self { self.inner = self.inner.with_skip_taskbar(skip); self } #[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))] fn skip_taskbar(self, _skip: bool) -> Self { self } #[allow(unused_variables, unused_mut)] fn theme(mut self, theme: Option) -> Self { self.inner = self.inner.with_theme(if let Some(t) = theme { match t { Theme::Dark => Some(TaoTheme::Dark), _ => Some(TaoTheme::Light), } } else { None }); self } fn has_icon(&self) -> bool { self.inner.window.window_icon.is_some() } fn get_theme(&self) -> Option { self.inner.window.preferred_theme.map(|theme| match theme { TaoTheme::Dark => Theme::Dark, _ => Theme::Light, }) } } #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] pub struct GtkWindow(pub gtk::ApplicationWindow); #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for GtkWindow {} #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] pub struct GtkBox(pub gtk::Box); #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for GtkBox {} pub struct SendRawWindowHandle(pub raw_window_handle::RawWindowHandle); unsafe impl Send for SendRawWindowHandle {} #[cfg(target_os = "macos")] #[derive(Debug, Clone)] pub enum ApplicationMessage { Show, Hide, } pub enum WindowMessage { AddEventListener(WindowEventId, Box), // Getters ScaleFactor(Sender), InnerPosition(Sender>>), OuterPosition(Sender>>), InnerSize(Sender>), OuterSize(Sender>), IsFullscreen(Sender), IsMinimized(Sender), IsMaximized(Sender), IsFocused(Sender), IsDecorated(Sender), IsResizable(Sender), IsMaximizable(Sender), IsMinimizable(Sender), IsClosable(Sender), IsVisible(Sender), Title(Sender), CurrentMonitor(Sender>), PrimaryMonitor(Sender>), MonitorFromPoint(Sender>, (f64, f64)), AvailableMonitors(Sender>), #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] GtkWindow(Sender), #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] GtkBox(Sender), RawWindowHandle(Sender>), Theme(Sender), // Setters Center, RequestUserAttention(Option), SetResizable(bool), SetMaximizable(bool), SetMinimizable(bool), SetClosable(bool), SetTitle(String), Maximize, Unmaximize, Minimize, Unminimize, Show, Hide, Close, Destroy, SetDecorations(bool), SetShadow(bool), SetAlwaysOnBottom(bool), SetAlwaysOnTop(bool), SetVisibleOnAllWorkspaces(bool), SetContentProtected(bool), SetSize(Size), SetMinSize(Option), SetMaxSize(Option), SetSizeConstraints(WindowSizeConstraints), SetPosition(Position), SetFullscreen(bool), SetFocus, SetIcon(TaoWindowIcon), SetSkipTaskbar(bool), SetCursorGrab(bool), SetCursorVisible(bool), SetCursorIcon(CursorIcon), SetCursorPosition(Position), SetIgnoreCursorEvents(bool), SetProgressBar(ProgressBarState), SetTitleBarStyle(tauri_utils::TitleBarStyle), DragWindow, ResizeDragWindow(tauri_runtime::ResizeDirection), RequestRedraw, } #[derive(Debug, Clone)] pub enum SynthesizedWindowEvent { Focused(bool), DragDrop(DragDropEvent), } impl From for WindowEventWrapper { fn from(event: SynthesizedWindowEvent) -> Self { let event = match event { SynthesizedWindowEvent::Focused(focused) => WindowEvent::Focused(focused), SynthesizedWindowEvent::DragDrop(event) => WindowEvent::DragDrop(event), }; Self(Some(event)) } } pub enum WebviewMessage { AddEventListener(WebviewEventId, Box), #[cfg(not(all(feature = "tracing", not(target_os = "android"))))] EvaluateScript(String), #[cfg(all(feature = "tracing", not(target_os = "android")))] EvaluateScript(String, Sender<()>, tracing::Span), WebviewEvent(WebviewEvent), SynthesizedWindowEvent(SynthesizedWindowEvent), Navigate(Url), Print, Close, SetPosition(Position), SetSize(Size), SetBounds(tauri_runtime::Rect), SetFocus, Reparent(WindowId, Sender>), SetAutoResize(bool), SetZoom(f64), // Getters Url(Sender>), Bounds(Sender>), Position(Sender>>), Size(Sender>>), WithWebview(Box), // Devtools #[cfg(any(debug_assertions, feature = "devtools"))] OpenDevTools, #[cfg(any(debug_assertions, feature = "devtools"))] CloseDevTools, #[cfg(any(debug_assertions, feature = "devtools"))] IsDevToolsOpen(Sender), } pub type CreateWindowClosure = Box>) -> Result + Send>; pub type CreateWebviewClosure = Box Result + Send>; pub enum Message { Task(Box), #[cfg(target_os = "macos")] SetActivationPolicy(ActivationPolicy), RequestExit(i32), #[cfg(target_os = "macos")] Application(ApplicationMessage), Window(WindowId, WindowMessage), Webview(WindowId, WebviewId, WebviewMessage), CreateWebview(WindowId, CreateWebviewClosure), CreateWindow(WindowId, CreateWindowClosure), CreateRawWindow( WindowId, Box (String, TaoWindowBuilder) + Send>, Sender>>, ), UserEvent(T), } impl Clone for Message { fn clone(&self) -> Self { match self { Self::UserEvent(t) => Self::UserEvent(t.clone()), _ => unimplemented!(), } } } /// The Tauri [`WebviewDispatch`] for [`Wry`]. #[derive(Debug, Clone)] pub struct WryWebviewDispatcher { window_id: Arc>, webview_id: WebviewId, context: Context, } impl WebviewDispatch for WryWebviewDispatcher { type Runtime = Wry; fn run_on_main_thread(&self, f: F) -> Result<()> { send_user_message(&self.context, Message::Task(Box::new(f))) } fn on_webview_event(&self, f: F) -> WindowEventId { let id = self.context.next_webview_event_id(); let _ = self.context.proxy.send_event(Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::AddEventListener(id, Box::new(f)), )); id } fn with_webview) + Send + 'static>(&self, f: F) -> Result<()> { send_user_message( &self.context, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::WithWebview(Box::new(move |webview| f(Box::new(webview)))), ), ) } #[cfg(any(debug_assertions, feature = "devtools"))] fn open_devtools(&self) { let _ = send_user_message( &self.context, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::OpenDevTools, ), ); } #[cfg(any(debug_assertions, feature = "devtools"))] fn close_devtools(&self) { let _ = send_user_message( &self.context, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::CloseDevTools, ), ); } /// Gets the devtools window's current open state. #[cfg(any(debug_assertions, feature = "devtools"))] fn is_devtools_open(&self) -> Result { webview_getter!(self, WebviewMessage::IsDevToolsOpen) } // Getters fn url(&self) -> Result { webview_getter!(self, WebviewMessage::Url)? } fn bounds(&self) -> Result { webview_getter!(self, WebviewMessage::Bounds)? } fn position(&self) -> Result> { webview_getter!(self, WebviewMessage::Position)? } fn size(&self) -> Result> { webview_getter!(self, WebviewMessage::Size)? } // Setters fn navigate(&self, url: Url) -> Result<()> { send_user_message( &self.context, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::Navigate(url), ), ) } fn print(&self) -> Result<()> { send_user_message( &self.context, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::Print, ), ) } fn close(&self) -> Result<()> { send_user_message( &self.context, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::Close, ), ) } fn set_bounds(&self, bounds: tauri_runtime::Rect) -> Result<()> { send_user_message( &self.context, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::SetBounds(bounds), ), ) } fn set_size(&self, size: Size) -> Result<()> { send_user_message( &self.context, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::SetSize(size), ), ) } fn set_position(&self, position: Position) -> Result<()> { send_user_message( &self.context, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::SetPosition(position), ), ) } fn set_focus(&self) -> Result<()> { send_user_message( &self.context, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::SetFocus, ), ) } fn reparent(&self, window_id: WindowId) -> Result<()> { let mut current_window_id = self.window_id.lock().unwrap(); let (tx, rx) = channel(); send_user_message( &self.context, Message::Webview( *current_window_id, self.webview_id, WebviewMessage::Reparent(window_id, tx), ), )?; rx.recv().unwrap()?; *current_window_id = window_id; Ok(()) } fn set_auto_resize(&self, auto_resize: bool) -> Result<()> { send_user_message( &self.context, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::SetAutoResize(auto_resize), ), ) } #[cfg(all(feature = "tracing", not(target_os = "android")))] fn eval_script>(&self, script: S) -> Result<()> { // use a channel so the EvaluateScript task uses the current span as parent let (tx, rx) = channel(); getter!( self, rx, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::EvaluateScript(script.into(), tx, tracing::Span::current()), ) ) } #[cfg(not(all(feature = "tracing", not(target_os = "android"))))] fn eval_script>(&self, script: S) -> Result<()> { send_user_message( &self.context, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::EvaluateScript(script.into()), ), ) } fn set_zoom(&self, scale_factor: f64) -> Result<()> { send_user_message( &self.context, Message::Webview( *self.window_id.lock().unwrap(), self.webview_id, WebviewMessage::SetZoom(scale_factor), ), ) } } /// The Tauri [`WindowDispatch`] for [`Wry`]. #[derive(Debug, Clone)] pub struct WryWindowDispatcher { window_id: WindowId, context: Context, } // SAFETY: this is safe since the `Context` usage is guarded on `send_user_message`. #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Sync for WryWindowDispatcher {} fn get_raw_window_handle( dispatcher: &WryWindowDispatcher, ) -> Result> { window_getter!(dispatcher, WindowMessage::RawWindowHandle) } impl WindowDispatch for WryWindowDispatcher { type Runtime = Wry; type WindowBuilder = WindowBuilderWrapper; fn run_on_main_thread(&self, f: F) -> Result<()> { send_user_message(&self.context, Message::Task(Box::new(f))) } fn on_window_event(&self, f: F) -> WindowEventId { let id = self.context.next_window_event_id(); let _ = self.context.proxy.send_event(Message::Window( self.window_id, WindowMessage::AddEventListener(id, Box::new(f)), )); id } // Getters fn scale_factor(&self) -> Result { window_getter!(self, WindowMessage::ScaleFactor) } fn inner_position(&self) -> Result> { window_getter!(self, WindowMessage::InnerPosition)? } fn outer_position(&self) -> Result> { window_getter!(self, WindowMessage::OuterPosition)? } fn inner_size(&self) -> Result> { window_getter!(self, WindowMessage::InnerSize) } fn outer_size(&self) -> Result> { window_getter!(self, WindowMessage::OuterSize) } fn is_fullscreen(&self) -> Result { window_getter!(self, WindowMessage::IsFullscreen) } fn is_minimized(&self) -> Result { window_getter!(self, WindowMessage::IsMinimized) } fn is_maximized(&self) -> Result { window_getter!(self, WindowMessage::IsMaximized) } fn is_focused(&self) -> Result { window_getter!(self, WindowMessage::IsFocused) } /// Gets the window's current decoration state. fn is_decorated(&self) -> Result { window_getter!(self, WindowMessage::IsDecorated) } /// Gets the window's current resizable state. fn is_resizable(&self) -> Result { window_getter!(self, WindowMessage::IsResizable) } /// Gets the current native window's maximize button state fn is_maximizable(&self) -> Result { window_getter!(self, WindowMessage::IsMaximizable) } /// Gets the current native window's minimize button state fn is_minimizable(&self) -> Result { window_getter!(self, WindowMessage::IsMinimizable) } /// Gets the current native window's close button state fn is_closable(&self) -> Result { window_getter!(self, WindowMessage::IsClosable) } fn is_visible(&self) -> Result { window_getter!(self, WindowMessage::IsVisible) } fn title(&self) -> Result { window_getter!(self, WindowMessage::Title) } fn current_monitor(&self) -> Result> { Ok(window_getter!(self, WindowMessage::CurrentMonitor)?.map(|m| MonitorHandleWrapper(m).into())) } fn primary_monitor(&self) -> Result> { Ok(window_getter!(self, WindowMessage::PrimaryMonitor)?.map(|m| MonitorHandleWrapper(m).into())) } fn monitor_from_point(&self, x: f64, y: f64) -> Result> { let (tx, rx) = channel(); let _ = send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::MonitorFromPoint(tx, (x, y))), ); Ok( rx.recv() .map_err(|_| crate::Error::FailedToReceiveMessage)? .map(|m| MonitorHandleWrapper(m).into()), ) } fn available_monitors(&self) -> Result> { Ok( window_getter!(self, WindowMessage::AvailableMonitors)? .into_iter() .map(|m| MonitorHandleWrapper(m).into()) .collect(), ) } fn theme(&self) -> Result { window_getter!(self, WindowMessage::Theme) } #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] fn gtk_window(&self) -> Result { window_getter!(self, WindowMessage::GtkWindow).map(|w| w.0) } #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] fn default_vbox(&self) -> Result { window_getter!(self, WindowMessage::GtkBox).map(|w| w.0) } fn window_handle( &self, ) -> std::result::Result, raw_window_handle::HandleError> { get_raw_window_handle(self) .map_err(|_| raw_window_handle::HandleError::Unavailable) .and_then(|r| r.map(|h| unsafe { raw_window_handle::WindowHandle::borrow_raw(h.0) })) } // Setters fn center(&self) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::Center), ) } fn request_user_attention(&self, request_type: Option) -> Result<()> { send_user_message( &self.context, Message::Window( self.window_id, WindowMessage::RequestUserAttention(request_type.map(Into::into)), ), ) } // Creates a window by dispatching a message to the event loop. // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock. fn create_window( &mut self, pending: PendingWindow, after_window_creation: Option, ) -> Result> { self.context.create_window(pending, after_window_creation) } // Creates a webview by dispatching a message to the event loop. // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock. fn create_webview( &mut self, pending: PendingWebview, ) -> Result> { self.context.create_webview(self.window_id, pending) } fn set_resizable(&self, resizable: bool) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetResizable(resizable)), ) } fn set_maximizable(&self, maximizable: bool) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetMaximizable(maximizable)), ) } fn set_minimizable(&self, minimizable: bool) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetMinimizable(minimizable)), ) } fn set_closable(&self, closable: bool) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetClosable(closable)), ) } fn set_title>(&self, title: S) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetTitle(title.into())), ) } fn maximize(&self) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::Maximize), ) } fn unmaximize(&self) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::Unmaximize), ) } fn minimize(&self) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::Minimize), ) } fn unminimize(&self) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::Unminimize), ) } fn show(&self) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::Show), ) } fn hide(&self) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::Hide), ) } fn close(&self) -> Result<()> { // NOTE: close cannot use the `send_user_message` function because it accesses the event loop callback self .context .proxy .send_event(Message::Window(self.window_id, WindowMessage::Close)) .map_err(|_| Error::FailedToSendMessage) } fn destroy(&self) -> Result<()> { // NOTE: destroy cannot use the `send_user_message` function because it accesses the event loop callback self .context .proxy .send_event(Message::Window(self.window_id, WindowMessage::Destroy)) .map_err(|_| Error::FailedToSendMessage) } fn set_decorations(&self, decorations: bool) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetDecorations(decorations)), ) } fn set_shadow(&self, enable: bool) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetShadow(enable)), ) } fn set_always_on_bottom(&self, always_on_bottom: bool) -> Result<()> { send_user_message( &self.context, Message::Window( self.window_id, WindowMessage::SetAlwaysOnBottom(always_on_bottom), ), ) } fn set_always_on_top(&self, always_on_top: bool) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetAlwaysOnTop(always_on_top)), ) } fn set_visible_on_all_workspaces(&self, visible_on_all_workspaces: bool) -> Result<()> { send_user_message( &self.context, Message::Window( self.window_id, WindowMessage::SetVisibleOnAllWorkspaces(visible_on_all_workspaces), ), ) } fn set_content_protected(&self, protected: bool) -> Result<()> { send_user_message( &self.context, Message::Window( self.window_id, WindowMessage::SetContentProtected(protected), ), ) } fn set_size(&self, size: Size) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetSize(size)), ) } fn set_min_size(&self, size: Option) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetMinSize(size)), ) } fn set_max_size(&self, size: Option) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetMaxSize(size)), ) } fn set_size_constraints(&self, constraints: WindowSizeConstraints) -> Result<()> { send_user_message( &self.context, Message::Window( self.window_id, WindowMessage::SetSizeConstraints(constraints), ), ) } fn set_position(&self, position: Position) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetPosition(position)), ) } fn set_fullscreen(&self, fullscreen: bool) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetFullscreen(fullscreen)), ) } fn set_focus(&self) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetFocus), ) } fn set_icon(&self, icon: Icon) -> Result<()> { send_user_message( &self.context, Message::Window( self.window_id, WindowMessage::SetIcon(TaoIcon::try_from(icon)?.0), ), ) } fn set_skip_taskbar(&self, skip: bool) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetSkipTaskbar(skip)), ) } fn set_cursor_grab(&self, grab: bool) -> crate::Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetCursorGrab(grab)), ) } fn set_cursor_visible(&self, visible: bool) -> crate::Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetCursorVisible(visible)), ) } fn set_cursor_icon(&self, icon: CursorIcon) -> crate::Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetCursorIcon(icon)), ) } fn set_cursor_position>(&self, position: Pos) -> crate::Result<()> { send_user_message( &self.context, Message::Window( self.window_id, WindowMessage::SetCursorPosition(position.into()), ), ) } fn set_ignore_cursor_events(&self, ignore: bool) -> crate::Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetIgnoreCursorEvents(ignore)), ) } fn start_dragging(&self) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::DragWindow), ) } fn start_resize_dragging(&self, direction: tauri_runtime::ResizeDirection) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::ResizeDragWindow(direction)), ) } fn set_progress_bar(&self, progress_state: ProgressBarState) -> Result<()> { send_user_message( &self.context, Message::Window( self.window_id, WindowMessage::SetProgressBar(progress_state), ), ) } fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> Result<()> { send_user_message( &self.context, Message::Window(self.window_id, WindowMessage::SetTitleBarStyle(style)), ) } } #[derive(Clone)] pub struct WebviewWrapper { label: String, id: WebviewId, inner: Rc, context_store: WebContextStore, webview_event_listeners: WebviewEventListeners, // the key of the WebContext if it's not shared context_key: Option, bounds: Arc>>, } impl Deref for WebviewWrapper { type Target = WebView; #[inline(always)] fn deref(&self) -> &Self::Target { &self.inner } } impl Drop for WebviewWrapper { fn drop(&mut self) { if Rc::get_mut(&mut self.inner).is_some() { self.context_store.lock().unwrap().remove(&self.context_key); } } } pub struct WindowWrapper { label: String, inner: Option>, // whether this window has child webviews // or it's just a container for a single webview has_children: AtomicBool, webviews: Vec, window_event_listeners: WindowEventListeners, #[cfg(windows)] is_window_transparent: bool, #[cfg(windows)] surface: Option, Arc>>, } impl fmt::Debug for WindowWrapper { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WindowWrapper") .field("label", &self.label) .field("inner", &self.inner) .finish() } } #[derive(Debug, Clone)] pub struct EventProxy(TaoEventLoopProxy>); #[cfg(target_os = "ios")] #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Sync for EventProxy {} impl EventLoopProxy for EventProxy { fn send_event(&self, event: T) -> Result<()> { self .0 .send_event(Message::UserEvent(event)) .map_err(|_| Error::EventLoopClosed) } } pub trait PluginBuilder { type Plugin: Plugin; fn build(self, context: Context) -> Self::Plugin; } pub trait Plugin { fn on_event( &mut self, event: &Event>, event_loop: &EventLoopWindowTarget>, proxy: &TaoEventLoopProxy>, control_flow: &mut ControlFlow, context: EventLoopIterationContext<'_, T>, web_context: &WebContextStore, ) -> bool; } /// A Tauri [`Runtime`] wrapper around wry. pub struct Wry { context: Context, event_loop: EventLoop>, } impl fmt::Debug for Wry { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Wry") .field("main_thread_id", &self.context.main_thread_id) .field("event_loop", &self.event_loop) .field("windows", &self.context.main_thread.windows) .field("web_context", &self.context.main_thread.web_context) .finish() } } /// A handle to the Wry runtime. #[derive(Debug, Clone)] pub struct WryHandle { context: Context, } // SAFETY: this is safe since the `Context` usage is guarded on `send_user_message`. #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Sync for WryHandle {} impl WryHandle { /// Creates a new tao window using a callback, and returns its window id. pub fn create_tao_window (String, TaoWindowBuilder) + Send + 'static>( &self, f: F, ) -> Result> { let id = self.context.next_window_id(); let (tx, rx) = channel(); send_user_message(&self.context, Message::CreateRawWindow(id, Box::new(f), tx))?; rx.recv().unwrap() } /// Gets the [`WebviewId'] associated with the given [`WindowId`]. pub fn window_id(&self, window_id: TaoWindowId) -> WindowId { *self .context .window_id_map .0 .lock() .unwrap() .get(&window_id) .unwrap() } /// Send a message to the event loop. pub fn send_event(&self, message: Message) -> Result<()> { self .context .proxy .send_event(message) .map_err(|_| Error::FailedToSendMessage)?; Ok(()) } pub fn plugin + 'static>(&mut self, plugin: P) where

>::Plugin: Send, { self .context .plugins .lock() .unwrap() .push(Box::new(plugin.build(self.context.clone()))); } } impl RuntimeHandle for WryHandle { type Runtime = Wry; fn create_proxy(&self) -> EventProxy { EventProxy(self.context.proxy.clone()) } #[cfg(target_os = "macos")] fn set_activation_policy(&self, activation_policy: ActivationPolicy) -> Result<()> { send_user_message( &self.context, Message::SetActivationPolicy(activation_policy), ) } fn request_exit(&self, code: i32) -> Result<()> { // NOTE: request_exit cannot use the `send_user_message` function because it accesses the event loop callback self .context .proxy .send_event(Message::RequestExit(code)) .map_err(|_| Error::FailedToSendMessage) } // Creates a window by dispatching a message to the event loop. // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock. fn create_window( &self, pending: PendingWindow, after_window_creation: Option, ) -> Result> { self.context.create_window(pending, after_window_creation) } // Creates a webview by dispatching a message to the event loop. // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock. fn create_webview( &self, window_id: WindowId, pending: PendingWebview, ) -> Result> { self.context.create_webview(window_id, pending) } fn run_on_main_thread(&self, f: F) -> Result<()> { send_user_message(&self.context, Message::Task(Box::new(f))) } fn display_handle(&self) -> std::result::Result { self.context.main_thread.window_target.display_handle() } fn primary_monitor(&self) -> Option { self .context .main_thread .window_target .primary_monitor() .map(|m| MonitorHandleWrapper(m).into()) } fn monitor_from_point(&self, x: f64, y: f64) -> Option { self .context .main_thread .window_target .monitor_from_point(x, y) .map(|m| MonitorHandleWrapper(m).into()) } fn available_monitors(&self) -> Vec { self .context .main_thread .window_target .available_monitors() .map(|m| MonitorHandleWrapper(m).into()) .collect() } fn cursor_position(&self) -> Result> { self .context .main_thread .window_target .cursor_position() .map(PhysicalPositionWrapper) .map(Into::into) .map_err(|_| Error::FailedToGetCursorPosition) } #[cfg(target_os = "macos")] fn show(&self) -> tauri_runtime::Result<()> { send_user_message( &self.context, Message::Application(ApplicationMessage::Show), ) } #[cfg(target_os = "macos")] fn hide(&self) -> tauri_runtime::Result<()> { send_user_message( &self.context, Message::Application(ApplicationMessage::Hide), ) } #[cfg(target_os = "android")] fn find_class<'a>( &self, env: &mut jni::JNIEnv<'a>, activity: &jni::objects::JObject<'_>, name: impl Into, ) -> std::result::Result, jni::errors::Error> { find_class(env, activity, name.into()) } #[cfg(target_os = "android")] fn run_on_android_context(&self, f: F) where F: FnOnce(&mut jni::JNIEnv, &jni::objects::JObject, &jni::objects::JObject) + Send + 'static, { dispatch(f) } } impl Wry { fn init_with_builder( mut event_loop_builder: EventLoopBuilder>, #[allow(unused_variables)] args: RuntimeInitArgs, ) -> Result { #[cfg(windows)] if let Some(hook) = args.msg_hook { use tao::platform::windows::EventLoopBuilderExtWindows; event_loop_builder.with_msg_hook(hook); } #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] if let Some(app_id) = args.app_id { use tao::platform::unix::EventLoopBuilderExtUnix; event_loop_builder.with_app_id(app_id); } Self::init(event_loop_builder.build()) } fn init(event_loop: EventLoop>) -> Result { let main_thread_id = current_thread().id(); let web_context = WebContextStore::default(); let windows = Arc::new(WindowsStore(RefCell::new(BTreeMap::default()))); let window_id_map = WindowIdStore::default(); let context = Context { window_id_map, main_thread_id, proxy: event_loop.create_proxy(), main_thread: DispatcherMainThreadContext { window_target: event_loop.deref().clone(), web_context, windows, #[cfg(feature = "tracing")] active_tracing_spans: Default::default(), }, plugins: Default::default(), next_window_id: Default::default(), next_webview_id: Default::default(), next_window_event_id: Default::default(), next_webview_event_id: Default::default(), next_webcontext_id: Default::default(), }; Ok(Self { context, event_loop, }) } } impl Runtime for Wry { type WindowDispatcher = WryWindowDispatcher; type WebviewDispatcher = WryWebviewDispatcher; type Handle = WryHandle; type EventLoopProxy = EventProxy; fn new(args: RuntimeInitArgs) -> Result { Self::init_with_builder(EventLoopBuilder::>::with_user_event(), args) } #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] fn new_any_thread(args: RuntimeInitArgs) -> Result { use tao::platform::unix::EventLoopBuilderExtUnix; let mut event_loop_builder = EventLoopBuilder::>::with_user_event(); event_loop_builder.with_any_thread(true); Self::init_with_builder(event_loop_builder, args) } #[cfg(windows)] fn new_any_thread(args: RuntimeInitArgs) -> Result { use tao::platform::windows::EventLoopBuilderExtWindows; let mut event_loop_builder = EventLoopBuilder::>::with_user_event(); event_loop_builder.with_any_thread(true); Self::init_with_builder(event_loop_builder, args) } fn create_proxy(&self) -> EventProxy { EventProxy(self.event_loop.create_proxy()) } fn handle(&self) -> Self::Handle { WryHandle { context: self.context.clone(), } } fn create_window( &self, pending: PendingWindow, after_window_creation: Option, ) -> Result> { let label = pending.label.clone(); let window_id = self.context.next_window_id(); let webview_id = pending .webview .as_ref() .map(|_| self.context.next_webview_id()); let window = create_window( window_id, webview_id.unwrap_or_default(), &self.event_loop, &self.context, pending, after_window_creation, )?; let dispatcher = WryWindowDispatcher { window_id, context: self.context.clone(), }; self .context .main_thread .windows .0 .borrow_mut() .insert(window_id, window); let detached_webview = webview_id.map(|id| DetachedWebview { label: label.clone(), dispatcher: WryWebviewDispatcher { window_id: Arc::new(Mutex::new(window_id)), webview_id: id, context: self.context.clone(), }, }); Ok(DetachedWindow { id: window_id, label, dispatcher, webview: detached_webview, }) } fn create_webview( &self, window_id: WindowId, pending: PendingWebview, ) -> Result> { let label = pending.label.clone(); let window = self .context .main_thread .windows .0 .borrow() .get(&window_id) .and_then(|w| w.inner.clone()); if let Some(window) = window { let window_id_wrapper = Arc::new(Mutex::new(window_id)); let webview_id = self.context.next_webview_id(); let webview = create_webview( WebviewKind::WindowChild, &window, window_id_wrapper.clone(), webview_id, &self.context, pending, )?; self .context .main_thread .windows .0 .borrow_mut() .get_mut(&window_id) .map(|w| { w.webviews.push(webview); w.has_children.store(true, Ordering::Relaxed); w }); let dispatcher = WryWebviewDispatcher { window_id: window_id_wrapper, webview_id, context: self.context.clone(), }; Ok(DetachedWebview { label, dispatcher }) } else { Err(Error::WindowNotFound) } } fn primary_monitor(&self) -> Option { self .context .main_thread .window_target .primary_monitor() .map(|m| MonitorHandleWrapper(m).into()) } fn monitor_from_point(&self, x: f64, y: f64) -> Option { self .context .main_thread .window_target .monitor_from_point(x, y) .map(|m| MonitorHandleWrapper(m).into()) } fn available_monitors(&self) -> Vec { self .context .main_thread .window_target .available_monitors() .map(|m| MonitorHandleWrapper(m).into()) .collect() } fn cursor_position(&self) -> Result> { self .context .main_thread .window_target .cursor_position() .map(PhysicalPositionWrapper) .map(Into::into) .map_err(|_| Error::FailedToGetCursorPosition) } #[cfg(target_os = "macos")] fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) { self .event_loop .set_activation_policy(tao_activation_policy(activation_policy)); } #[cfg(target_os = "macos")] fn show(&self) { self.event_loop.show_application(); } #[cfg(target_os = "macos")] fn hide(&self) { self.event_loop.hide_application(); } fn set_device_event_filter(&mut self, filter: DeviceEventFilter) { self .event_loop .set_device_event_filter(DeviceEventFilterWrapper::from(filter).0); } #[cfg(desktop)] fn run_iteration) + 'static>(&mut self, mut callback: F) { use tao::platform::run_return::EventLoopExtRunReturn; let windows = self.context.main_thread.windows.clone(); let window_id_map = self.context.window_id_map.clone(); let web_context = &self.context.main_thread.web_context; let plugins = self.context.plugins.clone(); #[cfg(feature = "tracing")] let active_tracing_spans = self.context.main_thread.active_tracing_spans.clone(); let proxy = self.event_loop.create_proxy(); self .event_loop .run_return(|event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; if let Event::MainEventsCleared = &event { *control_flow = ControlFlow::Exit; } for p in plugins.lock().unwrap().iter_mut() { let prevent_default = p.on_event( &event, event_loop, &proxy, control_flow, EventLoopIterationContext { callback: &mut callback, window_id_map: window_id_map.clone(), windows: windows.clone(), #[cfg(feature = "tracing")] active_tracing_spans: active_tracing_spans.clone(), }, web_context, ); if prevent_default { return; } } handle_event_loop( event, event_loop, control_flow, EventLoopIterationContext { callback: &mut callback, windows: windows.clone(), window_id_map: window_id_map.clone(), #[cfg(feature = "tracing")] active_tracing_spans: active_tracing_spans.clone(), }, ); }); } fn run) + 'static>(self, mut callback: F) { let windows = self.context.main_thread.windows.clone(); let window_id_map = self.context.window_id_map.clone(); let web_context = self.context.main_thread.web_context; let plugins = self.context.plugins.clone(); #[cfg(feature = "tracing")] let active_tracing_spans = self.context.main_thread.active_tracing_spans.clone(); let proxy = self.event_loop.create_proxy(); self.event_loop.run(move |event, event_loop, control_flow| { for p in plugins.lock().unwrap().iter_mut() { let prevent_default = p.on_event( &event, event_loop, &proxy, control_flow, EventLoopIterationContext { callback: &mut callback, window_id_map: window_id_map.clone(), windows: windows.clone(), #[cfg(feature = "tracing")] active_tracing_spans: active_tracing_spans.clone(), }, &web_context, ); if prevent_default { return; } } handle_event_loop( event, event_loop, control_flow, EventLoopIterationContext { callback: &mut callback, window_id_map: window_id_map.clone(), windows: windows.clone(), #[cfg(feature = "tracing")] active_tracing_spans: active_tracing_spans.clone(), }, ); }) } } pub struct EventLoopIterationContext<'a, T: UserEvent> { pub callback: &'a mut (dyn FnMut(RunEvent) + 'static), pub window_id_map: WindowIdStore, pub windows: Arc, #[cfg(feature = "tracing")] pub active_tracing_spans: ActiveTraceSpanStore, } struct UserMessageContext { windows: Arc, window_id_map: WindowIdStore, } fn handle_user_message( event_loop: &EventLoopWindowTarget>, message: Message, context: UserMessageContext, ) { let UserMessageContext { window_id_map, windows, } = context; match message { Message::Task(task) => task(), #[cfg(target_os = "macos")] Message::SetActivationPolicy(activation_policy) => { event_loop.set_activation_policy_at_runtime(tao_activation_policy(activation_policy)) } Message::RequestExit(_code) => panic!("cannot handle RequestExit on the main thread"), #[cfg(target_os = "macos")] Message::Application(application_message) => match application_message { ApplicationMessage::Show => { event_loop.show_application(); } ApplicationMessage::Hide => { event_loop.hide_application(); } }, Message::Window(id, window_message) => { let w = windows.0.borrow().get(&id).map(|w| { ( w.inner.clone(), w.webviews.clone(), w.has_children.load(Ordering::Relaxed), w.window_event_listeners.clone(), ) }); if let Some((Some(window), webviews, has_children, window_event_listeners)) = w { match window_message { WindowMessage::AddEventListener(id, listener) => { window_event_listeners.lock().unwrap().insert(id, listener); } // Getters WindowMessage::ScaleFactor(tx) => tx.send(window.scale_factor()).unwrap(), WindowMessage::InnerPosition(tx) => tx .send( window .inner_position() .map(|p| PhysicalPositionWrapper(p).into()) .map_err(|_| Error::FailedToSendMessage), ) .unwrap(), WindowMessage::OuterPosition(tx) => tx .send( window .outer_position() .map(|p| PhysicalPositionWrapper(p).into()) .map_err(|_| Error::FailedToSendMessage), ) .unwrap(), WindowMessage::InnerSize(tx) => tx .send(PhysicalSizeWrapper(inner_size(&window, &webviews, has_children)).into()) .unwrap(), WindowMessage::OuterSize(tx) => tx .send(PhysicalSizeWrapper(window.outer_size()).into()) .unwrap(), WindowMessage::IsFullscreen(tx) => tx.send(window.fullscreen().is_some()).unwrap(), WindowMessage::IsMinimized(tx) => tx.send(window.is_minimized()).unwrap(), WindowMessage::IsMaximized(tx) => tx.send(window.is_maximized()).unwrap(), WindowMessage::IsFocused(tx) => tx.send(window.is_focused()).unwrap(), WindowMessage::IsDecorated(tx) => tx.send(window.is_decorated()).unwrap(), WindowMessage::IsResizable(tx) => tx.send(window.is_resizable()).unwrap(), WindowMessage::IsMaximizable(tx) => tx.send(window.is_maximizable()).unwrap(), WindowMessage::IsMinimizable(tx) => tx.send(window.is_minimizable()).unwrap(), WindowMessage::IsClosable(tx) => tx.send(window.is_closable()).unwrap(), WindowMessage::IsVisible(tx) => tx.send(window.is_visible()).unwrap(), WindowMessage::Title(tx) => tx.send(window.title()).unwrap(), WindowMessage::CurrentMonitor(tx) => tx.send(window.current_monitor()).unwrap(), WindowMessage::PrimaryMonitor(tx) => tx.send(window.primary_monitor()).unwrap(), WindowMessage::MonitorFromPoint(tx, (x, y)) => { tx.send(window.monitor_from_point(x, y)).unwrap() } WindowMessage::AvailableMonitors(tx) => { tx.send(window.available_monitors().collect()).unwrap() } #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] WindowMessage::GtkWindow(tx) => tx.send(GtkWindow(window.gtk_window().clone())).unwrap(), #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] WindowMessage::GtkBox(tx) => tx .send(GtkBox(window.default_vbox().unwrap().clone())) .unwrap(), WindowMessage::RawWindowHandle(tx) => tx .send( window .window_handle() .map(|h| SendRawWindowHandle(h.as_raw())), ) .unwrap(), WindowMessage::Theme(tx) => { tx.send(map_theme(&window.theme())).unwrap(); } // Setters WindowMessage::Center => { #[cfg(not(target_os = "macos"))] if let Some(monitor) = window.current_monitor() { #[allow(unused_mut)] let mut window_size = window.outer_size(); #[cfg(windows)] if window.is_decorated() { use windows::Win32::Foundation::RECT; use windows::Win32::Graphics::Dwm::{ DwmGetWindowAttribute, DWMWA_EXTENDED_FRAME_BOUNDS, }; let mut rect = RECT::default(); let result = unsafe { DwmGetWindowAttribute( HWND(window.hwnd() as _), DWMWA_EXTENDED_FRAME_BOUNDS, &mut rect as *mut _ as *mut _, std::mem::size_of::() as u32, ) }; if result.is_ok() { window_size.height = (rect.bottom - rect.top) as u32; } } window.set_outer_position(calculate_window_center_position(window_size, monitor)); } #[cfg(target_os = "macos")] { use cocoa::{appkit::NSWindow, base::id}; let ns_window: id = window.ns_window() as _; unsafe { ns_window.center() }; } } WindowMessage::RequestUserAttention(request_type) => { window.request_user_attention(request_type.map(|r| r.0)); } WindowMessage::SetResizable(resizable) => { window.set_resizable(resizable); #[cfg(windows)] if !resizable { undecorated_resizing::detach_resize_handler(window.hwnd()); } else if !window.is_decorated() { undecorated_resizing::attach_resize_handler(window.hwnd()); } } WindowMessage::SetMaximizable(maximizable) => window.set_maximizable(maximizable), WindowMessage::SetMinimizable(minimizable) => window.set_minimizable(minimizable), WindowMessage::SetClosable(closable) => window.set_closable(closable), WindowMessage::SetTitle(title) => window.set_title(&title), WindowMessage::Maximize => window.set_maximized(true), WindowMessage::Unmaximize => window.set_maximized(false), WindowMessage::Minimize => window.set_minimized(true), WindowMessage::Unminimize => window.set_minimized(false), WindowMessage::Show => window.set_visible(true), WindowMessage::Hide => window.set_visible(false), WindowMessage::Close => { panic!("cannot handle `WindowMessage::Close` on the main thread") } WindowMessage::Destroy => { panic!("cannot handle `WindowMessage::Destroy` on the main thread") } WindowMessage::SetDecorations(decorations) => { window.set_decorations(decorations); #[cfg(windows)] if decorations { undecorated_resizing::detach_resize_handler(window.hwnd()); } else if window.is_resizable() { undecorated_resizing::attach_resize_handler(window.hwnd()); } } WindowMessage::SetShadow(_enable) => { #[cfg(windows)] window.set_undecorated_shadow(_enable); #[cfg(target_os = "macos")] window.set_has_shadow(_enable); } WindowMessage::SetAlwaysOnBottom(always_on_bottom) => { window.set_always_on_bottom(always_on_bottom) } WindowMessage::SetAlwaysOnTop(always_on_top) => window.set_always_on_top(always_on_top), WindowMessage::SetVisibleOnAllWorkspaces(visible_on_all_workspaces) => { window.set_visible_on_all_workspaces(visible_on_all_workspaces) } WindowMessage::SetContentProtected(protected) => window.set_content_protection(protected), WindowMessage::SetSize(size) => { window.set_inner_size(SizeWrapper::from(size).0); } WindowMessage::SetMinSize(size) => { window.set_min_inner_size(size.map(|s| SizeWrapper::from(s).0)); } WindowMessage::SetMaxSize(size) => { window.set_max_inner_size(size.map(|s| SizeWrapper::from(s).0)); } WindowMessage::SetSizeConstraints(constraints) => { window.set_inner_size_constraints(tao::window::WindowSizeConstraints { min_width: constraints.min_width, min_height: constraints.min_height, max_width: constraints.max_width, max_height: constraints.max_height, }); } WindowMessage::SetPosition(position) => { window.set_outer_position(PositionWrapper::from(position).0) } WindowMessage::SetFullscreen(fullscreen) => { if fullscreen { window.set_fullscreen(Some(Fullscreen::Borderless(None))) } else { window.set_fullscreen(None) } } WindowMessage::SetFocus => { window.set_focus(); } WindowMessage::SetIcon(icon) => { window.set_window_icon(Some(icon)); } #[allow(unused_variables)] WindowMessage::SetSkipTaskbar(skip) => { #[cfg(any(windows, target_os = "linux"))] window.set_skip_taskbar(skip); } WindowMessage::SetCursorGrab(grab) => { let _ = window.set_cursor_grab(grab); } WindowMessage::SetCursorVisible(visible) => { window.set_cursor_visible(visible); } WindowMessage::SetCursorIcon(icon) => { window.set_cursor_icon(CursorIconWrapper::from(icon).0); } WindowMessage::SetCursorPosition(position) => { let _ = window.set_cursor_position(PositionWrapper::from(position).0); } WindowMessage::SetIgnoreCursorEvents(ignore) => { let _ = window.set_ignore_cursor_events(ignore); } WindowMessage::DragWindow => { let _ = window.drag_window(); } WindowMessage::ResizeDragWindow(direction) => { let _ = window.drag_resize_window(match direction { tauri_runtime::ResizeDirection::East => tao::window::ResizeDirection::East, tauri_runtime::ResizeDirection::North => tao::window::ResizeDirection::North, tauri_runtime::ResizeDirection::NorthEast => tao::window::ResizeDirection::NorthEast, tauri_runtime::ResizeDirection::NorthWest => tao::window::ResizeDirection::NorthWest, tauri_runtime::ResizeDirection::South => tao::window::ResizeDirection::South, tauri_runtime::ResizeDirection::SouthEast => tao::window::ResizeDirection::SouthEast, tauri_runtime::ResizeDirection::SouthWest => tao::window::ResizeDirection::SouthWest, tauri_runtime::ResizeDirection::West => tao::window::ResizeDirection::West, }); } WindowMessage::RequestRedraw => { window.request_redraw(); } WindowMessage::SetProgressBar(progress_state) => { window.set_progress_bar(ProgressBarStateWrapper::from(progress_state).0); } WindowMessage::SetTitleBarStyle(_style) => { #[cfg(target_os = "macos")] match _style { TitleBarStyle::Visible => { window.set_titlebar_transparent(false); window.set_fullsize_content_view(true); } TitleBarStyle::Transparent => { window.set_titlebar_transparent(true); window.set_fullsize_content_view(false); } TitleBarStyle::Overlay => { window.set_titlebar_transparent(true); window.set_fullsize_content_view(true); } unknown => { #[cfg(feature = "tracing")] tracing::warn!("unknown title bar style applied: {unknown}"); #[cfg(not(feature = "tracing"))] eprintln!("unknown title bar style applied: {unknown}"); } }; } } } } Message::Webview(window_id, webview_id, webview_message) => { #[cfg(any( target_os = "macos", windows, target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] if let WebviewMessage::Reparent(new_parent_window_id, tx) = webview_message { let webview_handle = windows.0.borrow_mut().get_mut(&window_id).and_then(|w| { w.webviews .iter() .position(|w| w.id == webview_id) .map(|webview_index| w.webviews.remove(webview_index)) }); if let Some(webview) = webview_handle { if let Some((Some(new_parent_window), new_parent_window_webviews)) = windows .0 .borrow_mut() .get_mut(&new_parent_window_id) .map(|w| (w.inner.clone(), &mut w.webviews)) { #[cfg(target_os = "macos")] let reparent_result = { use wry::WebViewExtMacOS; webview.inner.reparent(new_parent_window.ns_window() as _) }; #[cfg(windows)] let reparent_result = { webview.inner.reparent(new_parent_window.hwnd()) }; #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] let reparent_result = { if let Some(container) = new_parent_window.default_vbox() { webview.inner.reparent(container) } else { Err(wry::Error::MessageSender) } }; match reparent_result { Ok(_) => { new_parent_window_webviews.push(webview); tx.send(Ok(())).unwrap(); } Err(e) => { log::error!("failed to reparent webview: {e}"); tx.send(Err(Error::FailedToSendMessage)).unwrap(); } } } } else { tx.send(Err(Error::FailedToSendMessage)).unwrap(); } return; } let webview_handle = windows.0.borrow().get(&window_id).map(|w| { ( w.inner.clone(), w.webviews.iter().find(|w| w.id == webview_id).cloned(), ) }); if let Some((Some(window), Some(webview))) = webview_handle { match webview_message { WebviewMessage::WebviewEvent(_) => { /* already handled */ } WebviewMessage::SynthesizedWindowEvent(_) => { /* already handled */ } WebviewMessage::Reparent(_window_id, _tx) => { /* already handled */ } WebviewMessage::AddEventListener(id, listener) => { webview .webview_event_listeners .lock() .unwrap() .insert(id, listener); } #[cfg(all(feature = "tracing", not(target_os = "android")))] WebviewMessage::EvaluateScript(script, tx, span) => { let _span = span.entered(); if let Err(e) = webview.evaluate_script(&script) { log::error!("{}", e); } tx.send(()).unwrap(); } #[cfg(not(all(feature = "tracing", not(target_os = "android"))))] WebviewMessage::EvaluateScript(script) => { if let Err(e) = webview.evaluate_script(&script) { log::error!("{}", e); } } WebviewMessage::Navigate(url) => { if let Err(e) = webview.load_url(url.as_str()) { log::error!("failed to navigate to url {}: {}", url, e); } } WebviewMessage::Print => { let _ = webview.print(); } WebviewMessage::Close => { windows.0.borrow_mut().get_mut(&window_id).map(|window| { if let Some(i) = window.webviews.iter().position(|w| w.id == webview.id) { window.webviews.remove(i); } window }); } WebviewMessage::SetBounds(bounds) => { let bounds: RectWrapper = bounds.into(); let bounds = bounds.0; if let Some(b) = &mut *webview.bounds.lock().unwrap() { let scale_factor = window.scale_factor(); let size = bounds.size.to_logical::(scale_factor); let position = bounds.position.to_logical::(scale_factor); let window_size = window.inner_size().to_logical::(scale_factor); b.width_rate = size.width / window_size.width; b.height_rate = size.height / window_size.height; b.x_rate = position.x / window_size.width; b.y_rate = position.y / window_size.height; } if let Err(e) = webview.set_bounds(bounds) { log::error!("failed to set webview size: {e}"); } } WebviewMessage::SetSize(size) => match webview.bounds() { Ok(mut bounds) => { bounds.size = size; let scale_factor = window.scale_factor(); let size = size.to_logical::(scale_factor); if let Some(b) = &mut *webview.bounds.lock().unwrap() { let window_size = window.inner_size().to_logical::(scale_factor); b.width_rate = size.width / window_size.width; b.height_rate = size.height / window_size.height; } if let Err(e) = webview.set_bounds(bounds) { log::error!("failed to set webview size: {e}"); } } Err(e) => { log::error!("failed to get webview bounds: {e}"); } }, WebviewMessage::SetPosition(position) => match webview.bounds() { Ok(mut bounds) => { bounds.position = position; let scale_factor = window.scale_factor(); let position = position.to_logical::(scale_factor); if let Some(b) = &mut *webview.bounds.lock().unwrap() { let window_size = window.inner_size().to_logical::(scale_factor); b.x_rate = position.x / window_size.width; b.y_rate = position.y / window_size.height; } if let Err(e) = webview.set_bounds(bounds) { log::error!("failed to set webview position: {e}"); } } Err(e) => { log::error!("failed to get webview bounds: {e}"); } }, WebviewMessage::SetZoom(scale_factor) => { if let Err(e) = webview.zoom(scale_factor) { log::error!("failed to set webview zoom: {e}"); } } // Getters WebviewMessage::Url(tx) => { tx.send( webview .url() .map(|u| u.parse().expect("invalid webview URL")) .map_err(|_| Error::FailedToSendMessage), ) .unwrap(); } WebviewMessage::Bounds(tx) => { tx.send( webview .bounds() .map(|bounds| tauri_runtime::Rect { size: bounds.size, position: bounds.position, }) .map_err(|_| Error::FailedToSendMessage), ) .unwrap(); } WebviewMessage::Position(tx) => { tx.send( webview .bounds() .map(|bounds| bounds.position.to_physical(window.scale_factor())) .map_err(|_| Error::FailedToSendMessage), ) .unwrap(); } WebviewMessage::Size(tx) => { tx.send( webview .bounds() .map(|bounds| bounds.size.to_physical(window.scale_factor())) .map_err(|_| Error::FailedToSendMessage), ) .unwrap(); } WebviewMessage::SetFocus => { if let Err(e) = webview.focus() { log::error!("failed to focus webview: {e}"); } } WebviewMessage::SetAutoResize(auto_resize) => match webview.bounds() { Ok(bounds) => { let scale_factor = window.scale_factor(); let window_size = window.inner_size().to_logical::(scale_factor); *webview.bounds.lock().unwrap() = if auto_resize { let size = bounds.size.to_logical::(scale_factor); let position = bounds.position.to_logical::(scale_factor); Some(WebviewBounds { x_rate: position.x / window_size.width, y_rate: position.y / window_size.height, width_rate: size.width / window_size.width, height_rate: size.height / window_size.height, }) } else { None }; } Err(e) => { log::error!("failed to get webview bounds: {e}"); } }, WebviewMessage::WithWebview(f) => { #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] { f(webview.webview()); } #[cfg(target_os = "macos")] { use wry::WebViewExtMacOS; f(Webview { webview: webview.webview(), manager: webview.manager(), ns_window: webview.ns_window(), }); } #[cfg(target_os = "ios")] { use tao::platform::ios::WindowExtIOS; use wry::WebViewExtIOS; f(Webview { webview: webview.inner.webview(), manager: webview.inner.manager(), view_controller: window.ui_view_controller() as cocoa::base::id, }); } #[cfg(windows)] { f(Webview { controller: webview.controller(), }); } #[cfg(target_os = "android")] { f(webview.handle()) } } #[cfg(any(debug_assertions, feature = "devtools"))] WebviewMessage::OpenDevTools => { webview.open_devtools(); } #[cfg(any(debug_assertions, feature = "devtools"))] WebviewMessage::CloseDevTools => { webview.close_devtools(); } #[cfg(any(debug_assertions, feature = "devtools"))] WebviewMessage::IsDevToolsOpen(tx) => { tx.send(webview.is_devtools_open()).unwrap(); } } } } Message::CreateWebview(window_id, handler) => { let window = windows .0 .borrow() .get(&window_id) .and_then(|w| w.inner.clone()); if let Some(window) = window { match handler(&window) { Ok(webview) => { windows.0.borrow_mut().get_mut(&window_id).map(|w| { w.webviews.push(webview); w.has_children.store(true, Ordering::Relaxed); w }); } Err(e) => { log::error!("{}", e); } } } } Message::CreateWindow(window_id, handler) => match handler(event_loop) { Ok(webview) => { windows.0.borrow_mut().insert(window_id, webview); } Err(e) => { log::error!("{}", e); } }, Message::CreateRawWindow(window_id, handler, sender) => { let (label, builder) = handler(); #[cfg(windows)] let is_window_transparent = builder.window.transparent; if let Ok(window) = builder.build(event_loop) { window_id_map.insert(window.id(), window_id); let window = Arc::new(window); #[cfg(windows)] let surface = if is_window_transparent { if let Ok(context) = softbuffer::Context::new(window.clone()) { if let Ok(mut surface) = softbuffer::Surface::new(&context, window.clone()) { clear_window_surface(&window, &mut surface); Some(surface) } else { None } } else { None } } else { None }; windows.0.borrow_mut().insert( window_id, WindowWrapper { label, has_children: AtomicBool::new(false), inner: Some(window.clone()), window_event_listeners: Default::default(), webviews: Vec::new(), #[cfg(windows)] is_window_transparent, #[cfg(windows)] surface, }, ); sender.send(Ok(Arc::downgrade(&window))).unwrap(); } else { sender.send(Err(Error::CreateWindow)).unwrap(); } } Message::UserEvent(_) => (), } } fn handle_event_loop( event: Event<'_, Message>, event_loop: &EventLoopWindowTarget>, control_flow: &mut ControlFlow, context: EventLoopIterationContext<'_, T>, ) { let EventLoopIterationContext { callback, window_id_map, windows, #[cfg(feature = "tracing")] active_tracing_spans, } = context; if *control_flow != ControlFlow::Exit { *control_flow = ControlFlow::Wait; } match event { Event::NewEvents(StartCause::Init) => { callback(RunEvent::Ready); } Event::NewEvents(StartCause::Poll) => { callback(RunEvent::Resumed); } Event::MainEventsCleared => { callback(RunEvent::MainEventsCleared); } Event::LoopDestroyed => { callback(RunEvent::Exit); } #[cfg(windows)] Event::RedrawRequested(id) => { if let Some(window_id) = window_id_map.get(&id) { let mut windows_ref = windows.0.borrow_mut(); if let Some(window) = windows_ref.get_mut(&window_id) { if window.is_window_transparent { if let Some(surface) = &mut window.surface { if let Some(window) = &window.inner { clear_window_surface(window, surface) } } } } } } #[cfg(feature = "tracing")] Event::RedrawEventsCleared => { active_tracing_spans.remove_window_draw(); } Event::UserEvent(Message::Webview( window_id, webview_id, WebviewMessage::WebviewEvent(event), )) => { let windows_ref = windows.0.borrow(); if let Some(window) = windows_ref.get(&window_id) { if let Some(webview) = window.webviews.iter().find(|w| w.id == webview_id) { let label = webview.label.clone(); let webview_event_listeners = webview.webview_event_listeners.clone(); drop(windows_ref); callback(RunEvent::WebviewEvent { label, event: event.clone(), }); let listeners = webview_event_listeners.lock().unwrap(); let handlers = listeners.values(); for handler in handlers { handler(&event); } } } } Event::UserEvent(Message::Webview( window_id, _webview_id, WebviewMessage::SynthesizedWindowEvent(event), )) => { if let Some(event) = WindowEventWrapper::from(event).0 { let windows_ref = windows.0.borrow(); let window = windows_ref.get(&window_id); if let Some(window) = window { let label = window.label.clone(); let window_event_listeners = window.window_event_listeners.clone(); drop(windows_ref); callback(RunEvent::WindowEvent { label, event: event.clone(), }); let listeners = window_event_listeners.lock().unwrap(); let handlers = listeners.values(); for handler in handlers { handler(&event); } } } } Event::WindowEvent { event, window_id, .. } => { if let Some(window_id) = window_id_map.get(&window_id) { { let windows_ref = windows.0.borrow(); if let Some(window) = windows_ref.get(&window_id) { if let Some(event) = WindowEventWrapper::parse(window, &event).0 { let label = window.label.clone(); let window_event_listeners = window.window_event_listeners.clone(); drop(windows_ref); callback(RunEvent::WindowEvent { label, event: event.clone(), }); let listeners = window_event_listeners.lock().unwrap(); let handlers = listeners.values(); for handler in handlers { handler(&event); } } } } match event { #[cfg(windows)] TaoWindowEvent::ThemeChanged(theme) => { if let Some(window) = windows.0.borrow().get(&window_id) { for webview in &window.webviews { let theme = match theme { TaoTheme::Dark => wry::Theme::Dark, TaoTheme::Light => wry::Theme::Light, _ => wry::Theme::Light, }; if let Err(e) = webview.set_theme(theme) { log::error!("failed to set theme: {e}"); } } } } TaoWindowEvent::CloseRequested => { on_close_requested(callback, window_id, windows); } TaoWindowEvent::Destroyed => { let removed = windows.0.borrow_mut().remove(&window_id).is_some(); if removed { let is_empty = windows.0.borrow().is_empty(); if is_empty { let (tx, rx) = channel(); callback(RunEvent::ExitRequested { code: None, tx }); let recv = rx.try_recv(); let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent)); if !should_prevent { *control_flow = ControlFlow::Exit; } } } } TaoWindowEvent::Resized(size) => { if let Some((Some(window), webviews)) = windows .0 .borrow() .get(&window_id) .map(|w| (w.inner.clone(), w.webviews.clone())) { let size = size.to_logical::(window.scale_factor()); for webview in webviews { if let Some(b) = &*webview.bounds.lock().unwrap() { if let Err(e) = webview.set_bounds(wry::Rect { position: LogicalPosition::new(size.width * b.x_rate, size.height * b.y_rate) .into(), size: LogicalSize::new(size.width * b.width_rate, size.height * b.height_rate) .into(), }) { log::error!("failed to autoresize webview: {e}"); } } } } } _ => {} } } } Event::UserEvent(message) => match message { Message::RequestExit(code) => { let (tx, rx) = channel(); callback(RunEvent::ExitRequested { code: Some(code), tx, }); let recv = rx.try_recv(); let should_prevent = matches!(recv, Ok(ExitRequestedEventAction::Prevent)); if !should_prevent { *control_flow = ControlFlow::Exit; } } Message::Window(id, WindowMessage::Close) => { on_close_requested(callback, id, windows); } Message::Window(id, WindowMessage::Destroy) => { on_window_close(id, windows); } Message::UserEvent(t) => callback(RunEvent::UserEvent(t)), message => { handle_user_message( event_loop, message, UserMessageContext { window_id_map, windows, }, ); } }, #[cfg(any(target_os = "macos", target_os = "ios"))] Event::Opened { urls } => { callback(RunEvent::Opened { urls }); } #[cfg(target_os = "macos")] Event::Reopen { has_visible_windows, .. } => callback(RunEvent::Reopen { has_visible_windows, }), _ => (), } } fn on_close_requested<'a, T: UserEvent>( callback: &'a mut (dyn FnMut(RunEvent) + 'static), window_id: WindowId, windows: Arc, ) { let (tx, rx) = channel(); let windows_ref = windows.0.borrow(); if let Some(w) = windows_ref.get(&window_id) { let label = w.label.clone(); let window_event_listeners = w.window_event_listeners.clone(); drop(windows_ref); let listeners = window_event_listeners.lock().unwrap(); let handlers = listeners.values(); for handler in handlers { handler(&WindowEvent::CloseRequested { signal_tx: tx.clone(), }); } callback(RunEvent::WindowEvent { label, event: WindowEvent::CloseRequested { signal_tx: tx }, }); if let Ok(true) = rx.try_recv() { } else { on_window_close(window_id, windows); } } } fn on_window_close(window_id: WindowId, windows: Arc) { if let Some(window_wrapper) = windows.0.borrow_mut().get_mut(&window_id) { window_wrapper.inner = None; #[cfg(windows)] window_wrapper.surface.take(); } } fn parse_proxy_url(url: &Url) -> Result { let host = url.host().map(|h| h.to_string()).unwrap_or_default(); let port = url.port().map(|p| p.to_string()).unwrap_or_default(); if url.scheme() == "http" { let config = ProxyConfig::Http(ProxyEndpoint { host, port }); Ok(config) } else if url.scheme() == "socks5" { let config = ProxyConfig::Socks5(ProxyEndpoint { host, port }); Ok(config) } else { Err(Error::InvalidProxyUrl) } } fn create_window( window_id: WindowId, webview_id: u32, event_loop: &EventLoopWindowTarget>, context: &Context, pending: PendingWindow>, after_window_creation: Option, ) -> Result { #[allow(unused_mut)] let PendingWindow { mut window_builder, label, webview, } = pending; #[cfg(feature = "tracing")] let _webview_create_span = tracing::debug_span!("wry::webview::create").entered(); #[cfg(feature = "tracing")] let window_draw_span = tracing::debug_span!("wry::window::draw").entered(); #[cfg(feature = "tracing")] let window_create_span = tracing::debug_span!(parent: &window_draw_span, "wry::window::create").entered(); let window_event_listeners = WindowEventListeners::default(); #[cfg(windows)] let is_window_transparent = window_builder.inner.window.transparent; #[cfg(target_os = "macos")] { if window_builder.tabbing_identifier.is_none() || window_builder.inner.window.transparent || !window_builder.inner.window.decorations { window_builder.inner = window_builder.inner.with_automatic_window_tabbing(false); } } #[cfg(desktop)] if window_builder.center { let monitor = if let Some(window_position) = &window_builder.inner.window.position { event_loop.available_monitors().find(|m| { let monitor_pos = m.position(); let monitor_size = m.size(); // type annotations required for 32bit targets. let window_position: LogicalPosition = window_position.to_logical(m.scale_factor()); monitor_pos.x <= window_position.x && window_position.x <= monitor_pos.x + monitor_size.width as i32 && monitor_pos.y <= window_position.y && window_position.y <= monitor_pos.y + monitor_size.height as i32 }) } else { event_loop.primary_monitor() }; if let Some(monitor) = monitor { let desired_size = window_builder .inner .window .inner_size .unwrap_or_else(|| TaoPhysicalSize::new(800, 600).into()); let scale_factor = monitor.scale_factor(); #[allow(unused_mut)] let mut window_size = window_builder .inner .window .inner_size_constraints .clamp(desired_size, scale_factor) .to_physical::(scale_factor); #[cfg(windows)] { if window_builder.inner.window.decorations { use windows::Win32::UI::WindowsAndMessaging::{AdjustWindowRect, WS_OVERLAPPEDWINDOW}; let mut rect = windows::Win32::Foundation::RECT::default(); let result = unsafe { AdjustWindowRect(&mut rect, WS_OVERLAPPEDWINDOW, false) }; if result.is_ok() { window_size.width += (rect.right - rect.left) as u32; // rect.bottom is made out of shadow, and we don't care about it window_size.height += -rect.top as u32; } } } let position = calculate_window_center_position(window_size, monitor); let logical_position = position.to_logical::(scale_factor); window_builder = window_builder.position(logical_position.x, logical_position.y); } } let window = window_builder.inner.build(event_loop).unwrap(); #[cfg(feature = "tracing")] { drop(window_create_span); context .main_thread .active_tracing_spans .0 .borrow_mut() .push(ActiveTracingSpan::WindowDraw { id: window.id(), span: window_draw_span, }); } context.window_id_map.insert(window.id(), window_id); if let Some(handler) = after_window_creation { let raw = RawWindow { #[cfg(windows)] hwnd: window.hwnd(), #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] gtk_window: window.gtk_window(), #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] default_vbox: window.default_vbox(), _marker: &std::marker::PhantomData, }; handler(raw); } let mut webviews = Vec::new(); if let Some(webview) = webview { webviews.push(create_webview( #[cfg(feature = "unstable")] WebviewKind::WindowChild, #[cfg(not(feature = "unstable"))] WebviewKind::WindowContent, &window, Arc::new(Mutex::new(window_id)), webview_id, context, webview, )?); } let window = Arc::new(window); #[cfg(windows)] let surface = if is_window_transparent { if let Ok(context) = softbuffer::Context::new(window.clone()) { if let Ok(mut surface) = softbuffer::Surface::new(&context, window.clone()) { clear_window_surface(&window, &mut surface); Some(surface) } else { None } } else { None } } else { None }; Ok(WindowWrapper { label, has_children: AtomicBool::new(false), inner: Some(window), webviews, window_event_listeners, #[cfg(windows)] is_window_transparent, #[cfg(windows)] surface, }) } /// the kind of the webview #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] enum WebviewKind { // webview is the entire window content WindowContent, // webview is a child of the window, which can contain other webviews too WindowChild, } #[derive(Debug, Clone)] struct WebviewBounds { x_rate: f32, y_rate: f32, width_rate: f32, height_rate: f32, } fn create_webview( kind: WebviewKind, window: &Window, window_id: Arc>, id: WebviewId, context: &Context, pending: PendingWebview>, ) -> Result { #[allow(unused_mut)] let PendingWebview { webview_attributes, uri_scheme_protocols, label, ipc_handler, url, .. } = pending; let builder = match kind { #[cfg(not(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" )))] WebviewKind::WindowChild => { // only way to account for menu bar height, and also works for multiwebviews :) let vbox = window.default_vbox().unwrap(); WebViewBuilder::new_gtk(vbox) } #[cfg(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" ))] WebviewKind::WindowChild => WebViewBuilder::new_as_child(&window), WebviewKind::WindowContent => { #[cfg(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" ))] let builder = WebViewBuilder::new(&window); #[cfg(not(any( target_os = "windows", target_os = "macos", target_os = "ios", target_os = "android" )))] let builder = { let vbox = window.default_vbox().unwrap(); WebViewBuilder::new_gtk(vbox) }; builder } }; let mut webview_builder = builder .with_focused(window.is_focused()) .with_url(&url) .with_transparent(webview_attributes.transparent) .with_accept_first_mouse(webview_attributes.accept_first_mouse) .with_hotkeys_zoom(webview_attributes.zoom_hotkeys_enabled); if webview_attributes.drag_drop_handler_enabled { let proxy = context.proxy.clone(); let window_id_ = window_id.clone(); webview_builder = webview_builder.with_drag_drop_handler(move |event| { let event = match event { WryDragDropEvent::Enter { paths, position: (x, y), } => DragDropEvent::Enter { paths, position: PhysicalPosition::new(x as _, y as _), }, WryDragDropEvent::Over { position: (x, y) } => DragDropEvent::Over { position: PhysicalPosition::new(x as _, y as _), }, WryDragDropEvent::Drop { paths, position: (x, y), } => DragDropEvent::Drop { paths, position: PhysicalPosition::new(x as _, y as _), }, WryDragDropEvent::Leave => DragDropEvent::Leave, _ => unimplemented!(), }; let message = if kind == WebviewKind::WindowContent { WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::DragDrop(event)) } else { WebviewMessage::WebviewEvent(WebviewEvent::DragDrop(event)) }; let _ = proxy.send_event(Message::Webview(*window_id_.lock().unwrap(), id, message)); true }); } if let Some(navigation_handler) = pending.navigation_handler { webview_builder = webview_builder.with_navigation_handler(move |url| { url .parse() .map(|url| navigation_handler(&url)) .unwrap_or(true) }); } let webview_bounds = if let Some(bounds) = webview_attributes.bounds { let bounds: RectWrapper = bounds.into(); let bounds = bounds.0; let scale_factor = window.scale_factor(); let position = bounds.position.to_logical::(scale_factor); let size = bounds.size.to_logical::(scale_factor); webview_builder = webview_builder.with_bounds(bounds); let window_size = window.inner_size().to_logical::(scale_factor); if webview_attributes.auto_resize { Some(WebviewBounds { x_rate: position.x / window_size.width, y_rate: position.y / window_size.height, width_rate: size.width / window_size.width, height_rate: size.height / window_size.height, }) } else { None } } else { #[cfg(feature = "unstable")] { webview_builder = webview_builder.with_bounds(wry::Rect { position: LogicalPosition::new(0, 0).into(), size: window.inner_size().into(), }); Some(WebviewBounds { x_rate: 0., y_rate: 0., width_rate: 1., height_rate: 1., }) } #[cfg(not(feature = "unstable"))] None }; if let Some(download_handler) = pending.download_handler { let download_handler_ = download_handler.clone(); webview_builder = webview_builder.with_download_started_handler(move |url, path| { if let Ok(url) = url.parse() { download_handler_(DownloadEvent::Requested { url, destination: path, }) } else { false } }); webview_builder = webview_builder.with_download_completed_handler(move |url, path, success| { if let Ok(url) = url.parse() { download_handler(DownloadEvent::Finished { url, path, success }); } }); } if let Some(page_load_handler) = pending.on_page_load_handler { webview_builder = webview_builder.with_on_page_load_handler(move |event, url| { let _ = url.parse().map(|url| { page_load_handler( url, match event { wry::PageLoadEvent::Started => tauri_runtime::webview::PageLoadEvent::Started, wry::PageLoadEvent::Finished => tauri_runtime::webview::PageLoadEvent::Finished, }, ) }); }); } if let Some(user_agent) = webview_attributes.user_agent { webview_builder = webview_builder.with_user_agent(&user_agent); } if let Some(proxy_url) = webview_attributes.proxy_url { let config = parse_proxy_url(&proxy_url)?; webview_builder = webview_builder.with_proxy_config(config); } #[cfg(windows)] { if let Some(additional_browser_args) = webview_attributes.additional_browser_args { webview_builder = webview_builder.with_additional_browser_args(&additional_browser_args); } webview_builder = webview_builder.with_theme(match window.theme() { TaoTheme::Dark => wry::Theme::Dark, TaoTheme::Light => wry::Theme::Light, _ => wry::Theme::Light, }); } #[cfg(windows)] { webview_builder = webview_builder.with_https_scheme(false); } webview_builder = webview_builder.with_ipc_handler(create_ipc_handler( kind, window_id.clone(), id, context.clone(), label.clone(), ipc_handler, )); for (scheme, protocol) in uri_scheme_protocols { webview_builder = webview_builder.with_asynchronous_custom_protocol(scheme, move |request, responder| { protocol( request, Box::new(move |response| responder.respond(response)), ) }); } for script in webview_attributes.initialization_scripts { webview_builder = webview_builder.with_initialization_script(&script); } let mut web_context = context .main_thread .web_context .lock() .expect("poisoned WebContext store"); let is_first_context = web_context.is_empty(); let automation_enabled = std::env::var("TAURI_WEBVIEW_AUTOMATION").as_deref() == Ok("true"); let web_context_key = // force a unique WebContext when automation is false; // the context must be stored on the HashMap because it must outlive the WebView on macOS if automation_enabled { webview_attributes.data_directory.clone() } else { // unique key let key = context.next_webcontext_id().to_string().into(); Some(key) }; let entry = web_context.entry(web_context_key.clone()); let web_context = match entry { Occupied(occupied) => occupied.into_mut(), Vacant(vacant) => { let mut web_context = WebContext::new(webview_attributes.data_directory); web_context.set_allows_automation(if automation_enabled { is_first_context } else { false }); vacant.insert(web_context) } }; if webview_attributes.clipboard { webview_builder.attrs.clipboard = true; } if webview_attributes.incognito { webview_builder.attrs.incognito = true; } #[cfg(any(debug_assertions, feature = "devtools"))] { webview_builder = webview_builder.with_devtools(true); } #[cfg(target_os = "android")] { if let Some(on_webview_created) = pending.on_webview_created { webview_builder = webview_builder.on_webview_created(move |ctx| { on_webview_created(tauri_runtime::webview::CreationContext { env: ctx.env, activity: ctx.activity, webview: ctx.webview, }) }); } } let webview = webview_builder .with_web_context(web_context) .build() .map_err(|e| Error::CreateWebview(Box::new(e)))?; if kind == WebviewKind::WindowContent { #[cfg(any( target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] undecorated_resizing::attach_resize_handler(&webview); #[cfg(windows)] if window.is_resizable() && !window.is_decorated() { undecorated_resizing::attach_resize_handler(window.hwnd()); } } #[cfg(windows)] if kind == WebviewKind::WindowContent { let controller = webview.controller(); let proxy = context.proxy.clone(); let proxy_ = proxy.clone(); let window_id_ = window_id.clone(); let mut token = EventRegistrationToken::default(); unsafe { controller.add_GotFocus( &FocusChangedEventHandler::create(Box::new(move |_, _| { let _ = proxy.send_event(Message::Webview( *window_id_.lock().unwrap(), id, WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(true)), )); Ok(()) })), &mut token, ) } .unwrap(); unsafe { controller.add_LostFocus( &FocusChangedEventHandler::create(Box::new(move |_, _| { let _ = proxy_.send_event(Message::Webview( *window_id.lock().unwrap(), id, WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(false)), )); Ok(()) })), &mut token, ) } .unwrap(); } Ok(WebviewWrapper { label, id, inner: Rc::new(webview), context_store: context.main_thread.web_context.clone(), webview_event_listeners: Default::default(), context_key: if automation_enabled { None } else { web_context_key }, bounds: Arc::new(Mutex::new(webview_bounds)), }) } /// Create a wry ipc handler from a tauri ipc handler. fn create_ipc_handler( _kind: WebviewKind, window_id: Arc>, webview_id: WebviewId, context: Context, label: String, ipc_handler: Option>>, ) -> Box { Box::new(move |request| { if let Some(handler) = &ipc_handler { handler( DetachedWebview { label: label.clone(), dispatcher: WryWebviewDispatcher { window_id: window_id.clone(), webview_id, context: context.clone(), }, }, request, ); } }) } #[cfg(target_os = "macos")] fn inner_size( window: &Window, webviews: &[WebviewWrapper], has_children: bool, ) -> TaoPhysicalSize { if !has_children && !webviews.is_empty() { use wry::WebViewExtMacOS; let webview = webviews.first().unwrap(); let view_frame = unsafe { cocoa::appkit::NSView::frame(webview.webview()) }; let logical: TaoLogicalSize = (view_frame.size.width, view_frame.size.height).into(); return logical.to_physical(window.scale_factor()); } window.inner_size() } #[cfg(not(target_os = "macos"))] #[allow(unused_variables)] fn inner_size( window: &Window, webviews: &[WebviewWrapper], has_children: bool, ) -> TaoPhysicalSize { window.inner_size() } fn calculate_window_center_position( window_size: TaoPhysicalSize, target_monitor: MonitorHandle, ) -> TaoPhysicalPosition { #[cfg(windows)] { use tao::platform::windows::MonitorHandleExtWindows; use windows::Win32::Graphics::Gdi::{GetMonitorInfoW, HMONITOR, MONITORINFO}; let mut monitor_info = MONITORINFO { cbSize: std::mem::size_of::() as u32, ..Default::default() }; let status = unsafe { GetMonitorInfoW(HMONITOR(target_monitor.hmonitor() as _), &mut monitor_info) }; if status.into() { let available_width = monitor_info.rcWork.right - monitor_info.rcWork.left; let available_height = monitor_info.rcWork.bottom - monitor_info.rcWork.top; let x = (available_width - window_size.width as i32) / 2 + monitor_info.rcWork.left; let y = (available_height - window_size.height as i32) / 2 + monitor_info.rcWork.top; return TaoPhysicalPosition::new(x, y); } } let screen_size = target_monitor.size(); let monitor_pos = target_monitor.position(); let x = (screen_size.width as i32 - window_size.width as i32) / 2 + monitor_pos.x; let y = (screen_size.height as i32 - window_size.height as i32) / 2 + monitor_pos.y; TaoPhysicalPosition::new(x, y) } #[cfg(windows)] fn clear_window_surface( window: &Window, surface: &mut softbuffer::Surface, Arc>, ) { let size = window.inner_size(); if let (Some(width), Some(height)) = ( std::num::NonZeroU32::new(size.width), std::num::NonZeroU32::new(size.height), ) { surface.resize(width, height).unwrap(); let mut buffer = surface.buffer_mut().unwrap(); buffer.fill(0); let _ = buffer.present(); } }