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;
36
37pub use origin_notifications_tauri::TauriNotificationService;
38
39use origin_app::Application;
40use tauri::{AppHandle, Manager, Wry};
41use tokio_util::sync::CancellationToken;
42
43/// A `tauri::Builder` with the Origin plugin set already registered.
44///
45/// Single-instance must be the first plugin, which is easy to get wrong by hand —
46/// one more reason for products not to assemble this themselves.
47pub fn builder(config: &HostConfig) -> tauri::Builder<Wry> {
48    let mut builder = tauri::Builder::default();
49
50    if config.single_instance {
51        builder = builder.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
52            focus_main_window(app);
53        }));
54    }
55
56    builder = builder
57        .plugin(tauri_plugin_notification::init())
58        .plugin(tauri_plugin_opener::init());
59
60    if config.window_state {
61        builder = builder.plugin(tauri_plugin_window_state::Builder::default().build());
62    }
63
64    builder
65}
66
67/// Hand the assembled application to the desktop shell.
68///
69/// Call this from `setup`, after the composition root has built the [`Application`].
70pub fn attach(app: &AppHandle, application: Application, config: &HostConfig) -> tauri::Result<()> {
71    let scheduler = CancellationToken::new();
72    let state = OriginState::new(application, config.clone(), scheduler.clone());
73
74    // Started here rather than in the product: `setup` runs outside a runtime context,
75    // and knowing which executor to use is the host layer's job (ADR-0001).
76    let engine = state.application().platform().sync.clone();
77    tauri::async_runtime::spawn(async move { engine.run(scheduler).await });
78
79    bridge::forward_platform_events(app, state.application());
80
81    if config.tray {
82        tray::install(app, config)?;
83    }
84
85    app.manage(state);
86    tracing::info!(app_id = %config.app_id, "origin host attached");
87    Ok(())
88}
89
90/// Show and focus the main window, creating nothing — if it was closed to tray it is
91/// only hidden.
92pub fn focus_main_window(app: &AppHandle) {
93    if let Some(window) = app.get_webview_window("main") {
94        let _ = window.show();
95        let _ = window.unminimize();
96        let _ = window.set_focus();
97    }
98}
99
100/// Builds a Tauri invoke handler containing Origin's own commands plus the product's.
101///
102/// Tauri allows exactly one invoke handler per application, so products must not call
103/// `tauri::generate_handler!` themselves — they would drop the platform commands.
104#[macro_export]
105macro_rules! origin_handler {
106    ($($command:path),* $(,)?) => {
107        ::tauri::generate_handler![
108            $crate::commands::origin_app_info,
109            $crate::commands::origin_setting_get,
110            $crate::commands::origin_setting_set,
111            $crate::commands::origin_settings_customised,
112            $crate::commands::origin_open_url,
113            $crate::commands::origin_accounts,
114            $crate::commands::origin_account_disconnect,
115            $crate::commands::origin_connectors,
116            $crate::commands::origin_jobs,
117            $crate::commands::origin_job_cancel,
118            $crate::commands::origin_sync_status,
119            $crate::commands::origin_sync_now,
120            $crate::commands::origin_health,
121            $($command),*
122        ]
123    };
124}