pub(crate) mod menu;
pub use menu::{MenuEvent, MenuHandle};
use crate::{
app::AppHandle,
command::{CommandArg, CommandItem},
event::{Event, EventHandler},
hooks::{InvokePayload, InvokeResponder},
manager::WindowManager,
runtime::{
monitor::Monitor as RuntimeMonitor,
webview::{WebviewAttributes, WindowBuilder},
window::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
DetachedWindow, JsEventListenerKey, PendingWindow, WindowEvent,
},
Dispatch, Icon, Runtime, UserAttentionType,
},
sealed::ManagerBase,
sealed::RuntimeOrDispatch,
utils::config::WindowUrl,
Invoke, InvokeError, InvokeMessage, InvokeResolver, Manager, PageLoadPayload,
};
use serde::Serialize;
use tauri_macros::default_runtime;
use std::{
hash::{Hash, Hasher},
sync::Arc,
};
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Monitor {
pub(crate) name: Option<String>,
pub(crate) size: PhysicalSize<u32>,
pub(crate) position: PhysicalPosition<i32>,
pub(crate) scale_factor: f64,
}
impl From<RuntimeMonitor> for Monitor {
fn from(monitor: RuntimeMonitor) -> Self {
Self {
name: monitor.name,
size: monitor.size,
position: monitor.position,
scale_factor: monitor.scale_factor,
}
}
}
impl Monitor {
pub fn name(&self) -> Option<&String> {
self.name.as_ref()
}
pub fn size(&self) -> &PhysicalSize<u32> {
&self.size
}
pub fn position(&self) -> &PhysicalPosition<i32> {
&self.position
}
pub fn scale_factor(&self) -> f64 {
self.scale_factor
}
}
#[default_runtime(crate::Wry, wry)]
#[derive(Debug)]
pub struct Window<R: Runtime> {
window: DetachedWindow<R>,
manager: WindowManager<R>,
pub(crate) app_handle: AppHandle<R>,
}
#[cfg(any(windows, target_os = "macos"))]
#[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "macos"))))]
unsafe impl<R: Runtime> raw_window_handle::HasRawWindowHandle for Window<R> {
#[cfg(windows)]
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
let mut handle = raw_window_handle::Win32Handle::empty();
handle.hwnd = self.hwnd().expect("failed to get window `hwnd`");
raw_window_handle::RawWindowHandle::Win32(handle)
}
#[cfg(target_os = "macos")]
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
let mut handle = raw_window_handle::AppKitHandle::empty();
handle.ns_window = self
.ns_window()
.expect("failed to get window's `ns_window`");
raw_window_handle::RawWindowHandle::AppKit(handle)
}
}
impl<R: Runtime> Clone for Window<R> {
fn clone(&self) -> Self {
Self {
window: self.window.clone(),
manager: self.manager.clone(),
app_handle: self.app_handle.clone(),
}
}
}
impl<R: Runtime> Hash for Window<R> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.window.label.hash(state)
}
}
impl<R: Runtime> Eq for Window<R> {}
impl<R: Runtime> PartialEq for Window<R> {
fn eq(&self, other: &Self) -> bool {
self.window.label.eq(&other.window.label)
}
}
impl<R: Runtime> Manager<R> for Window<R> {}
impl<R: Runtime> ManagerBase<R> for Window<R> {
fn manager(&self) -> &WindowManager<R> {
&self.manager
}
fn runtime(&self) -> RuntimeOrDispatch<'_, R> {
RuntimeOrDispatch::Dispatch(self.dispatcher())
}
fn app_handle(&self) -> AppHandle<R> {
self.app_handle.clone()
}
}
impl<'de, R: Runtime> CommandArg<'de, R> for Window<R> {
fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {
Ok(command.message.window())
}
}
impl<R: Runtime> Window<R> {
pub(crate) fn new(
manager: WindowManager<R>,
window: DetachedWindow<R>,
app_handle: AppHandle<R>,
) -> Self {
Self {
window,
manager,
app_handle,
}
}
pub fn create_window<F>(
&mut self,
label: String,
url: WindowUrl,
setup: F,
) -> crate::Result<Window<R>>
where
F: FnOnce(
<R::Dispatcher as Dispatch>::WindowBuilder,
WebviewAttributes,
) -> (
<R::Dispatcher as Dispatch>::WindowBuilder,
WebviewAttributes,
),
{
let (window_builder, webview_attributes) = setup(
<R::Dispatcher as Dispatch>::WindowBuilder::new(),
WebviewAttributes::new(url),
);
self.create_new_window(PendingWindow::new(
window_builder,
webview_attributes,
label,
))
}
pub(crate) fn invoke_responder(&self) -> Arc<InvokeResponder<R>> {
self.manager.invoke_responder()
}
pub(crate) fn dispatcher(&self) -> R::Dispatcher {
self.window.dispatcher.clone()
}
pub fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()> {
self
.window
.dispatcher
.run_on_main_thread(f)
.map_err(Into::into)
}
pub fn on_message(self, payload: InvokePayload) -> crate::Result<()> {
let manager = self.manager.clone();
match payload.cmd.as_str() {
"__initialized" => {
let payload: PageLoadPayload = serde_json::from_value(payload.inner)?;
manager.run_on_page_load(self, payload);
}
_ => {
let message = InvokeMessage::new(
self.clone(),
manager.state(),
payload.cmd.to_string(),
payload.inner,
);
let resolver = InvokeResolver::new(self, payload.callback, payload.error);
let invoke = Invoke { message, resolver };
if let Some(module) = &payload.tauri_module {
let module = module.to_string();
crate::endpoints::handle(module, invoke, manager.config(), manager.package_info());
} else if payload.cmd.starts_with("plugin:") {
manager.extend_api(invoke);
} else {
manager.run_invoke_handler(invoke);
}
}
}
Ok(())
}
pub fn label(&self) -> &str {
&self.window.label
}
pub fn emit_and_trigger<S: Serialize + Clone>(
&self,
event: &str,
payload: S,
) -> crate::Result<()> {
self.trigger(event, Some(serde_json::to_string(&payload)?));
self.emit(event, payload)
}
pub(crate) fn emit_internal<S: Serialize>(
&self,
event: &str,
source_window_label: Option<&str>,
payload: S,
) -> crate::Result<()> {
self.eval(&format!(
"window['{}']({{event: {}, windowLabel: {}, payload: {}}})",
self.manager.event_emit_function_name(),
serde_json::to_string(event)?,
serde_json::to_string(&source_window_label)?,
serde_json::to_value(payload)?,
))?;
Ok(())
}
pub fn emit<S: Serialize + Clone>(&self, event: &str, payload: S) -> crate::Result<()> {
self
.manager
.emit_filter(event, Some(self.label()), payload, |w| {
w.has_js_listener(None, event) || w.has_js_listener(Some(self.label().into()), event)
})?;
Ok(())
}
pub fn listen<F>(&self, event: impl Into<String>, handler: F) -> EventHandler
where
F: Fn(Event) + Send + 'static,
{
let label = self.window.label.clone();
self.manager.listen(event.into(), Some(label), handler)
}
pub fn unlisten(&self, handler_id: EventHandler) {
self.manager.unlisten(handler_id)
}
pub fn once<F>(&self, event: impl Into<String>, handler: F) -> EventHandler
where
F: FnOnce(Event) + Send + 'static,
{
let label = self.window.label.clone();
self.manager.once(event.into(), Some(label), handler)
}
pub fn trigger(&self, event: &str, data: Option<String>) {
let label = self.window.label.clone();
self.manager.trigger(event, Some(label), data)
}
pub fn eval(&self, js: &str) -> crate::Result<()> {
self.window.dispatcher.eval_script(js).map_err(Into::into)
}
pub fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) {
self.window.dispatcher.on_window_event(f);
}
pub fn on_menu_event<F: Fn(MenuEvent) + Send + 'static>(&self, f: F) -> uuid::Uuid {
let menu_ids = self.window.menu_ids.clone();
self.window.dispatcher.on_menu_event(move |event| {
f(MenuEvent {
menu_item_id: menu_ids
.lock()
.unwrap()
.get(&event.menu_item_id)
.unwrap()
.clone(),
})
})
}
pub(crate) fn register_js_listener(&self, window_label: Option<String>, event: String, id: u64) {
self
.window
.js_event_listeners
.lock()
.unwrap()
.entry(JsEventListenerKey {
window_label,
event,
})
.or_insert_with(Default::default)
.insert(id);
}
pub(crate) fn unregister_js_listener(&self, id: u64) {
let mut empty = None;
let mut js_listeners = self.window.js_event_listeners.lock().unwrap();
for (key, ids) in js_listeners.iter_mut() {
if ids.contains(&id) {
ids.remove(&id);
if ids.is_empty() {
empty.replace(key.clone());
}
break;
}
}
if let Some(key) = empty {
js_listeners.remove(&key);
}
}
pub(crate) fn has_js_listener(&self, window_label: Option<String>, event: &str) -> bool {
self
.window
.js_event_listeners
.lock()
.unwrap()
.contains_key(&JsEventListenerKey {
window_label,
event: event.into(),
})
}
#[cfg(any(debug_assertions, feature = "devtools"))]
#[cfg_attr(doc_cfg, doc(cfg(any(debug_assertions, feature = "devtools"))))]
pub fn open_devtools(&self) {
self.window.dispatcher.open_devtools();
}
pub fn menu_handle(&self) -> MenuHandle<R> {
MenuHandle {
ids: self.window.menu_ids.clone(),
dispatcher: self.dispatcher(),
}
}
pub fn scale_factor(&self) -> crate::Result<f64> {
self.window.dispatcher.scale_factor().map_err(Into::into)
}
pub fn inner_position(&self) -> crate::Result<PhysicalPosition<i32>> {
self.window.dispatcher.inner_position().map_err(Into::into)
}
pub fn outer_position(&self) -> crate::Result<PhysicalPosition<i32>> {
self.window.dispatcher.outer_position().map_err(Into::into)
}
pub fn inner_size(&self) -> crate::Result<PhysicalSize<u32>> {
self.window.dispatcher.inner_size().map_err(Into::into)
}
pub fn outer_size(&self) -> crate::Result<PhysicalSize<u32>> {
self.window.dispatcher.outer_size().map_err(Into::into)
}
pub fn is_fullscreen(&self) -> crate::Result<bool> {
self.window.dispatcher.is_fullscreen().map_err(Into::into)
}
pub fn is_maximized(&self) -> crate::Result<bool> {
self.window.dispatcher.is_maximized().map_err(Into::into)
}
pub fn is_decorated(&self) -> crate::Result<bool> {
self.window.dispatcher.is_decorated().map_err(Into::into)
}
pub fn is_resizable(&self) -> crate::Result<bool> {
self.window.dispatcher.is_resizable().map_err(Into::into)
}
pub fn is_visible(&self) -> crate::Result<bool> {
self.window.dispatcher.is_visible().map_err(Into::into)
}
pub fn current_monitor(&self) -> crate::Result<Option<Monitor>> {
self
.window
.dispatcher
.current_monitor()
.map(|m| m.map(Into::into))
.map_err(Into::into)
}
pub fn primary_monitor(&self) -> crate::Result<Option<Monitor>> {
self
.window
.dispatcher
.primary_monitor()
.map(|m| m.map(Into::into))
.map_err(Into::into)
}
pub fn available_monitors(&self) -> crate::Result<Vec<Monitor>> {
self
.window
.dispatcher
.available_monitors()
.map(|m| m.into_iter().map(Into::into).collect())
.map_err(Into::into)
}
#[cfg(target_os = "macos")]
pub fn ns_window(&self) -> crate::Result<*mut std::ffi::c_void> {
self.window.dispatcher.ns_window().map_err(Into::into)
}
#[cfg(windows)]
pub fn hwnd(&self) -> crate::Result<*mut std::ffi::c_void> {
self
.window
.dispatcher
.hwnd()
.map(|hwnd| hwnd.0 as *mut _)
.map_err(Into::into)
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
pub fn gtk_window(&self) -> crate::Result<gtk::ApplicationWindow> {
self.window.dispatcher.gtk_window().map_err(Into::into)
}
pub fn center(&self) -> crate::Result<()> {
self.window.dispatcher.center().map_err(Into::into)
}
pub fn request_user_attention(
&self,
request_type: Option<UserAttentionType>,
) -> crate::Result<()> {
self
.window
.dispatcher
.request_user_attention(request_type)
.map_err(Into::into)
}
pub fn print(&self) -> crate::Result<()> {
self.window.dispatcher.print().map_err(Into::into)
}
pub fn set_resizable(&self, resizable: bool) -> crate::Result<()> {
self
.window
.dispatcher
.set_resizable(resizable)
.map_err(Into::into)
}
pub fn set_title(&self, title: &str) -> crate::Result<()> {
self
.window
.dispatcher
.set_title(title.to_string())
.map_err(Into::into)
}
pub fn maximize(&self) -> crate::Result<()> {
self.window.dispatcher.maximize().map_err(Into::into)
}
pub fn unmaximize(&self) -> crate::Result<()> {
self.window.dispatcher.unmaximize().map_err(Into::into)
}
pub fn minimize(&self) -> crate::Result<()> {
self.window.dispatcher.minimize().map_err(Into::into)
}
pub fn unminimize(&self) -> crate::Result<()> {
self.window.dispatcher.unminimize().map_err(Into::into)
}
pub fn show(&self) -> crate::Result<()> {
self.window.dispatcher.show().map_err(Into::into)
}
pub fn hide(&self) -> crate::Result<()> {
self.window.dispatcher.hide().map_err(Into::into)
}
pub fn close(&self) -> crate::Result<()> {
self.window.dispatcher.close().map_err(Into::into)
}
pub fn set_decorations(&self, decorations: bool) -> crate::Result<()> {
self
.window
.dispatcher
.set_decorations(decorations)
.map_err(Into::into)
}
pub fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> {
self
.window
.dispatcher
.set_always_on_top(always_on_top)
.map_err(Into::into)
}
pub fn set_size<S: Into<Size>>(&self, size: S) -> crate::Result<()> {
self
.window
.dispatcher
.set_size(size.into())
.map_err(Into::into)
}
pub fn set_min_size<S: Into<Size>>(&self, size: Option<S>) -> crate::Result<()> {
self
.window
.dispatcher
.set_min_size(size.map(|s| s.into()))
.map_err(Into::into)
}
pub fn set_max_size<S: Into<Size>>(&self, size: Option<S>) -> crate::Result<()> {
self
.window
.dispatcher
.set_max_size(size.map(|s| s.into()))
.map_err(Into::into)
}
pub fn set_position<Pos: Into<Position>>(&self, position: Pos) -> crate::Result<()> {
self
.window
.dispatcher
.set_position(position.into())
.map_err(Into::into)
}
pub fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {
self
.window
.dispatcher
.set_fullscreen(fullscreen)
.map_err(Into::into)
}
pub fn set_focus(&self) -> crate::Result<()> {
self.window.dispatcher.set_focus().map_err(Into::into)
}
pub fn set_icon(&self, icon: Icon) -> crate::Result<()> {
self.window.dispatcher.set_icon(icon).map_err(Into::into)
}
pub fn set_skip_taskbar(&self, skip: bool) -> crate::Result<()> {
self
.window
.dispatcher
.set_skip_taskbar(skip)
.map_err(Into::into)
}
pub fn start_dragging(&self) -> crate::Result<()> {
self.window.dispatcher.start_dragging().map_err(Into::into)
}
}
#[cfg(test)]
mod tests {
#[test]
fn window_is_send_sync() {
crate::test_utils::assert_send::<super::Window>();
crate::test_utils::assert_sync::<super::Window>();
}
}