Skip to content

Commit 86e5211

Browse files
var-ggclaude
andcommitted
feat(settings): expose update_check + open_settings_file via commands
Promotes the last hand-edit-only knob (update_check) into the Settings GUI and gives the "reveal settings.json" affordance its own Tauri command so it can live inside the Settings window instead of cluttering the tray with a second "Settings*" entry. Backend changes: - AppSettings now carries `update_check` (Enabled/Manual/Disabled) and `updater_available` (false for Scoop/MSIX installs so the frontend can hide the section). - New `set_update_check` command: persists + calls `refresh_indicator` so the tray dot reacts immediately. - New `open_settings_file` command: same logic as the tray's previous private helper, now reachable from the frontend. - `refresh_indicator` honours `update_check == Disabled` (returns no version, so the dot + "Update available" item disappear). - `build_menu` drops the "Open settings file…" entry and additionally omits "Check for updates" when update_check is Disabled — matching the enum's "no tray affordances" doc. Tray menu becomes: [Update available?] Check for updates | Settings… | Reset panel position | Quit gitwink. One Settings entry, no duplicate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 23ac1c0 commit 86e5211

5 files changed

Lines changed: 75 additions & 41 deletions

File tree

src-tauri/src/commands.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,15 @@ pub struct AppSettings {
768768
/// When true, the panel is "pinned" — no blur auto-hide, shows in
769769
/// the taskbar, not always-on-top. False = tray-glance default.
770770
pub panel_pinned: bool,
771+
/// Self-update behaviour. Serialized as "enabled" / "manual" /
772+
/// "disabled" via the enum's lowercase rename. Meaningful only when
773+
/// `updater_available` is true.
774+
pub update_check: settings::UpdateCheckMode,
775+
/// False for Scoop and Microsoft Store (MSIX) installs — those
776+
/// channels manage their own updates, so the Updates section in
777+
/// the Settings window is hidden and the `update_check` knob is
778+
/// inert. The frontend uses this flag to gate the UI.
779+
pub updater_available: bool,
771780
}
772781

773782
#[tauri::command]
@@ -781,6 +790,8 @@ pub fn get_settings(app: AppHandle) -> AppSettings {
781790
.filter(|h| !h.trim().is_empty())
782791
.unwrap_or_else(|| settings::DEFAULT_PANEL_HOTKEY.to_string()),
783792
panel_pinned: s.panel_pinned.unwrap_or(false),
793+
update_check: s.update_check,
794+
updater_available: !update::installed_via_scoop() && !update::installed_via_msix(),
784795
}
785796
}
786797

@@ -881,6 +892,31 @@ pub async fn open_settings_window(app: AppHandle) {
881892
});
882893
}
883894

895+
/// Persist the self-update mode and refresh the tray dot immediately
896+
/// (Disabled hides the indicator + tray "Check for updates" item via
897+
/// build_menu; refresh_indicator drives the rebuild).
898+
#[tauri::command]
899+
pub fn set_update_check(app: AppHandle, mode: settings::UpdateCheckMode) {
900+
settings::save_update_check_mode(&app, mode);
901+
update::refresh_indicator(&app);
902+
}
903+
904+
/// Reveal `settings.json` in the user's default editor (or the OS file
905+
/// handler for `.json`). Exposed for the "Open settings.json" link in
906+
/// the Settings window's footer — the tray menu used to host this but
907+
/// it was demoted to keep the tray to one Settings entry.
908+
#[tauri::command]
909+
pub fn open_settings_file(app: AppHandle) -> Result<(), String> {
910+
use tauri_plugin_opener::OpenerExt;
911+
// ensure_path writes a default file if missing so the editor never
912+
// opens to a "file not found" dialog on a fresh install.
913+
let path = settings::ensure_path(&app).map_err(|e| e.to_string())?;
914+
let path_str = path.to_string_lossy().into_owned();
915+
app.opener()
916+
.open_path(&path_str, None::<&str>)
917+
.map_err(|e| e.to_string())
918+
}
919+
884920
fn unix_now() -> i64 {
885921
SystemTime::now()
886922
.duration_since(UNIX_EPOCH)

src-tauri/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,8 @@ pub fn run() {
357357
commands::set_panel_hotkey,
358358
commands::set_panel_pinned,
359359
commands::open_settings_window,
360+
commands::set_update_check,
361+
commands::open_settings_file,
360362
])
361363
.run(tauri::generate_context!())
362364
.expect("error while running tauri application");

src-tauri/src/settings.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,14 @@ pub fn save_panel_pinned(app: &AppHandle, pinned: bool) {
237237
eprintln!("settings: failed to persist panel_pinned: {e:#}");
238238
}
239239
}
240+
241+
/// Persist the self-update mode (`Enabled` / `Manual` / `Disabled`).
242+
/// Refreshing the tray indicator + menu is the caller's job — see
243+
/// `set_update_check` in commands.rs.
244+
pub fn save_update_check_mode(app: &AppHandle, mode: UpdateCheckMode) {
245+
let mut s = load(app);
246+
s.update_check = mode;
247+
if let Err(e) = save(app, &s) {
248+
eprintln!("settings: failed to persist update_check: {e:#}");
249+
}
250+
}

src-tauri/src/tray.rs

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use tauri::{
44
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
55
App, AppHandle, Wry,
66
};
7-
use tauri_plugin_opener::OpenerExt;
87

9-
use crate::{settings, update, window};
8+
use crate::settings::{self, UpdateCheckMode};
9+
use crate::{update, window};
1010

