Checking for updates in the desktop app answers in its settings window, with a restart beside a downloaded version.
Check for updates… in the Help menu or the tray opens the settings window at Updates, which checks and says where this copy stands: up to date and when it last heard back, a version downloaded with Restart now beside it, or that g1t.sh could not be reached. A notification alone was easy to miss, and nothing at all with the system's notifications off. A download the background finishes shows there too, and the Help menu gains Restart to update, as the tray has.
- Claude-Session:
- https://claude.ai/code/session_01MMRPpook51KPMQQRurb5kC
11 files+186−360/11 viewed
| 141 | 141 | select:hover { | |
| 142 | 142 | border-color: #3a3a44; | |
| 143 | 143 | } | |
| 144 | + | .setting > .buttons { | |
| 145 | + | flex-direction: row; | |
| 146 | + | flex: none; | |
| 147 | + | gap: 0.5rem; | |
| 148 | + | } | |
| 149 | + | button.primary { | |
| 150 | + | background: var(--accent); | |
| 151 | + | border-color: var(--accent); | |
| 152 | + | color: var(--accent-ink); | |
| 153 | + | } | |
| 154 | + | button.primary:hover { | |
| 155 | + | filter: brightness(1.06); | |
| 156 | + | } | |
| 157 | + | button[hidden] { | |
| 158 | + | display: none; | |
| 159 | + | } | |
| 144 | 160 | button:disabled { | |
| 145 | 161 | opacity: 0.6; | |
| 146 | 162 | } |
| 78 | 78 | <span><strong>Download updates by themselves</strong><span class="muted">Each one installs when you restart or quit g1t, never in the middle of something.</span></span> | |
| 79 | 79 | <input type="checkbox" role="switch" id="autoUpdate" /> | |
| 80 | 80 | </label> | |
| 81 | − | <div class="setting"> | |
| 82 | − | <span><strong id="version">g1t</strong><span class="muted" id="update-status"> </span></span> | |
| 83 | − | <button type="button" id="check">Check for updates</button> | |
| 81 | + | <div class="setting" id="updates-row"> | |
| 82 | + | <span><strong id="version">g1t</strong><span class="muted" id="update-status" aria-live="polite"> </span></span> | |
| 83 | + | <span class="buttons"> | |
| 84 | + | <button type="button" id="restart" class="primary" hidden>Restart now</button> | |
| 85 | + | <button type="button" id="check">Check for updates</button> | |
| 86 | + | </span> | |
| 84 | 87 | </div> | |
| 85 | 88 | </section> | |
| 86 | 89 |
| 53 | 53 | zoom.value = String(view.zoom); | |
| 54 | 54 | if (zoom.value === "") zoom.value = "1"; | |
| 55 | 55 | byId("version").textContent = "g1t " + view.version; | |
| 56 | − | if (view.updateReady) byId("update-status").textContent = "Version " + view.updateReady + " is downloaded. Restart g1t to use it."; | |
| 56 | + | if (!checking) updateStatus(view); | |
| 57 | 57 | byId("server").textContent = hostOf(view.server); | |
| 58 | 58 | } | |
| 59 | 59 | ||
| ⋯ | |||
| 84 | 84 | }); | |
| 85 | 85 | }); | |
| 86 | 86 | ||
| 87 | − | byId("check").addEventListener("click", function () { | |
| 87 | + | // Updates: where this copy stands, said in the window, so a check from | |
| 88 | + | // the menu or the tray is answered here and cannot be missed. | |
| 89 | + | var checking = false; | |
| 90 | + | ||
| 91 | + | function when(ms) { | |
| 92 | + | var date = new Date(ms); | |
| 93 | + | var today = new Date().toDateString() === date.toDateString(); | |
| 94 | + | var time = date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }); | |
| 95 | + | return today ? "today at " + time : date.toLocaleDateString([], { month: "short", day: "numeric" }) + " at " + time; | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | function ready(version) { | |
| 99 | + | byId("update-status").textContent = "Version " + version + " is downloaded and installs when g1t restarts."; | |
| 100 | + | byId("restart").hidden = false; | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | function updateStatus(view) { | |
| 104 | + | if (view.updateReady) return ready(view.updateReady); | |
| 105 | + | byId("restart").hidden = true; | |
| 106 | + | byId("update-status").textContent = view.checkedAt | |
| 107 | + | ? "Up to date. Last checked " + when(view.checkedAt) + "." | |
| 108 | + | : view.autoUpdate | |
| 109 | + | ? "Not checked yet since g1t started; it checks on its own shortly." | |
| 110 | + | : "Checking on its own is off."; | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | function check() { | |
| 114 | + | if (checking) return; | |
| 115 | + | checking = true; | |
| 88 | 116 | var button = byId("check"); | |
| 89 | 117 | var status = byId("update-status"); | |
| 90 | 118 | button.disabled = true; | |
| 91 | − | status.textContent = "Checking…"; | |
| 119 | + | status.textContent = "Checking, and downloading any new version…"; | |
| 92 | 120 | invoke("check_for_updates") | |
| 93 | 121 | .then(function (answer) { | |
| 94 | − | if (answer === "current") status.textContent = "You have the newest version."; | |
| 95 | − | else if (answer.indexOf("ready:") === 0) status.textContent = "Version " + answer.slice(6) + " is downloaded. Restart g1t to use it."; | |
| 96 | − | else status.textContent = answer; | |
| 122 | + | if (answer.indexOf("ready:") === 0) ready(answer.slice(6)); | |
| 123 | + | else status.textContent = "Up to date. Last checked " + when(Date.now()) + "."; | |
| 97 | 124 | }) | |
| 98 | 125 | .catch(function (error) { | |
| 99 | − | status.textContent = "Could not check: " + (typeof error === "string" ? error : "try again later."); | |
| 126 | + | var text = typeof error === "string" ? error : ""; | |
| 127 | + | status.textContent = /sending request|connect|dns|timed out/i.test(text) || !text | |
| 128 | + | ? "Could not reach g1t.sh to check. Check your connection and try again." | |
| 129 | + | : "Could not check: " + text; | |
| 100 | 130 | }) | |
| 101 | 131 | .then(function () { | |
| 132 | + | checking = false; | |
| 102 | 133 | button.disabled = false; | |
| 103 | 134 | }); | |
| 135 | + | } | |
| 136 | + | ||
| 137 | + | byId("check").addEventListener("click", check); | |
| 138 | + | byId("restart").addEventListener("click", function () { | |
| 139 | + | byId("restart").disabled = true; | |
| 140 | + | byId("update-status").textContent = "Installing…"; | |
| 141 | + | invoke("restart_to_update").catch(function (error) { | |
| 142 | + | byId("restart").disabled = false; | |
| 143 | + | fail(error); | |
| 144 | + | }); | |
| 104 | 145 | }); | |
| 105 | 146 | ||
| 147 | + | // The menu's or tray's "Check for updates…" with this window open, and a | |
| 148 | + | // download finishing in the background (src/settings.rs). | |
| 149 | + | var events = window.__TAURI__ && window.__TAURI__.event; | |
| 150 | + | if (events && typeof events.listen === "function") { | |
| 151 | + | events.listen("g1t-check-updates", check).catch(function () {}); | |
| 152 | + | events | |
| 153 | + | .listen("g1t-update-ready", function (event) { | |
| 154 | + | if (!checking && typeof event.payload === "string") ready(event.payload); | |
| 155 | + | }) | |
| 156 | + | .catch(function () {}); | |
| 157 | + | } | |
| 158 | + | ||
| 106 | 159 | byId("change-server").addEventListener("click", function () { | |
| 107 | 160 | invoke("change_server").catch(fail); | |
| 108 | 161 | }); | |
| ⋯ | |||
| 119 | 172 | .then(function (view) { | |
| 120 | 173 | platform(view.platform); | |
| 121 | 174 | render(view); | |
| 175 | + | // Opened by "Check for updates…" in a menu. | |
| 176 | + | if (new URLSearchParams(location.search).has("check")) { | |
| 177 | + | byId("updates-row").scrollIntoView({ block: "center" }); | |
| 178 | + | check(); | |
| 179 | + | } | |
| 122 | 180 | }) | |
| 123 | 181 | .catch(fail); | |
| 124 | 182 | })(); | |
| 131 | 131 | } | |
| 132 | 132 | ||
| 133 | 133 | /// Looks for an update now: `current`, `ready:<version>`, or an error. | |
| 134 | + | /// The settings window shows the answer itself; anyone else asking is also | |
| 135 | + | /// told with a notification. | |
| 134 | 136 | #[tauri::command] | |
| 135 | 137 | pub async fn check_for_updates(app: AppHandle, webview: Webview) -> Result<String, String> { | |
| 136 | 138 | from_either(&app, &webview)?; | |
| 137 | − | updates::check(&app, true).await | |
| 139 | + | updates::check(&app, webview.label() != settings::SETTINGS).await | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | /// The settings window's "Restart now": installs the downloaded update and starts it. | |
| 143 | + | #[tauri::command] | |
| 144 | + | pub fn restart_to_update(app: AppHandle, webview: Webview) -> Result<(), String> { | |
| 145 | + | from_settings(&webview)?; | |
| 146 | + | updates::restart(&app); | |
| 147 | + | Ok(()) | |
| 138 | 148 | } | |
| 139 | 149 | ||
| 140 | 150 | /// Opens the app's settings window: the site's account menu asks for it. |
| 32 | 32 | pub allowed: Mutex<Vec<String>>, | |
| 33 | 33 | /// An update downloaded and waiting to be installed, and its version. | |
| 34 | 34 | pub update: Mutex<Option<updates::Ready>>, | |
| 35 | + | /// When the updater last heard back from g1t.sh, in milliseconds since 1970. | |
| 36 | + | pub checked_at: Mutex<Option<u64>>, | |
| 35 | 37 | /// What the badge shows. | |
| 36 | 38 | pub waiting: Mutex<i64>, | |
| 37 | 39 | /// The app's own settings, as the settings window last left them. |
| 57 | 57 | bridge::save_settings, | |
| 58 | 58 | bridge::set_open_at_login, | |
| 59 | 59 | bridge::change_server, | |
| 60 | + | bridge::restart_to_update, | |
| 60 | 61 | ]) | |
| 61 | 62 | .on_menu_event(|app, event| menu::handle(app, event.id().as_ref())) | |
| 62 | 63 | .setup(|app| { |
| 68 | 68 | true, | |
| 69 | 69 | &[&PredefinedMenuItem::minimize(app, None)?, &PredefinedMenuItem::maximize(app, None)?], | |
| 70 | 70 | )?; | |
| 71 | − | let help = Submenu::with_items(app, "Help", true, &[&item("docs", "Desktop app guide", None)?, &item("status", "g1t.sh status", None)?, &separator()?, &check])?; | |
| 71 | + | let help = Submenu::with_items( | |
| 72 | + | app, | |
| 73 | + | "Help", | |
| 74 | + | true, | |
| 75 | + | &[&item("docs", "Desktop app guide", None)?, &item("status", "g1t.sh status", None)?, &separator()?, &check, &tray::restart_item(app)?], | |
| 76 | + | )?; | |
| 72 | 77 | // macOS keeps About in g1t's own menu; elsewhere it closes Help. | |
| 73 | 78 | #[cfg(not(target_os = "macos"))] | |
| 74 | 79 | { | |
| ⋯ | |||
| 136 | 141 | "open" => tray::show(app), | |
| 137 | 142 | "settings" => settings::open(app), | |
| 138 | 143 | "server" => tray::launcher(app, "change"), | |
| 139 | − | "check" => { | |
| 140 | − | let app = app.clone(); | |
| 141 | − | tauri::async_runtime::spawn(async move { | |
| 142 | − | let _ = updates::check(&app, true).await; | |
| 143 | − | }); | |
| 144 | − | } | |
| 144 | + | // The settings window asks, and shows the answer where it cannot be missed. | |
| 145 | + | "check" => settings::open_and_check(app), | |
| 145 | 146 | "restart" => updates::restart(app), | |
| 146 | 147 | "quit" => updates::quit(app), | |
| 147 | 148 | "close" => tray::close(app), | |
| 7 | 7 | //! through the autostart plugin, and read back from it. | |
| 8 | 8 | ||
| 9 | 9 | use serde::{Deserialize, Serialize}; | |
| 10 | − | use tauri::{AppHandle, Manager, WebviewUrl, WebviewWindowBuilder}; | |
| 10 | + | use tauri::{AppHandle, Emitter, Manager, WebviewUrl, WebviewWindowBuilder}; | |
| 11 | 11 | use tauri_plugin_autostart::ManagerExt; | |
| 12 | 12 | ||
| 13 | 13 | use crate::{WINDOW, config}; | |
| ⋯ | |||
| 69 | 69 | pub platform: String, | |
| 70 | 70 | pub server: String, | |
| 71 | 71 | pub update_ready: Option<String>, | |
| 72 | + | /// When the app last asked for updates and heard back, in milliseconds since 1970. | |
| 73 | + | pub checked_at: Option<u64>, | |
| 72 | 74 | } | |
| 73 | 75 | ||
| 74 | 76 | pub fn view(app: &AppHandle) -> View { | |
| ⋯ | |||
| 79 | 81 | platform: std::env::consts::OS.into(), | |
| 80 | 82 | server: config::server(app).to_string(), | |
| 81 | 83 | update_ready: crate::updates::ready(app), | |
| 84 | + | checked_at: crate::updates::checked_at(app), | |
| 82 | 85 | } | |
| 83 | 86 | } | |
| 84 | 87 | ||
| ⋯ | |||
| 125 | 128 | std::env::args().any(|arg| arg == AT_LOGIN) && config::settings(app).start_hidden | |
| 126 | 129 | } | |
| 127 | 130 | ||
| 131 | + | /// What the settings window listens for: check for updates now. | |
| 132 | + | pub const CHECK_EVENT: &str = "g1t-check-updates"; | |
| 133 | + | /// What it listens for: an update is downloaded, with its version. | |
| 134 | + | pub const READY_EVENT: &str = "g1t-update-ready"; | |
| 135 | + | ||
| 128 | 136 | /// Opens the settings window, or brings it forward. | |
| 129 | 137 | pub fn open(app: &AppHandle) { | |
| 138 | + | show(app, false); | |
| 139 | + | } | |
| 140 | + | ||
| 141 | + | /// "Check for updates…" in a menu: the settings window opens and checks, | |
| 142 | + | /// and says what it found, the download included, in its Updates section. | |
| 143 | + | pub fn open_and_check(app: &AppHandle) { | |
| 144 | + | show(app, true); | |
| 145 | + | } | |
| 146 | + | ||
| 147 | + | fn show(app: &AppHandle, check: bool) { | |
| 130 | 148 | if let Some(window) = app.get_webview_window(SETTINGS) { | |
| 131 | 149 | let _ = window.unminimize(); | |
| 132 | 150 | let _ = window.show(); | |
| 133 | 151 | let _ = window.set_focus(); | |
| 152 | + | if check { | |
| 153 | + | let _ = app.emit_to(SETTINGS, CHECK_EVENT, ()); | |
| 154 | + | } | |
| 134 | 155 | return; | |
| 135 | 156 | } | |
| 136 | − | let built = WebviewWindowBuilder::new(app, SETTINGS, WebviewUrl::App("settings.html".into())) | |
| 157 | + | // A window still loading has no listener yet: the address asks instead. | |
| 158 | + | let page = if check { "settings.html?check" } else { "settings.html" }; | |
| 159 | + | let built = WebviewWindowBuilder::new(app, SETTINGS, WebviewUrl::App(page.into())) | |
| 137 | 160 | .title("g1t settings") | |
| 138 | 161 | .inner_size(560.0, 680.0) | |
| 139 | 162 | .min_inner_size(440.0, 420.0) | |
| 4 | 4 | //! back. Quit is here too. The menu's rows are handled with the app | |
| 5 | 5 | //! menu's (menu.rs). | |
| 6 | 6 | ||
| 7 | − | use std::sync::OnceLock; | |
| 7 | + | use std::sync::Mutex; | |
| 8 | 8 | ||
| 9 | 9 | use tauri::menu::{Menu, MenuItem, PredefinedMenuItem}; | |
| 10 | 10 | use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}; | |
| ⋯ | |||
| 14 | 14 | ||
| 15 | 15 | pub const TRAY: &str = "g1t"; | |
| 16 | 16 | ||
| 17 | − | /// The menu row that restarts into a downloaded update: off until one is. | |
| 18 | − | static RESTART: OnceLock<MenuItem<Wry>> = OnceLock::new(); | |
| 17 | + | /// The menu rows that restart into a downloaded update, the tray's and | |
| 18 | + | /// the Help menu's: off until one is. | |
| 19 | + | static RESTART: Mutex<Vec<MenuItem<Wry>>> = Mutex::new(Vec::new()); | |
| 19 | 20 | ||
| 21 | + | /// A row that restarts into the update, kept so `update_ready` can turn it on. | |
| 22 | + | pub fn restart_item(app: &AppHandle) -> tauri::Result<MenuItem<Wry>> { | |
| 23 | + | let item = MenuItem::with_id(app, "restart", "Restart to finish updating", false, None::<&str>)?; | |
| 24 | + | RESTART.lock().unwrap().push(item.clone()); | |
| 25 | + | Ok(item) | |
| 26 | + | } | |
| 27 | + | ||
| 20 | 28 | pub fn install(app: &AppHandle) -> tauri::Result<()> { | |
| 21 | 29 | let open = MenuItem::with_id(app, "open", "Open g1t", true, None::<&str>)?; | |
| 22 | 30 | let check = MenuItem::with_id(app, "check", "Check for updates…", true, None::<&str>)?; | |
| 23 | − | let restart = MenuItem::with_id(app, "restart", "Restart to finish updating", false, None::<&str>)?; | |
| 31 | + | let restart = restart_item(app)?; | |
| 24 | 32 | let settings = MenuItem::with_id(app, "settings", "Settings…", true, None::<&str>)?; | |
| 25 | 33 | let server = MenuItem::with_id(app, "server", "Use another g1t…", true, None::<&str>)?; | |
| 26 | 34 | let quit = MenuItem::with_id(app, "quit", "Quit g1t", true, None::<&str>)?; | |
| ⋯ | |||
| 38 | 46 | &quit, | |
| 39 | 47 | ], | |
| 40 | 48 | )?; | |
| 41 | − | let _ = RESTART.set(restart); | |
| 42 | 49 | let mut tray = TrayIconBuilder::with_id(TRAY) | |
| 43 | 50 | .menu(&menu) | |
| 44 | 51 | .show_menu_on_left_click(false) | |
| ⋯ | |||
| 55 | 62 | Ok(()) | |
| 56 | 63 | } | |
| 57 | 64 | ||
| 58 | − | /// The update is downloaded: the menu says so, and offers the restart. | |
| 65 | + | /// The update is downloaded: the menus say so, and offer the restart. | |
| 59 | 66 | pub fn update_ready(version: &str) { | |
| 60 | − | if let Some(item) = RESTART.get() { | |
| 67 | + | for item in RESTART.lock().unwrap().iter() { | |
| 61 | 68 | let _ = item.set_text(format!("Restart to update to {version}")); | |
| 62 | 69 | let _ = item.set_enabled(true); | |
| 63 | 70 | } | |
| 8 | 8 | ||
| 9 | 9 | use std::time::Duration; | |
| 10 | 10 | ||
| 11 | − | use tauri::AppHandle; | |
| 12 | − | use tauri::Manager; | |
| 11 | + | use tauri::{AppHandle, Emitter, Manager}; | |
| 13 | 12 | use tauri_plugin_notification::NotificationExt; | |
| 14 | 13 | use tauri_plugin_updater::{Update, UpdaterExt}; | |
| 15 | 14 | ||
| 16 | − | use crate::{config, tray}; | |
| 15 | + | use crate::{config, settings, tray}; | |
| 17 | 16 | ||
| 18 | 17 | /// An update downloaded and checked, waiting to be installed. | |
| 19 | 18 | pub struct Ready { | |
| ⋯ | |||
| 39 | 38 | }); | |
| 40 | 39 | } | |
| 41 | 40 | ||
| 41 | + | /// When the updater last heard back, in milliseconds since 1970. | |
| 42 | + | pub fn checked_at(app: &AppHandle) -> Option<u64> { | |
| 43 | + | *app.state::<config::State>().checked_at.lock().unwrap() | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | fn heard_back(app: &AppHandle) { | |
| 47 | + | let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|since| since.as_millis() as u64).ok(); | |
| 48 | + | *app.state::<config::State>().checked_at.lock().unwrap() = now; | |
| 49 | + | } | |
| 50 | + | ||
| 42 | 51 | /// The version downloaded and waiting, if one is. | |
| 43 | 52 | pub fn ready(app: &AppHandle) -> Option<String> { | |
| 44 | 53 | app.state::<config::State>().update.lock().unwrap().as_ref().map(|ready| ready.version.clone()) | |
| ⋯ | |||
| 60 | 69 | } | |
| 61 | 70 | let updater = app.updater().map_err(|error| error.to_string())?; | |
| 62 | 71 | let update = match updater.check().await { | |
| 63 | − | Ok(Some(update)) => update, | |
| 72 | + | Ok(Some(update)) => { | |
| 73 | + | heard_back(app); | |
| 74 | + | update | |
| 75 | + | } | |
| 64 | 76 | Ok(None) => { | |
| 77 | + | heard_back(app); | |
| 65 | 78 | if asked { | |
| 66 | 79 | tell(app, "g1t is up to date", &format!("You have version {}.", env!("CARGO_PKG_VERSION"))); | |
| 67 | 80 | } | |
| ⋯ | |||
| 79 | 92 | Ok(bytes) => { | |
| 80 | 93 | *app.state::<config::State>().update.lock().unwrap() = Some(Ready { version: version.clone(), update, bytes }); | |
| 81 | 94 | tray::update_ready(&version); | |
| 95 | + | let _ = app.emit_to(settings::SETTINGS, settings::READY_EVENT, version.clone()); | |
| 82 | 96 | tell(app, "g1t is ready to update", &format!("Version {version} is downloaded. Restart g1t from the tray to use it.")); | |
| 83 | 97 | Ok(format!("ready:{version}")) | |
| 84 | 98 | } | |
| 87 | 87 | | **View** | **Reload** (Ctrl+R), **Actual size** (Ctrl+0), **Zoom in** (Ctrl+=), **Zoom out** (Ctrl+-), full screen | | |
| 88 | 88 | | **Go** | **Back** (Alt+Left, or ⌘[), **Forward** (Alt+Right, or ⌘]), **Home**, **Notifications** | | |
| 89 | 89 | | **Window** | Minimize and maximize | | |
| 90 | − | | **Help** | **Desktop app guide** and **g1t.sh status**, in your browser; **Check for updates…**; **About g1t** | | |
| 90 | + | | **Help** | **Desktop app guide** and **g1t.sh status**, in your browser; **Check for updates…**; **Restart to update**, once one is downloaded; **About g1t** | | |
| 91 | 91 | ||
| 92 | 92 | On Windows and Linux, **Show the menu bar** in the settings hides it; the | |
| 93 | 93 | tray's **Settings…** and your account menu still open the settings to bring | |
| ⋯ | |||
| 113 | 113 | | **Zoom** | 100% | How large g1t is drawn, from 67% to 200%. The View menu changes it too. | | |
| 114 | 114 | | **Download updates by themselves** | On | Off, the app never checks on its own; **Check for updates** still does. | | |
| 115 | 115 | ||
| 116 | − | The settings window also shows the app's version, with **Check for | |
| 117 | − | updates**, and the server it opens, with **Change…** to [use another | |
| 116 | + | The settings window also shows the app's version and whether it is up to | |
| 117 | + | date, with **Check for updates** (see [Updates](#updates)), and the server it opens, with **Change…** to [use another | |
| 118 | 118 | g1t](#your-own-g1t). | |
| 119 | 119 | ||
| 120 | 120 | ## Links | |
| ⋯ | |||
| 138 | 138 | shortly after it starts and every six hours, and downloads one in the | |
| 139 | 139 | background. It never installs under you: a notification and the tray's | |
| 140 | 140 | **Restart to update** say it is ready, and the restart, or quitting, | |
| 141 | − | installs it. **Check for updates…** in the tray or the Help menu asks now | |
| 142 | − | and tells you the answer. To stop the app checking on its own, turn off | |
| 141 | + | installs it. | |
| 142 | + | ||
| 143 | + | To see where your copy stands, choose **Check for updates…** in the Help | |
| 144 | + | menu or the tray, or **Check for updates** in [the settings](#settings). | |
| 145 | + | The menu and the tray open the settings window at its Updates section, | |
| 146 | + | which checks and says the answer there: | |
| 147 | + | ||
| 148 | + | - **Up to date. Last checked today at 10:42.** You have the newest version. | |
| 149 | + | - **Version 0.2.0 is downloaded and installs when g1t restarts.** A | |
| 150 | + | **Restart now** button beside it installs it and opens the new version, | |
| 151 | + | as **Restart to update** in the Help menu and the tray do. | |
| 152 | + | - **Could not reach g1t.sh to check.** Try again once you are online. | |
| 153 | + | ||
| 154 | + | A check finds and downloads a new version in one go, so the answer can | |
| 155 | + | take a moment on a slow connection. The automatic checks and yours are the | |
| 156 | + | same check: a version the background found is shown as downloaded when | |
| 157 | + | you look. To stop the app checking on its own, turn off | |
| 143 | 158 | **Download updates by themselves** in [the settings](#settings). | |
| 144 | 159 | ||
| 145 | 160 | Every release is signed with g1t's key, whose public half is built into | |