Skip to content
232 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.

Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r91//! The app's own settings: how it starts, what closing the window does,
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.2//! how it tells the person something arrived, its title bar, zoom,
3//! updates, and the runner. They live in their own window (`launcher/settings.html`),
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r94//! opened from the menu, the tray, or the site's account menu, and are
5//! kept with the server in `desktop.json` (config.rs). Whether g1t opens
6//! when someone signs in to their computer is kept by the system itself,
7//! through the autostart plugin, and read back from it.
8
9use serde::{Deserialize, Serialize};
10use tauri::{AppHandle, Emitter, Manager, WebviewUrl, WebviewWindowBuilder};
11use tauri_plugin_autostart::ManagerExt;
12
13use crate::config;
14
15/// The settings window's label.
16pub const SETTINGS: &str = "settings";
17
18/// The argument the system starts g1t with at sign-in, so it can tell a
19/// start of its own from someone opening it.
20pub const AT_LOGIN: &str = "--at-login";
21
22/// The zoom levels the View menu steps through.
23pub const ZOOMS: &[f64] = &[0.67, 0.75, 0.8, 0.9, 1.0, 1.1, 1.25, 1.5, 1.75, 2.0];
24
25#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
26#[serde(default, rename_all = "camelCase")]
27pub struct Settings {
28 /// Opened at sign-in, g1t waits in the tray instead of opening its window.
29 pub start_hidden: bool,
30 /// Closing the window leaves g1t running in the tray, for notifications;
31 /// otherwise closing it quits.
32 pub close_to_tray: bool,
33 /// Notifications from the system, for what the site says needs the person.
34 pub notifications: bool,
35 /// The taskbar icon flashes, or the dock icon bounces, with a notification.
36 pub flash: bool,
37 /// What needs the person, counted on the icon.
38 pub badge: bool,
39 /// The system's title bar, and on Windows and Linux its menu bar, in
40 /// place of g1t's own (frame.rs). Takes effect when g1t restarts.
41 pub system_title_bar: bool,
42 /// The site's zoom, 1 for as it is.
43 pub zoom: f64,
44 /// Updates download by themselves; a person can still check from the menu.
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.45 /// The runner's own updates follow it too (runner/mod.rs).
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r946 pub auto_update: bool,
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.47 /// Runs the workspace's self-hosted jobs on this computer while g1t is
48 /// open (runner/mod.rs). On unless someone turns it off.
49 pub runner: bool,
50 /// How the runner runs: sandboxed unless someone chooses otherwise.
51 pub runner_backend: crate::runner::Backend,
52 /// The workspace it runs for; the one the person is in when it first registers.
53 pub runner_workspace: Option<String>,
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r954}
55
56impl Default for Settings {
57 fn default() -> Self {
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.58 Self {
59 start_hidden: true,
60 close_to_tray: true,
61 notifications: true,
62 flash: true,
63 badge: true,
64 system_title_bar: false,
65 zoom: 1.0,
66 auto_update: true,
67 runner: true,
68 runner_backend: crate::runner::Backend::Sandboxed,
69 runner_workspace: None,
70 }
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r971 }
72}
73
74impl Settings {
75 /// A zoom the app offers, nearest to the one asked for.
76 pub fn clamp_zoom(zoom: f64) -> f64 {
77 ZOOMS.iter().copied().min_by(|a, b| (a - zoom).abs().total_cmp(&(b - zoom).abs())).unwrap_or(1.0)
78 }
79}
80
81/// What the settings window shows: the settings, and what the app and the
82/// system know.
83#[derive(Serialize)]
84#[serde(rename_all = "camelCase")]
85pub struct View {
86 #[serde(flatten)]
87 pub settings: Settings,
88 pub open_at_login: bool,
89 pub version: String,
90 pub platform: String,
91 pub server: String,
92 pub update_ready: Option<String>,
93 /// When the app last asked for updates and heard back, in milliseconds since 1970.
94 pub checked_at: Option<u64>,
95 /// The title bar setting differs from the one the windows have: a restart applies it.
96 pub title_bar_changed: bool,
97 /// Whether this window draws g1t's own title bar, which the page then draws (settings.html).
98 pub custom_frame: bool,
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.99 /// The runner's state, diagnosis and recent log.
100 pub runner_view: crate::runner::View,
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9101}
102
103pub fn view(app: &AppHandle) -> View {
104 View {
105 settings: config::settings(app),
106 open_at_login: app.autolaunch().is_enabled().unwrap_or(false),
107 version: env!("CARGO_PKG_VERSION").into(),
108 platform: std::env::consts::OS.into(),
109 server: config::server(app).to_string(),
110 update_ready: crate::updates::ready(app),
111 checked_at: crate::updates::checked_at(app),
112 title_bar_changed: crate::frame::started(app) != crate::frame::custom(app),
113 custom_frame: crate::frame::started(app),
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.114 runner_view: crate::runner::view(app),
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9115 }
116}
117
118/// Keeps new settings and puts them to work at once.
119pub fn apply(app: &AppHandle, mut settings: Settings) -> Result<(), String> {
120 settings.zoom = Settings::clamp_zoom(settings.zoom);
121 let before = config::settings(app);
122 config::set_settings(app, settings.clone())?;
123 if let Some(site) = crate::frame::site(app).filter(|_| before.zoom != settings.zoom) {
124 let _ = site.set_zoom(settings.zoom);
125 }
126 if before.badge != settings.badge {
127 crate::badge::refresh(app);
128 }
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.129 if (before.runner, before.runner_backend, &before.runner_workspace) != (settings.runner, settings.runner_backend, &settings.runner_workspace) {
130 crate::runner::settings_changed(app);
131 }
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9132 Ok(())
133}
134
135/// Turns opening at sign-in on or off, with the system.
136pub fn open_at_login(app: &AppHandle, on: bool) -> Result<(), String> {
137 let autolaunch = app.autolaunch();
138 let result = if on { autolaunch.enable() } else { autolaunch.disable() };
139 result.map_err(|error| error.to_string())
140}
141
142/// Steps the zoom in (1), out (-1), or back to as it is (0).
143pub fn zoom(app: &AppHandle, step: i32) {
144 let mut settings = config::settings(app);
145 let at = ZOOMS.iter().position(|zoom| *zoom == Settings::clamp_zoom(settings.zoom)).unwrap_or(4);
146 settings.zoom = match step {
147 0 => 1.0,
148 1 => ZOOMS[(at + 1).min(ZOOMS.len() - 1)],
149 _ => ZOOMS[at.saturating_sub(1)],
150 };
151 let _ = apply(app, settings);
152}
153
154/// Whether this launch is the system's, at sign-in, and g1t should wait in the tray.
155pub fn starts_hidden(app: &AppHandle) -> bool {
156 std::env::args().any(|arg| arg == AT_LOGIN) && config::settings(app).start_hidden
157}
158
159/// What the settings window listens for: check for updates now.
160pub const CHECK_EVENT: &str = "g1t-check-updates";
161/// What it listens for: an update is downloaded, with its version.
162pub const READY_EVENT: &str = "g1t-update-ready";
163
164/// Opens the settings window, or brings it forward.
165pub fn open(app: &AppHandle) {
166 show(app, false);
167}
168
169/// "Check for updates…" in a menu: the settings window opens and checks,
170/// and says what it found, the download included, in its Updates section.
171pub fn open_and_check(app: &AppHandle) {
172 show(app, true);
173}
174
175fn show(app: &AppHandle, check: bool) {
176 if let Some(window) = app.get_webview_window(SETTINGS) {
177 let _ = window.unminimize();
178 let _ = window.show();
179 let _ = window.set_focus();
180 if check {
181 let _ = app.emit_to(SETTINGS, CHECK_EVENT, ());
182 }
183 return;
184 }
185 // A window still loading has no listener yet: the address asks instead.
186 let page = if check { "settings.html?check" } else { "settings.html" };
187 let custom = crate::frame::started(app);
188 let builder = WebviewWindowBuilder::new(app, SETTINGS, WebviewUrl::App(page.into()))
189 .title("g1t settings");
190 let built = crate::frame::dialog(builder, custom)
191 .inner_size(560.0, 680.0)
192 .min_inner_size(440.0, 420.0)
193 .maximizable(false)
194 .minimizable(false)
195 .center()
196 // The page is bundled with the app; its links go to the browser (settings.js).
197 .on_navigation(config::is_launcher)
198 .build();
199 match built {
200 Ok(window) => {
201 let _ = window.set_focus();
202 }
203 Err(error) => eprintln!("g1t: the settings window could not open: {error}"),
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::Settings;
210
211 #[test]
212 fn a_missing_setting_is_its_default() {
213 let settings: Settings = serde_json::from_str(r#"{"closeToTray":false}"#).unwrap();
214 assert!(!settings.close_to_tray);
215 assert!(settings.notifications);
216 assert_eq!(settings.zoom, 1.0);
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.217 // The runner is on, and sandboxed, for someone who never chose.
218 assert!(settings.runner);
219 assert_eq!(settings.runner_backend, crate::runner::Backend::Sandboxed);
220 let chosen: Settings = serde_json::from_str(r#"{"runner":false,"runnerBackend":"host","runnerWorkspace":"acme"}"#).unwrap();
221 assert!(!chosen.runner);
222 assert_eq!(chosen.runner_backend, crate::runner::Backend::Host);
223 assert_eq!(chosen.runner_workspace.as_deref(), Some("acme"));
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9224 }
225
226 #[test]
227 fn zoom_is_one_the_app_offers() {
228 assert_eq!(Settings::clamp_zoom(1.07), 1.1);
229 assert_eq!(Settings::clamp_zoom(9.0), 2.0);
230 assert_eq!(Settings::clamp_zoom(0.1), 0.67);
231 }
232}

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