- 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:
@@ -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(),
|
||||||
|
|||||||
@@ -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, ()) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user