From 17076d6a79e4e9d69322c1e2af09107105b55f77 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 8 Oct 2026 15:00:03 -0400 Subject: [PATCH] DRY pass #2, batch 7, F21: the desktop's main window, clock and launcher dir (#5372) - lib.rs MAIN_WINDOW: the "main" window label that the reminder worker, the capture window and the shell each wrote, 5 sites in all. - lib.rs now_ms(): the epoch-milliseconds clock that the reminder worker and autosync's cycle stamp each computed. LastCycle.at_ms becomes i64, the same number on the wire. - integration.rs applications_dir(): the XDG launcher directory that the entry path and the desktop-database refresh each built. rustfmt --check is clean in the CI image. Co-Authored-By: Claude Opus 5.5 --- desktop/src-tauri/src/autosync.rs | 8 +++----- desktop/src-tauri/src/capture.rs | 2 +- desktop/src-tauri/src/integration.rs | 9 +++++++-- desktop/src-tauri/src/lib.rs | 16 ++++++++++++++-- desktop/src-tauri/src/reminders.rs | 14 ++++---------- 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/desktop/src-tauri/src/autosync.rs b/desktop/src-tauri/src/autosync.rs index 1ce3d63..becf6c4 100644 --- a/desktop/src-tauri/src/autosync.rs +++ b/desktop/src-tauri/src/autosync.rs @@ -46,7 +46,7 @@ use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender}; use std::sync::Mutex; -use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use std::time::{Duration, Instant}; use serde::Serialize; use tauri::{AppHandle, Emitter, Manager}; @@ -85,7 +85,7 @@ pub enum Trigger { #[derive(Debug, Clone, Serialize)] pub struct LastCycle { /// When it finished, in milliseconds since the epoch — the frontend formats it. - pub at_ms: u64, + pub at_ms: i64, pub trigger: Trigger, pub ok: bool, /// Why it failed, in words a person can act on. None when `ok`. @@ -299,9 +299,7 @@ fn worker(app: AppHandle, rx: Receiver) { } let cycle = LastCycle { - at_ms: SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_or(0, |d| d.as_millis() as u64), + at_ms: crate::now_ms(), trigger, ok: result.is_ok(), error: result.as_ref().err().cloned(), diff --git a/desktop/src-tauri/src/capture.rs b/desktop/src-tauri/src/capture.rs index f3cc9ab..4aa2b8b 100644 --- a/desktop/src-tauri/src/capture.rs +++ b/desktop/src-tauri/src/capture.rs @@ -214,7 +214,7 @@ pub fn capture_done(saved: bool, app: AppHandle) -> Result<(), String> { window.hide().map_err(|e| e.to_string())?; } if saved { - if let Some(main) = app.get_webview_window("main") { + if let Some(main) = app.get_webview_window(crate::MAIN_WINDOW) { // Failure here is cosmetic — the note is saved either way and the board // will show it on its next load — so it is logged, not raised. if let Err(e) = main.emit(CAPTURED_EVENT, ()) { diff --git a/desktop/src-tauri/src/integration.rs b/desktop/src-tauri/src/integration.rs index 06c2b75..a19d183 100644 --- a/desktop/src-tauri/src/integration.rs +++ b/desktop/src-tauri/src/integration.rs @@ -42,8 +42,13 @@ fn appimage_path() -> Option { std::env::var_os("APPIMAGE").map(PathBuf::from) } +/// Where per-user launchers live (XDG): `~/.local/share/applications`. +fn applications_dir(home: &Path) -> PathBuf { + home.join(".local/share/applications") +} + fn desktop_entry_path() -> Option { - home().map(|h| h.join(".local/share/applications").join(DESKTOP_ENTRY_NAME)) + home().map(|h| applications_dir(&h).join(DESKTOP_ENTRY_NAME)) } fn icon_path() -> Option { @@ -154,7 +159,7 @@ fn set_executable(_p: &Path) -> Result<(), String> { // Best-effort: refresh the desktop database so the entry appears promptly. The entry // still works on next login without it, so failures are ignored. fn refresh_desktop_database(home: &Path) { - let apps = home.join(".local/share/applications"); + let apps = applications_dir(home); let _ = std::process::Command::new("update-desktop-database") .arg(&apps) .output(); diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 6be1cc4..16078cd 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -54,6 +54,18 @@ pub(crate) fn display_version() -> &'static str { // this crate — the extraction changed where they live, not what they are. use inkwell_core::{local, sync}; +/// The app's own window, as `tauri.conf.json` names it. The capture window is the +/// other one (`capture::CAPTURE_WINDOW`). +pub(crate) const MAIN_WINDOW: &str = "main"; + +/// Milliseconds since the Unix epoch: the unit the page's clocks and the reminder +/// worker count in. 0 for a clock set before 1970. +pub(crate) fn now_ms() -> i64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map_or(0, |d| d.as_millis() as i64) +} + pub fn run() { use tauri_plugin_log::{Target, TargetKind}; @@ -156,7 +168,7 @@ pub fn run() { reminders::start(app.handle())?; // Coming back to the window is when someone is about to look, so it asks // for a cycle. Rate-limited in autosync, because focus flaps constantly. - if let Some(window) = app.get_webview_window("main") { + if let Some(window) = app.get_webview_window(MAIN_WINDOW) { let handle = app.handle().clone(); window.on_window_event(move |event| { if let tauri::WindowEvent::Focused(true) = event { @@ -243,7 +255,7 @@ pub fn run() { /// colour, is a cosmetic loss and must never stop the app from opening. fn paint_window_before_the_webview_does(app: &tauri::App) { use tauri::Manager; - let Some(window) = app.get_webview_window("main") else { + let Some(window) = app.get_webview_window(MAIN_WINDOW) else { return; }; // Unknown theme reads as light, matching the platform default we'd get anyway. diff --git a/desktop/src-tauri/src/reminders.rs b/desktop/src-tauri/src/reminders.rs index b40352b..6b3ff97 100644 --- a/desktop/src-tauri/src/reminders.rs +++ b/desktop/src-tauri/src/reminders.rs @@ -21,7 +21,7 @@ //! reports clicks only on mobile. The toast waiting in the window does. use std::collections::HashMap; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::time::Duration; use tauri::{AppHandle, Emitter, Manager}; use tauri_plugin_notification::NotificationExt; @@ -53,7 +53,7 @@ fn worker(app: AppHandle) { let mut announcer = Announcer::default(); loop { std::thread::sleep(TICK); - let now = now_ms(); + let now = crate::now_ms(); let due = match due(&app, now) { Ok(due) => due, Err(e) => { @@ -74,7 +74,7 @@ fn due(app: &AppHandle, now: i64) -> Result, String> { } fn announce(app: &AppHandle, reminder: &DueReminder) { - if let Err(e) = app.emit_to("main", DUE_EVENT, reminder) { + if let Err(e) = app.emit_to(crate::MAIN_WINDOW, DUE_EVENT, reminder) { log::warn!("could not tell the window about a due reminder: {e}"); } if main_window_in_front(app) { @@ -99,16 +99,10 @@ fn announce(app: &AppHandle, reminder: &DueReminder) { /// Whether someone is looking at the main window right now. Unknown counts as no: /// a duplicate alert is a smaller failure than a missed one. fn main_window_in_front(app: &AppHandle) -> bool { - app.get_webview_window("main") + app.get_webview_window(crate::MAIN_WINDOW) .is_some_and(|w| w.is_focused().unwrap_or(false) && !w.is_minimized().unwrap_or(true)) } -fn now_ms() -> i64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_or(0, |d| d.as_millis() as i64) -} - /// Which occurrences have been announced. Keyed by note id, holding the `remind_at` /// announced: completing or snoozing a reminder changes that value, so the next /// occurrence is new to it.