Skip to content
131 linesCodeBlameRaw
1//! The g1t desktop app: g1t.sh, or a g1t of your own, in a window of its
2//! own. The window loads the live site, so every change to g1t reaches it
3//! the moment it is deployed and nothing here needs a new build for it;
4//! only what is native lives in this program: notifications from the
5//! system, a badge on the icon, a tray, a menu, `g1t://` links, the server
6//! to use, the app's own settings, and updates that install themselves
7//! (docs.g1t.sh/guides/desktop/).
8//!
9//! The site finds the app at `window.g1tDesktop` (bridge.js), the one
10//! contract between the two; what the page asks of it is answered by the
11//! commands in bridge.rs, each of which checks that the page asking is the
12//! configured server's.
13#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
14
15mod badge;
16mod bridge;
17mod config;
18mod frame;
19mod links;
20mod menu;
21mod navigation;
22mod settings;
23mod tray;
24mod updates;
25
26use tauri::{RunEvent, WebviewUrl, WebviewWindowBuilder, WindowEvent};
27use tauri_plugin_window_state::{StateFlags, WindowExt};
28
29/// The one window's label.
30pub const WINDOW: &str = "main";
31
32fn main() {
33 tauri::Builder::default()
34 // A second launch (a g1t:// link, or the icon clicked again) goes to
35 // the app already running: the link reaches links.rs through the
36 // deep-link plugin, and the window comes forward.
37 .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| tray::show(app)))
38 .plugin(tauri_plugin_deep_link::init())
39 .plugin(tauri_plugin_notification::init())
40 .plugin(tauri_plugin_opener::init())
41 .plugin(tauri_plugin_updater::Builder::new().build())
42 // The window's size and place come back in setup, without the plugin
43 // also showing it: at sign-in g1t may start in the tray.
44 .plugin(tauri_plugin_window_state::Builder::new().skip_initial_state(WINDOW).with_denylist(&[settings::SETTINGS]).build())
45 // Opening at sign-in, which settings turn on; the argument tells that start from someone's.
46 .plugin(tauri_plugin_autostart::init(tauri_plugin_autostart::MacosLauncher::LaunchAgent, Some(vec![settings::AT_LOGIN])))
47 .manage(config::State::default())
48 .invoke_handler(tauri::generate_handler![
49 bridge::desktop_info,
50 bridge::notify,
51 bridge::set_badge,
52 bridge::open_url,
53 bridge::reachable,
54 bridge::set_server,
55 bridge::check_for_updates,
56 bridge::open_settings,
57 bridge::get_settings,
58 bridge::save_settings,
59 bridge::set_open_at_login,
60 bridge::change_server,
61 bridge::restart_to_update,
62 bridge::restart,
63 bridge::window_control,
64 bridge::window_menu,
65 ])
66 .on_menu_event(|app, event| menu::handle(app, event.id().as_ref()))
67 .setup(|app| {
68 let handle = app.handle().clone();
69 config::load(&handle);
70 config::allow_site(&handle)?;
71 let hidden = settings::starts_hidden(&handle);
72 let prefs = config::settings(&handle);
73 let custom = frame::started(&handle);
74 let builder = WebviewWindowBuilder::new(app, WINDOW, WebviewUrl::App("index.html".into()))
75 .title("g1t")
76 .inner_size(1280.0, 840.0)
77 .min_inner_size(720.0, 480.0)
78 .visible(false)
79 .initialization_script(bridge::script(WINDOW, custom))
80 .on_navigation({
81 let handle = handle.clone();
82 move |url| navigation::allowed(&handle, url)
83 })
84 .on_page_load({
85 let handle = handle.clone();
86 move |_webview, payload| config::at(&handle, payload.url().clone())
87 });
88 // macOS has one menu bar, the app's; elsewhere the menu is the window's own.
89 #[cfg(target_os = "macos")]
90 app.set_menu(menu::build(&handle)?)?;
91 #[cfg(not(target_os = "macos"))]
92 let builder = builder.menu(menu::build(&handle)?);
93 let window = frame::style(builder, custom).build()?;
94 // Opened at sign-in, g1t waits in the tray; otherwise the window comes up where it was.
95 let _ = window.restore_state(StateFlags::all() - StateFlags::VISIBLE);
96 #[cfg(not(target_os = "macos"))]
97 frame::menu_bar(&window, custom);
98 if !hidden {
99 let _ = window.show();
100 }
101 if prefs.zoom != 1.0 {
102 let _ = window.set_zoom(prefs.zoom);
103 }
104 // Closing the window keeps g1t running, for notifications, unless settings say it quits.
105 window.on_window_event({
106 let handle = handle.clone();
107 move |event| {
108 if let WindowEvent::CloseRequested { api, .. } = event {
109 api.prevent_close();
110 tray::close(&handle);
111 }
112 }
113 });
114 tray::install(&handle)?;
115 links::install(&handle);
116 updates::schedule(handle);
117 Ok(())
118 })
119 .build(tauri::generate_context!())
120 .expect("g1t could not start")
121 .run(|app, event| match event {
122 // The dock icon, clicked while the window is hidden.
123 #[cfg(target_os = "macos")]
124 RunEvent::Reopen { .. } => tray::show(app),
125 // No window open is not a reason to quit: the tray is still there.
126 RunEvent::ExitRequested { api, code: None, .. } => api.prevent_exit(),
127 _ => {
128 let _ = app;
129 }
130 });
131}