Skip to content
285 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 the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.54 /// Sandboxed, each kind of job on or off on its own: Linux jobs can
55 /// run while Windows jobs are stopped. All on unless someone stops one.
56 pub runner_capabilities: RunnerCapabilities,
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r957}
58
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.59/// Each kind of job the sandbox runs, on or off (`runnerCapabilities` in
60/// desktop.json: `{"linux": true, "windows": false}`).
61#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(default)]
63pub struct RunnerCapabilities {
64 pub linux: bool,
65 pub windows: bool,
66 pub macos: bool,
67}
68
69impl Default for RunnerCapabilities {
70 fn default() -> Self {
71 Self { linux: true, windows: true, macos: true }
72 }
73}
74
75impl RunnerCapabilities {
76 /// Whether a kind of job is on; one this app doesn't know is.
77 pub fn on(&self, id: &str) -> bool {
78 match id {
79 "linux" => self.linux,
80 "windows" => self.windows,
81 "macos" => self.macos,
82 _ => true,
83 }
84 }
85
86 /// Turns a kind of job on or off: false for a kind this app doesn't know.
87 pub fn set(&mut self, id: &str, on: bool) -> bool {
88 let kind = match id {
89 "linux" => &mut self.linux,
90 "windows" => &mut self.windows,
91 "macos" => &mut self.macos,
92 _ => return false,
93 };
94 *kind = on;
95 true
96 }
97}
98
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r999impl Default for Settings {
100 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.101 Self {
102 start_hidden: true,
103 close_to_tray: true,
104 notifications: true,
105 flash: true,
106 badge: true,
107 system_title_bar: false,
108 zoom: 1.0,
109 auto_update: true,
110 runner: true,
111 runner_backend: crate::runner::Backend::Sandboxed,
112 runner_workspace: None,
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.113 runner_capabilities: RunnerCapabilities::default(),
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 }
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9115 }
116}
117
118impl Settings {
119 /// A zoom the app offers, nearest to the one asked for.
120 pub fn clamp_zoom(zoom: f64) -> f64 {
121 ZOOMS.iter().copied().min_by(|a, b| (a - zoom).abs().total_cmp(&(b - zoom).abs())).unwrap_or(1.0)
122 }
123}
124
125/// What the settings window shows: the settings, and what the app and the
126/// system know.
127#[derive(Serialize)]
128#[serde(rename_all = "camelCase")]
129pub struct View {
130 #[serde(flatten)]
131 pub settings: Settings,
132 pub open_at_login: bool,
133 pub version: String,
134 pub platform: String,
135 pub server: String,
136 pub update_ready: Option<String>,
137 /// When the app last asked for updates and heard back, in milliseconds since 1970.
138 pub checked_at: Option<u64>,
139 /// The title bar setting differs from the one the windows have: a restart applies it.
140 pub title_bar_changed: bool,
141 /// Whether this window draws g1t's own title bar, which the page then draws (settings.html).
142 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.143 /// The runner's state, diagnosis and recent log.
144 pub runner_view: crate::runner::View,
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9145}
146
147pub fn view(app: &AppHandle) -> View {
148 View {
149 settings: config::settings(app),
150 open_at_login: app.autolaunch().is_enabled().unwrap_or(false),
151 version: env!("CARGO_PKG_VERSION").into(),
152 platform: std::env::consts::OS.into(),
153 server: config::server(app).to_string(),
154 update_ready: crate::updates::ready(app),
155 checked_at: crate::updates::checked_at(app),
156 title_bar_changed: crate::frame::started(app) != crate::frame::custom(app),
157 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.158 runner_view: crate::runner::view(app),
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9159 }
160}
161
162/// Keeps new settings and puts them to work at once.
163pub fn apply(app: &AppHandle, mut settings: Settings) -> Result<(), String> {
164 settings.zoom = Settings::clamp_zoom(settings.zoom);
165 let before = config::settings(app);
166 config::set_settings(app, settings.clone())?;
167 if let Some(site) = crate::frame::site(app).filter(|_| before.zoom != settings.zoom) {
168 let _ = site.set_zoom(settings.zoom);
169 }
170 if before.badge != settings.badge {
171 crate::badge::refresh(app);
172 }
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.173 let runner = |s: &Settings| (s.runner, s.runner_backend, s.runner_workspace.clone(), s.runner_capabilities.clone());
174 if runner(&before) != runner(&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.175 crate::runner::settings_changed(app);
176 }
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9177 Ok(())
178}
179
180/// Turns opening at sign-in on or off, with the system.
181pub fn open_at_login(app: &AppHandle, on: bool) -> Result<(), String> {
182 let autolaunch = app.autolaunch();
183 let result = if on { autolaunch.enable() } else { autolaunch.disable() };
184 result.map_err(|error| error.to_string())
185}
186
187/// Steps the zoom in (1), out (-1), or back to as it is (0).
188pub fn zoom(app: &AppHandle, step: i32) {
189 let mut settings = config::settings(app);
190 let at = ZOOMS.iter().position(|zoom| *zoom == Settings::clamp_zoom(settings.zoom)).unwrap_or(4);
191 settings.zoom = match step {
192 0 => 1.0,
193 1 => ZOOMS[(at + 1).min(ZOOMS.len() - 1)],
194 _ => ZOOMS[at.saturating_sub(1)],
195 };
196 let _ = apply(app, settings);
197}
198
199/// Whether this launch is the system's, at sign-in, and g1t should wait in the tray.
200pub fn starts_hidden(app: &AppHandle) -> bool {
201 std::env::args().any(|arg| arg == AT_LOGIN) && config::settings(app).start_hidden
202}
203
204/// What the settings window listens for: check for updates now.
205pub const CHECK_EVENT: &str = "g1t-check-updates";
206/// What it listens for: an update is downloaded, with its version.
207pub const READY_EVENT: &str = "g1t-update-ready";
208
209/// Opens the settings window, or brings it forward.
210pub fn open(app: &AppHandle) {
211 show(app, false);
212}
213
214/// "Check for updates…" in a menu: the settings window opens and checks,
215/// and says what it found, the download included, in its Updates section.
216pub fn open_and_check(app: &AppHandle) {
217 show(app, true);
218}
219
220fn show(app: &AppHandle, check: bool) {
221 if let Some(window) = app.get_webview_window(SETTINGS) {
222 let _ = window.unminimize();
223 let _ = window.show();
224 let _ = window.set_focus();
225 if check {
226 let _ = app.emit_to(SETTINGS, CHECK_EVENT, ());
227 }
228 return;
229 }
230 // A window still loading has no listener yet: the address asks instead.
231 let page = if check { "settings.html?check" } else { "settings.html" };
232 let custom = crate::frame::started(app);
233 let builder = WebviewWindowBuilder::new(app, SETTINGS, WebviewUrl::App(page.into()))
234 .title("g1t settings");
235 let built = crate::frame::dialog(builder, custom)
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.236 .inner_size(620.0, 720.0)
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9237 .min_inner_size(440.0, 420.0)
238 .maximizable(false)
239 .minimizable(false)
240 .center()
241 // The page is bundled with the app; its links go to the browser (settings.js).
242 .on_navigation(config::is_launcher)
243 .build();
244 match built {
245 Ok(window) => {
246 let _ = window.set_focus();
247 }
248 Err(error) => eprintln!("g1t: the settings window could not open: {error}"),
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::Settings;
255
256 #[test]
257 fn a_missing_setting_is_its_default() {
258 let settings: Settings = serde_json::from_str(r#"{"closeToTray":false}"#).unwrap();
259 assert!(!settings.close_to_tray);
260 assert!(settings.notifications);
261 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.262 // The runner is on, and sandboxed, for someone who never chose.
263 assert!(settings.runner);
264 assert_eq!(settings.runner_backend, crate::runner::Backend::Sandboxed);
265 let chosen: Settings = serde_json::from_str(r#"{"runner":false,"runnerBackend":"host","runnerWorkspace":"acme"}"#).unwrap();
266 assert!(!chosen.runner);
267 assert_eq!(chosen.runner_backend, crate::runner::Backend::Host);
268 assert_eq!(chosen.runner_workspace.as_deref(), Some("acme"));
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.269 // Every kind of job is on until someone stops one; a kind left out is on.
270 assert!(chosen.runner_capabilities.on("linux") && chosen.runner_capabilities.on("windows"));
271 let one_off: Settings = serde_json::from_str(r#"{"runnerCapabilities":{"windows":false}}"#).unwrap();
272 assert!(one_off.runner_capabilities.on("linux"));
273 assert!(!one_off.runner_capabilities.on("windows"));
274 let mut kinds = one_off.runner_capabilities.clone();
275 assert!(kinds.set("windows", true) && kinds.on("windows"));
276 assert!(!kinds.set("plan9", false));
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9277 }
278
279 #[test]
280 fn zoom_is_one_the_app_offers() {
281 assert_eq!(Settings::clamp_zoom(1.07), 1.1);
282 assert_eq!(Settings::clamp_zoom(9.0), 2.0);
283 assert_eq!(Settings::clamp_zoom(0.1), 0.67);
284 }
285}

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