Skip to main content

origin_tauri/
lib.rs

1//! The Tauri host layer (ADR-0001).
2//!
3//! Everything Tauri-specific about an Origin application lives here: plugin
4//! registration, tray, window handling, the IPC surface and the bridge that forwards
5//! platform events to the webview.
6//!
7//! A product's `main.rs` stays small:
8//!
9//! ```ignore
10//! fn main() {
11//!     let config = HostConfig::new("dev.origin.demo");
12//!     origin_tauri::builder(&config)
13//!         .invoke_handler(origin_handler![my_product_command])
14//!         .setup(move |app| {
15//!             let application = my_product::build(app.handle())?;
16//!             origin_tauri::attach(app.handle(), application, &config)?;
17//!             Ok(())
18//!         })
19//!         .run(tauri::generate_context!())
20//!         .expect("failed to start");
21//! }
22//! ```
23
24mod bridge;
25pub mod commands;
26mod config;
27pub mod defaults;
28mod opener;
29mod state;
30mod tray;
31
32pub use commands::CommandError;
33pub use config::HostConfig;
34pub use opener::TauriOpener;
35pub use state::OriginState;
36pub use tray::TauriTrayService;
37
38pub use origin_notifications_tauri::TauriNotificationService;
39
40use origin_app::Application;
41use tauri::{AppHandle, Manager, Runtime, Wry};
42use tokio_util::sync::CancellationToken;
43
44/// A `tauri::Builder` with the Origin plugin set already registered.
45///
46/// Single-instance must be the first plugin, which is easy to get wrong by hand —
47/// one more reason for products not to assemble this themselves.
48pub fn builder(config: &HostConfig) -> tauri::Builder<Wry> {
49    let mut builder = tauri::Builder::default();
50
51    if config.single_instance {
52        builder = builder.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
53            focus_main_window(app);
54        }));
55    }
56
57    builder = builder
58        .plugin(tauri_plugin_notification::init())
59        .plugin(tauri_plugin_opener::init());
60
61    if config.window_state {
62        builder = builder.plugin(tauri_plugin_window_state::Builder::default().build());
63    }
64
65    builder
66}
67
68/// Hand the assembled application to the desktop shell.
69///
70/// Call this from `setup`, after the composition root has built the [`Application`].
71pub fn attach(app: &AppHandle, application: Application, config: &HostConfig) -> tauri::Result<()> {
72    let scheduler = CancellationToken::new();
73    let state = OriginState::new(application, config.clone(), scheduler.clone());
74
75    // Started here rather than in the product: `setup` runs outside a runtime context,
76    // and knowing which executor to use is the host layer's job (ADR-0001).
77    let engine = state.application().platform().sync.clone();
78    tauri::async_runtime::spawn(async move { engine.run(scheduler).await });
79
80    bridge::forward_platform_events(app, state.application());
81
82    if config.tray {
83        tray::install(app, config, state.application().platform().events.clone())?;
84    }
85
86    app.manage(state);
87    tracing::info!(app_id = %config.app_id, "origin host attached");
88    Ok(())
89}
90
91/// Show and focus the main window, creating nothing — if it was closed to tray it is
92/// only hidden.
93pub fn focus_main_window<R: Runtime>(app: &AppHandle<R>) {
94    if let Some(window) = app.get_webview_window("main") {
95        let _ = window.show();
96        let _ = window.unminimize();
97        let _ = window.set_focus();
98    }
99}
100
101/// Builds a Tauri invoke handler containing Origin's own commands plus the product's.
102///
103/// Tauri allows exactly one invoke handler per application, so products must not call
104/// `tauri::generate_handler!` themselves — they would drop the platform commands.
105#[macro_export]
106macro_rules! origin_handler {
107    ($($command:path),* $(,)?) => {
108        ::tauri::generate_handler![
109            $crate::commands::origin_app_info,
110            $crate::commands::origin_setting_get,
111            $crate::commands::origin_setting_set,
112            $crate::commands::origin_settings_customised,
113            $crate::commands::origin_open_url,
114            $crate::commands::origin_accounts,
115            $crate::commands::origin_account_disconnect,
116            $crate::commands::origin_connectors,
117            $crate::commands::origin_jobs,
118            $crate::commands::origin_job_cancel,
119            $crate::commands::origin_sync_status,
120            $crate::commands::origin_sync_now,
121            $crate::commands::origin_health,
122            $($command),*
123        ]
124    };
125}