Skip to content
311 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

preparing for cloud work1//! What the site may ask of the app: the commands behind `window.g1tDesktop`
2//! (bridge.js), and the launcher's own.
3//!
4//! Every command checks who is asking. The site's commands answer only a
5//! page of the configured server; the launcher's only the page bundled
6//! with the app. A page from anywhere else gets nothing, whatever it
7//! loads.
8
9use serde::Serialize;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r910use tauri::{AppHandle, Manager, UserAttentionType, Webview};
preparing for cloud work11use tauri_plugin_notification::NotificationExt;
12use tauri_plugin_opener::OpenerExt;
13use url::Url;
14
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r915use crate::settings::{self, Settings};
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.16use crate::{badge, config, frame, runner, updates};
preparing for cloud work17
18const SCRIPT: &str = include_str!("bridge.js");
19
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r920/// The bridge, run at the start of every page the site's web view loads:
21/// `custom` is whether g1t's title bar is above it (frame.rs).
22pub fn script(custom: bool) -> String {
23 SCRIPT
24 .replace("__FRAME__", if custom { "custom" } else { "system" })
25 .replace("__VERSION__", env!("CARGO_PKG_VERSION"))
26 .replace("__PLATFORM__", std::env::consts::OS)
27 .replace("\"__OUTSIDE__\"", &serde_json::to_string(crate::navigation::OUTSIDE).expect("a list of paths is JSON"))
preparing for cloud work28}
29
30/// What the site and the launcher learn about the app.
31#[derive(Serialize)]
32pub struct Info {
33 pub version: String,
34 pub platform: String,
35 pub server: String,
36 /// The version downloaded and waiting for a restart, if one is.
37 pub update_ready: Option<String>,
38}
39
40fn caller(webview: &Webview) -> Result<Url, String> {
41 webview.url().map_err(|error| error.to_string())
42}
43
44/// The page asking is one of the server's.
45fn from_site(app: &AppHandle, webview: &Webview) -> Result<(), String> {
46 let url = caller(webview)?;
47 if config::is_server(app, &url) { Ok(()) } else { Err("only g1t's own pages may ask that".into()) }
48}
49
50/// The page asking is the launcher.
51fn from_launcher(webview: &Webview) -> Result<(), String> {
52 let url = caller(webview)?;
53 if config::is_launcher(&url) { Ok(()) } else { Err("only the launcher may ask that".into()) }
54}
55
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r956/// The page asking is the settings window's, bundled with the app.
57fn from_settings(webview: &Webview) -> Result<(), String> {
58 from_launcher(webview)?;
59 if webview.label() == settings::SETTINGS { Ok(()) } else { Err("only the settings window may ask that".into()) }
60}
61
preparing for cloud work62fn from_either(app: &AppHandle, webview: &Webview) -> Result<(), String> {
63 from_site(app, webview).or_else(|_| from_launcher(webview))
64}
65
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r966/// The page asking is the one under the title bar, whichever it is: the
67/// site's, the launcher, or GitHub's while signing in.
68fn from_page(app: &AppHandle, webview: &Webview) -> Result<(), String> {
69 let url = caller(webview)?;
70 let shown = config::is_server(app, &url) || config::is_launcher(&url) || crate::navigation::signing_in(&url);
71 if webview.label() == frame::SITE && shown { Ok(()) } else { Err("only the page in the window may ask that".into()) }
72}
73
74/// The page asking is the title bar's, bundled with the app.
75fn from_bar(webview: &Webview) -> Result<(), String> {
76 from_launcher(webview)?;
77 if webview.label() == frame::BAR { Ok(()) } else { Err("only the title bar may ask that".into()) }
78}
79
80/// The page asking has window buttons: the title bar, or the settings window's own.
81fn from_frame(webview: &Webview) -> Result<(), String> {
82 from_bar(webview).or_else(|_| from_settings(webview))
83}
84
preparing for cloud work85#[tauri::command]
86pub fn desktop_info(app: AppHandle, webview: Webview) -> Result<Info, String> {
87 from_either(&app, &webview)?;
88 Ok(Info {
89 version: env!("CARGO_PKG_VERSION").into(),
90 platform: std::env::consts::OS.into(),
91 server: config::server(&app).to_string(),
92 update_ready: updates::ready(&app),
93 })
94}
95
96/// A notification from the system: the site asks for one when a message
97/// is for the person and the window is behind another.
98#[tauri::command]
99pub fn notify(app: AppHandle, webview: Webview, title: String, body: String) -> Result<(), String> {
100 from_site(&app, &webview)?;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9101 let settings = config::settings(&app);
102 if let Some(window) = crate::frame::window(&app).filter(|window| settings.flash && !window.is_focused().unwrap_or(false)) {
103 let _ = window.request_user_attention(Some(UserAttentionType::Informational));
104 }
105 if !settings.notifications {
106 return Ok(());
107 }
preparing for cloud work108 app.notification().builder().title(title).body(body).show().map_err(|error| error.to_string())
109}
110
111/// What needs the person, on the icon and the tray; 0 clears it.
112#[tauri::command]
113pub fn set_badge(app: AppHandle, webview: Webview, count: i64) -> Result<(), String> {
114 from_site(&app, &webview)?;
115 badge::set(&app, count.max(0));
116 Ok(())
117}
118
119/// A link to somewhere else, opened in the browser the system uses.
120#[tauri::command]
121pub fn open_url(app: AppHandle, webview: Webview, url: String) -> Result<(), String> {
122 from_either(&app, &webview)?;
123 let parsed = Url::parse(&url).map_err(|error| error.to_string())?;
124 if !matches!(parsed.scheme(), "http" | "https" | "mailto") {
125 return Err(format!("{} links stay in the app", parsed.scheme()));
126 }
127 app.opener().open_url(parsed.as_str(), None::<&str>).map_err(|error| error.to_string())
128}
129
130/// Whether a server answers at all: the launcher asks before the window
131/// goes there, so a machine with no network sees a page of ours, not the
132/// web view's own error.
133#[tauri::command]
134pub async fn reachable(app: AppHandle, webview: Webview, url: Option<String>) -> Result<bool, String> {
135 from_launcher(&webview)?;
136 let target = match url {
137 Some(text) => config::parse(&text).ok_or("That is not a web address.")?,
138 None => config::server(&app),
139 };
140 let client = reqwest::Client::builder()
141 .timeout(std::time::Duration::from_secs(6))
142 .build()
143 .map_err(|error| error.to_string())?;
144 Ok(client.head(target).send().await.is_ok())
145}
146
147/// The launcher chose a server: it is kept, and the window goes there next.
148#[tauri::command]
149pub fn set_server(app: AppHandle, webview: Webview, url: String) -> Result<String, String> {
150 from_launcher(&webview)?;
151 config::set_server(&app, &url).map(|server| server.to_string())
152}
153
154/// Looks for an update now: `current`, `ready:<version>`, or an error.
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9155/// The settings window shows the answer itself; anyone else asking is also
156/// told with a notification.
preparing for cloud work157#[tauri::command]
158pub async fn check_for_updates(app: AppHandle, webview: Webview) -> Result<String, String> {
159 from_either(&app, &webview)?;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9160 updates::check(&app, webview.label() != settings::SETTINGS).await
161}
162
163/// A title bar button: `minimize`, `maximize` (which toggles), `close`,
164/// `drag`, or `maximized` to ask. Answers whether the window is maximized.
165#[tauri::command]
166pub fn window_control(app: AppHandle, webview: Webview, action: String) -> Result<bool, String> {
167 from_frame(&webview)?;
168 crate::frame::control(&app, &webview.window(), &action)
169}
170
171/// The title bar's menu button: the app's menu, at that place in the window.
172#[tauri::command]
173pub fn window_menu(webview: Webview, x: f64, y: f64) -> Result<(), String> {
174 from_bar(&webview)?;
175 crate::frame::menu(&webview.window(), x, y)
176}
177
178/// The page under the title bar says what it is: its title, its search,
179/// whether there is a way back, its colours (frame.rs).
180#[tauri::command]
181pub fn title_bar_page(app: AppHandle, webview: Webview, page: frame::Page) -> Result<(), String> {
182 from_page(&app, &webview)?;
183 frame::page(&app, page);
184 Ok(())
185}
186
187/// What the title bar shows now: it asks as it loads.
188#[tauri::command]
189pub fn title_bar_current(app: AppHandle, webview: Webview) -> Result<frame::Page, String> {
190 from_bar(&webview)?;
191 Ok(frame::current(&app))
192}
193
194/// A title bar button that acts on the page: `back`, `forward`, `search`, `focus`.
195#[tauri::command]
196pub fn title_bar_act(app: AppHandle, webview: Webview, action: String) -> Result<(), String> {
197 from_bar(&webview)?;
198 frame::act(&app, &action)
199}
200
201/// Restarts g1t, installing a downloaded update on the way: the settings
202/// window offers it after a change that needs one.
203#[tauri::command]
204pub fn restart(app: AppHandle, webview: Webview) -> Result<(), String> {
205 from_settings(&webview)?;
206 if updates::ready(&app).is_some() {
207 updates::restart(&app);
208 }
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.209 runner::shutdown(&app);
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9210 app.restart()
211}
212
213/// The settings window's "Restart now": installs the downloaded update and starts it.
214#[tauri::command]
215pub fn restart_to_update(app: AppHandle, webview: Webview) -> Result<(), String> {
216 from_settings(&webview)?;
217 updates::restart(&app);
218 Ok(())
219}
220
221/// Opens the app's settings window: the site's account menu asks for it.
222#[tauri::command]
223pub fn open_settings(app: AppHandle, webview: Webview) -> Result<(), String> {
224 from_either(&app, &webview)?;
225 settings::open(&app);
226 Ok(())
227}
228
229/// What the settings window shows.
230#[tauri::command]
231pub fn get_settings(app: AppHandle, webview: Webview) -> Result<settings::View, String> {
232 from_settings(&webview)?;
233 Ok(settings::view(&app))
234}
235
236/// The settings window changed something: everything but opening at sign-in.
237#[tauri::command]
238pub fn save_settings(app: AppHandle, webview: Webview, settings: Settings) -> Result<settings::View, String> {
239 from_settings(&webview)?;
240 settings::apply(&app, settings)?;
241 Ok(settings::view(&app))
242}
243
244/// Opening at sign-in, which the system keeps.
245#[tauri::command]
246pub fn set_open_at_login(app: AppHandle, webview: Webview, on: bool) -> Result<settings::View, String> {
247 from_settings(&webview)?;
248 settings::open_at_login(&app, on)?;
249 Ok(settings::view(&app))
250}
251
252/// The settings window's "Change…": the main window goes to the launcher's choice of server.
253#[tauri::command]
254pub fn change_server(app: AppHandle, webview: Webview) -> Result<(), String> {
255 from_settings(&webview)?;
256 if let Some(window) = app.get_webview_window(settings::SETTINGS) {
257 let _ = window.close();
258 }
259 crate::tray::launcher(&app, "change");
260 Ok(())
261}
262
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.263/// The runner's state, diagnosis and recent log: for the site's card for
264/// this computer and the settings window.
265#[tauri::command]
266pub fn runner_status(app: AppHandle, webview: Webview) -> Result<runner::View, String> {
267 from_site(&app, &webview).or_else(|_| from_settings(&webview))?;
268 Ok(runner::view(&app))
269}
270
271/// The site, on every page: whether someone is signed in, the workspaces
272/// they own (the only ones they can add runners to), and the one they are
273/// in. Answers the workspace a registration token is wanted for, if one is.
274#[tauri::command]
275pub fn runner_offer(app: AppHandle, webview: Webview, signed_in: bool, workspaces: Vec<runner::Offered>, current: Option<String>) -> Result<Option<String>, String> {
276 from_site(&app, &webview)?;
277 runner::offer(&app, signed_in, workspaces, current);
278 Ok(runner::wanted(&app))
279}
280
281/// The site made a registration token for the workspace the app asked
282/// for, or says why not: `allowed` is false when the person may not add
283/// runners there. The token goes to the runner and nowhere else.
284#[tauri::command]
285pub fn runner_register(app: AppHandle, webview: Webview, workspace: String, token: Option<String>, allowed: bool, error: Option<String>) -> Result<(), String> {
286 from_site(&app, &webview)?;
287 runner::register(&app, workspace, token, allowed, error);
288 Ok(())
289}
290
291/// A button for the runner: Start, Stop, or a diagnosis's fix
292/// (runner/diagnosis.rs), from settings or the site's card.
293#[tauri::command]
294pub fn runner_control(app: AppHandle, webview: Webview, action: String) -> Result<(), String> {
295 from_site(&app, &webview).or_else(|_| from_settings(&webview))?;
296 runner::act(&app, &action)
297}
298
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9299#[cfg(test)]
300mod tests {
301 #[test]
302 fn the_bridge_has_nothing_left_to_fill_in() {
303 let script = super::script(true);
304 for placeholder in ["__VERSION__", "__PLATFORM__", "__OUTSIDE__", "__FRAME__"] {
305 assert!(!script.contains(placeholder), "{placeholder} is left in the bridge");
306 }
307 assert!(script.contains(r#"var FRAME = "custom";"#));
308 assert!(super::script(false).contains(r#"var FRAME = "system";"#));
309 assert!(script.contains(r#"var OUTSIDE = ["/pricing""#));
310 }
preparing for cloud work311}

This file's history is long; its oldest lines are credited to the oldest commit read.