Skip to main content

tauri_plugin_window_system/
lib.rs

1mod commands;
2mod events;
3mod lifecycle;
4mod message;
5mod registry;
6
7use crate::lifecycle::capture_window_geometry;
8use crate::registry::{window_system_error, WindowSystemErrorKind};
9use std::cmp::Ordering;
10use tauri::plugin::{Builder, TauriPlugin};
11use tauri::{Manager, Runtime, WebviewWindow};
12
13pub use commands::{RestoreSkipReason, RestoreSkippedWindow, RestoreWindowsResult};
14pub use message::{
15    BroadcastWindowMessageRequest, SendWindowMessageRequest, WindowMessageDispatchResult,
16    WindowMessageEnvelope, WindowMessageKind, WindowMessageScope,
17};
18pub use registry::{WindowDescriptor, WindowGeometry, WindowRegistry, WindowStateStore};
19
20pub fn init<R: Runtime>() -> TauriPlugin<R> {
21    Builder::new("window-system")
22        .setup(|app, _api| {
23            let app_data_dir = app.path().app_data_dir().map_err(|err| err.to_string())?;
24            let state_path = app_data_dir.join("window-system/windows.json");
25            let registry = WindowRegistry::new(WindowStateStore::new(state_path));
26
27            // The plugin keeps registry and persisted geometry in managed state so commands and
28            // window-event hooks share a single source of truth.
29            // Seed windows that Tauri already created from config so parent lookups can resolve
30            // immediately on startup.
31            sync_live_windows(app, &registry)?;
32            app.manage(registry);
33            Ok(())
34        })
35        .on_drop(|app| {
36            if let Err(err) = app.state::<WindowRegistry>().flush() {
37                eprintln!("window-system: drop flush failed: {err}");
38            }
39        })
40        .invoke_handler(tauri::generate_handler![
41            commands::open_window,
42            commands::close_window,
43            commands::list_windows,
44            commands::emit_to_window,
45            commands::restore_windows,
46            commands::send_window_message,
47            commands::broadcast_window_message
48        ])
49        .build()
50}
51
52pub(crate) fn sync_live_windows<R: Runtime>(
53    app: &tauri::AppHandle<R>,
54    registry: &WindowRegistry,
55) -> Result<(), String> {
56    let mut windows: Vec<_> = app.webview_windows().into_values().collect();
57    windows.sort_by(compare_window_labels);
58
59    for window in windows {
60        sync_live_webview_window(registry, &window)?;
61    }
62
63    Ok(())
64}
65
66pub(crate) fn sync_live_window<R: Runtime>(
67    app: &tauri::AppHandle<R>,
68    registry: &WindowRegistry,
69    label: &str,
70) -> Result<(), String> {
71    if registry.get(label)?.is_some() {
72        // The caller is already registered, so avoid re-capturing geometry and rewriting
73        // the same descriptor on every child-open path.
74        return Ok(());
75    }
76
77    let window = app.get_webview_window(label).ok_or_else(|| {
78        window_system_error(
79            WindowSystemErrorKind::WindowNotFound,
80            format!("window not found: {label}"),
81        )
82    })?;
83
84    sync_live_webview_window(registry, &window)
85}
86
87fn sync_live_webview_window<R: Runtime>(
88    registry: &WindowRegistry,
89    window: &WebviewWindow<R>,
90) -> Result<(), String> {
91    registry.upsert_live_window(live_window_descriptor(window)?)?;
92    Ok(())
93}
94
95fn live_window_descriptor<R: Runtime>(
96    window: &WebviewWindow<R>,
97) -> Result<WindowDescriptor, String> {
98    Ok(WindowDescriptor {
99        label: window.label().to_string(),
100        url: window.url().map_err(|err| err.to_string())?.to_string(),
101        parent: None,
102        title: window.title().ok(),
103        geometry: capture_window_geometry(window).ok(),
104    })
105}
106
107fn compare_window_labels<R: Runtime>(
108    left: &WebviewWindow<R>,
109    right: &WebviewWindow<R>,
110) -> Ordering {
111    left.label().cmp(right.label())
112}