Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
132 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::note_dev_server(&handle);
70 config::load(&handle);
71 config::allow_site(&handle)?;
72 let hidden = settings::starts_hidden(&handle);
73 let prefs = config::settings(&handle);
74 let custom = frame::started(&handle);
75 let builder = WebviewWindowBuilder::new(app, WINDOW, WebviewUrl::App("index.html".into()))
76 .title("g1t")
77 .inner_size(1280.0, 840.0)
78 .min_inner_size(720.0, 480.0)
79 .visible(false)
80 .initialization_script(bridge::script(WINDOW, custom))
81 .on_navigation({
82 let handle = handle.clone();
83 move |url| navigation::allowed(&handle, url)
84 })
85 .on_page_load({
86 let handle = handle.clone();
87 move |_webview, payload| config::at(&handle, payload.url().clone())
88 });
89 // macOS has one menu bar, the app's; elsewhere the menu is the window's own.
90 #[cfg(target_os = "macos")]
91 app.set_menu(menu::build(&handle)?)?;
92 #[cfg(not(target_os = "macos"))]
93 let builder = builder.menu(menu::build(&handle)?);
94 let window = frame::style(builder, custom).build()?;
95 // Opened at sign-in, g1t waits in the tray; otherwise the window comes up where it was.
96 let _ = window.restore_state(StateFlags::all() - StateFlags::VISIBLE);
97 #[cfg(not(target_os = "macos"))]
98 frame::menu_bar(&window, custom);
99 if !hidden {
100 let _ = window.show();
101 }
102 if prefs.zoom != 1.0 {
103 let _ = window.set_zoom(prefs.zoom);
104 }
105 // Closing the window keeps g1t running, for notifications, unless settings say it quits.
106 window.on_window_event({
107 let handle = handle.clone();
108 move |event| {
109 if let WindowEvent::CloseRequested { api, .. } = event {
110 api.prevent_close();
111 tray::close(&handle);
112 }
113 }
114 });
115 tray::install(&handle)?;
116 links::install(&handle);
117 updates::schedule(handle);
118 Ok(())
119 })
120 .build(tauri::generate_context!())
121 .expect("g1t could not start")
122 .run(|app, event| match event {
123 // The dock icon, clicked while the window is hidden.
124 #[cfg(target_os = "macos")]
125 RunEvent::Reopen { .. } => tray::show(app),
126 // No window open is not a reason to quit: the tray is still there.
127 RunEvent::ExitRequested { api, code: None, .. } => api.prevent_exit(),
128 _ => {
129 let _ = app;
130 }
131 });
132}