1111
const TRAY_ID: &str = "main";
1212

@@ -54,6 +54,11 @@ pub fn setup(app: &App) -> tauri::Result<()> {
5454
/// "Update available" item is prepended above a separator. The menu is
5555
/// rebuilt wholesale (rather than toggling item visibility) whenever the
5656
/// update state changes — see `set_update_indicator`.
57+
///
58+
/// The "Open settings file…" entry used to live here next to "Settings…"
59+
/// but was demoted to a footer link inside the Settings window itself —
60+
/// two "Settings*" items in a small tray menu were redundant. The same
61+
/// affordance is still reachable: tray → Settings… → "Open settings.json".
5762
fn build_menu(app: &AppHandle, update_version: Option<&str>) -> tauri::Result<Menu<Wry>> {
5863
let reset = MenuItem::with_id(
5964
app,
@@ -62,22 +67,20 @@ fn build_menu(app: &AppHandle, update_version: Option<&str>) -> tauri::Result<Me
6267
true,
6368
None::<&str>,
6469
)?;
65-
let settings = MenuItem::with_id(app, "settings", "Settings…", true, None::<&str>)?;
66-
let open_settings = MenuItem::with_id(
67-
app,
68-
"open_settings",
69-
"Open settings file…",
70-
true,
71-
None::<&str>,
72-
)?;
70+
let settings_item =
71+
MenuItem::with_id(app, "settings", "Settings…", true, None::<&str>)?;
7372
let quit = MenuItem::with_id(app, "quit", "Quit gitwink", true, None::<&str>)?;
7473
let sep = PredefinedMenuItem::separator(app)?;
7574

76-
// Microsoft Store installs update via the Store itself — gitwink shows
77-
// no in-app updater UI. Scoop and direct installs keep the
78-
// "Check for updates" item.
79-
if update::installed_via_msix() {
80-
return Menu::with_items(app, &[&settings, &reset, &open_settings, &sep, &quit]);
75+
// Microsoft Store installs update via the Store itself — gitwink
76+
// shows no in-app updater UI. Same goes for the Disabled mode the
77+
// user picked in Settings ("no tray affordances" per UpdateCheckMode
78+
// doc). Scoop installs keep the item since manual_check still
79+
// surfaces the modal with the `scoop update` hint.
80+
let hide_updater =
81+
update::installed_via_msix() || settings::load(app).update_check == UpdateCheckMode::Disabled;
82+
if hide_updater {
83+
return Menu::with_items(app, &[&settings_item, &reset, &sep, &quit]);
8184
}
8285

8386
let check =
@@ -98,17 +101,14 @@ fn build_menu(app: &AppHandle, update_version: Option<&str>) -> tauri::Result<Me
98101
&update_item,
99102
&sep_top,
100103
&check,
101-
&settings,
104+
&settings_item,
102105
&reset,
103-
&open_settings,
104106
&sep,
105107
&quit,
106108
],
107109
)
108110
}
109-
None => {
110-
Menu::with_items(app, &[&check, &settings, &reset, &open_settings, &sep, &quit])
111-
}
111+
None => Menu::with_items(app, &[&check, &settings_item, &reset, &sep, &quit]),
112112
}
113113
}
114114

@@ -117,7 +117,6 @@ fn handle_menu_event(app: &AppHandle, event: MenuEvent) {
117117
"quit" => app.exit(0),
118118
"reset_position" => settings::clear_panel_position(app),
119119
"settings" => window::open_settings(app),
120-
"open_settings" => open_settings_file(app),
121120
"check_updates" => update::manual_check(app),
122121
"update_available" => update::open_modal(app),
123122
_ => {}
@@ -196,20 +195,3 @@ fn with_dot(base: &Image<'_>) -> Image<'static> {
196195
Image::new_owned(rgba, w, h)
197196
}
198197

199-
/// Reveal settings.json in the user's default editor (or the OS file
200-
/// handler for `.json`). We `ensure_path` first so the file always exists
201-
/// when the editor opens — otherwise the user would land on a blank
202-
/// "file not found" dialog the first time they try this.
203-
fn open_settings_file(app: &AppHandle) {
204-
let path = match settings::ensure_path(app) {
205-
Ok(p) => p,
206-
Err(e) => {
207-
eprintln!("gitwink: failed to ensure settings file: {e:#}");
208-
return;
209-
}
210-
};
211-
let path_str = path.to_string_lossy().into_owned();
212-
if let Err(e) = app.opener().open_path(&path_str, None::<&str>) {
213-
eprintln!("gitwink: failed to open settings file {path_str:?}: {e}");
214-
}
215-
}

src-tauri/src/update.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,14 @@ async fn run_check(app: &AppHandle, manual: bool) -> anyhow::Result<()> {
143143
}
144144

145145
/// Recompute the tray dot + menu item from current state + settings.
146-
/// Shown iff an update exists, isn't the skipped version, and isn't
147-
/// snoozed.
146+
/// Shown iff an update exists, isn't the skipped version, isn't
147+
/// snoozed, and the user hasn't switched update_check to Disabled
148+
/// (Disabled means "no tray affordances", per the enum doc).
148149
pub fn refresh_indicator(app: &AppHandle) {
149150
let s = settings::load(app);
150-
let version = {
151+
let version = if s.update_check == UpdateCheckMode::Disabled {
152+
None
153+
} else {
151154
let state = app.state::<UpdateState>();
152155
let slot = state.available.lock().unwrap();
153156
slot.as_ref().and_then(|u| {

0 commit comments

Comments
 (0)