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 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 15:00:03 -04:00
co-authored by Claude Opus 5.5
parent 00dc8840cc
commit 17076d6a79
5 changed files with 29 additions and 20 deletions
+3 -5
View File
@@ -46,7 +46,7 @@
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender}; use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender};
use std::sync::Mutex; use std::sync::Mutex;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use std::time::{Duration, Instant};
use serde::Serialize; use serde::Serialize;
use tauri::{AppHandle, Emitter, Manager}; use tauri::{AppHandle, Emitter, Manager};
@@ -85,7 +85,7 @@ pub enum Trigger {
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct LastCycle { pub struct LastCycle {
/// When it finished, in milliseconds since the epoch — the frontend formats it. /// When it finished, in milliseconds since the epoch — the frontend formats it.
pub at_ms: u64, pub at_ms: i64,
pub trigger: Trigger, pub trigger: Trigger,
pub ok: bool, pub ok: bool,
/// Why it failed, in words a person can act on. None when `ok`. /// Why it failed, in words a person can act on. None when `ok`.
@@ -299,9 +299,7 @@ fn worker(app: AppHandle, rx: Receiver<Request>) {
} }
let cycle = LastCycle { let cycle = LastCycle {
at_ms: SystemTime::now() at_ms: crate::now_ms(),
.duration_since(UNIX_EPOCH)
.map_or(0, |d| d.as_millis() as u64),
trigger, trigger,
ok: result.is_ok(), ok: result.is_ok(),
error: result.as_ref().err().cloned(), error: result.as_ref().err().cloned(),
+1 -1
View File
@@ -214,7 +214,7 @@ pub fn capture_done(saved: bool, app: AppHandle) -> Result<(), String> {
window.hide().map_err(|e| e.to_string())?; window.hide().map_err(|e| e.to_string())?;
} }
if saved { 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 // 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. // will show it on its next load — so it is logged, not raised.
if let Err(e) = main.emit(CAPTURED_EVENT, ()) { if let Err(e) = main.emit(CAPTURED_EVENT, ()) {
+7 -2
View File
@@ -42,8 +42,13 @@ fn appimage_path() -> Option<PathBuf> {
std::env::var_os("APPIMAGE").map(PathBuf::from) 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<PathBuf> { fn desktop_entry_path() -> Option<PathBuf> {
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<PathBuf> { fn icon_path() -> Option<PathBuf> {
@@ -154,7 +159,7 @@ fn set_executable(_p: &Path) -> Result<(), String> {
// Best-effort: refresh the desktop database so the entry appears promptly. The entry // Best-effort: refresh the desktop database so the entry appears promptly. The entry
// still works on next login without it, so failures are ignored. // still works on next login without it, so failures are ignored.
fn refresh_desktop_database(home: &Path) { 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") let _ = std::process::Command::new("update-desktop-database")
.arg(&apps) .arg(&apps)
.output(); .output();
+14 -2
View File
@@ -54,6 +54,18 @@ pub(crate) fn display_version() -> &'static str {
// this crate — the extraction changed where they live, not what they are. // this crate — the extraction changed where they live, not what they are.
use inkwell_core::{local, sync}; 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() { pub fn run() {
use tauri_plugin_log::{Target, TargetKind}; use tauri_plugin_log::{Target, TargetKind};
@@ -156,7 +168,7 @@ pub fn run() {
reminders::start(app.handle())?; reminders::start(app.handle())?;
// Coming back to the window is when someone is about to look, so it asks // 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. // 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(); let handle = app.handle().clone();
window.on_window_event(move |event| { window.on_window_event(move |event| {
if let tauri::WindowEvent::Focused(true) = 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. /// colour, is a cosmetic loss and must never stop the app from opening.
fn paint_window_before_the_webview_does(app: &tauri::App) { fn paint_window_before_the_webview_does(app: &tauri::App) {
use tauri::Manager; use tauri::Manager;
let Some(window) = app.get_webview_window("main") else { let Some(window) = app.get_webview_window(MAIN_WINDOW) else {
return; return;
}; };
// Unknown theme reads as light, matching the platform default we'd get anyway. // Unknown theme reads as light, matching the platform default we'd get anyway.
+4 -10
View File
@@ -21,7 +21,7 @@
//! reports clicks only on mobile. The toast waiting in the window does. //! reports clicks only on mobile. The toast waiting in the window does.
use std::collections::HashMap; use std::collections::HashMap;
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::Duration;
use tauri::{AppHandle, Emitter, Manager}; use tauri::{AppHandle, Emitter, Manager};
use tauri_plugin_notification::NotificationExt; use tauri_plugin_notification::NotificationExt;
@@ -53,7 +53,7 @@ fn worker(app: AppHandle) {
let mut announcer = Announcer::default(); let mut announcer = Announcer::default();
loop { loop {
std::thread::sleep(TICK); std::thread::sleep(TICK);
let now = now_ms(); let now = crate::now_ms();
let due = match due(&app, now) { let due = match due(&app, now) {
Ok(due) => due, Ok(due) => due,
Err(e) => { Err(e) => {
@@ -74,7 +74,7 @@ fn due(app: &AppHandle, now: i64) -> Result<Vec<DueReminder>, String> {
} }
fn announce(app: &AppHandle, reminder: &DueReminder) { 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}"); log::warn!("could not tell the window about a due reminder: {e}");
} }
if main_window_in_front(app) { 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: /// 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. /// a duplicate alert is a smaller failure than a missed one.
fn main_window_in_front(app: &AppHandle) -> bool { 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)) .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` /// 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 /// announced: completing or snoozing a reminder changes that value, so the next
/// occurrence is new to it. /// occurrence is new to it.