launcher: scan desktop files directly, keep state in XDG_STATE_HOME
The desktop-entry cache lived in $XDG_CACHE_HOME/wldash/desktop.json. On an NFS-shared home that is a remote read and write on every start (unless the session happens to export a machine-local XDG_CACHE_HOME), and it went stale whenever a package was installed until someone ran --desktop-refresh. Scanning the applications directories takes a few milliseconds over local /usr/share and already happened on a background thread, so the cache bought nothing: drop it, along with simd-json and the serde derives on Desktop. --desktop-refresh stays as a no-op so old key bindings keep working. Calculator history and the prompt saved on Escape are state, not cache: they move to $XDG_STATE_HOME/wldash (~/.local/state), written only on Enter/Escape, and the old cache location is still read as a fallback. With the serde derives gone, two Desktop fields were unused; use them: Type=Link entries now open their URL with xdg-open, and Terminal=true entries run under $TERMINAL -e when it is set. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
03a3c681b0
commit
9953865128
+49
-24
@@ -16,7 +16,7 @@ use crate::{
|
||||
fonts::FontMap,
|
||||
keyboard::{KeyEvent, keysyms},
|
||||
utils::{
|
||||
desktop::{Desktop, load_desktop_cache, load_desktop_files, write_desktop_cache},
|
||||
desktop::{Desktop, load_desktop_files},
|
||||
xdg,
|
||||
},
|
||||
widgets::{Geometry, Widget},
|
||||
@@ -225,6 +225,29 @@ fn strip_field_codes(exec: &str) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
/// Path of a small persistent state file (calc history, saved prompt)
|
||||
/// under $XDG_STATE_HOME/wldash.
|
||||
fn state_path(name: &str) -> String {
|
||||
format!("{}/wldash/{}", xdg::state_folder(), name)
|
||||
}
|
||||
|
||||
/// Read a state file, falling back to where older versions kept it
|
||||
/// ($XDG_CACHE_HOME/wldash) so nothing is lost on upgrade.
|
||||
fn read_state(name: &str) -> Option<String> {
|
||||
read_to_string(state_path(name))
|
||||
.or_else(|_| read_to_string(format!("{}/wldash/{}", xdg::cache_folder(), name)))
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Create (truncate) a state file, making the directory on demand.
|
||||
fn create_state(name: &str) -> Option<File> {
|
||||
let path = state_path(name);
|
||||
if let Some(dir) = std::path::Path::new(&path).parent() {
|
||||
let _ = std::fs::create_dir_all(dir);
|
||||
}
|
||||
File::create(path).ok()
|
||||
}
|
||||
|
||||
trait InterfaceWidget {
|
||||
/// Number of selectable rows (used to clamp the selection).
|
||||
fn rows(&self) -> usize;
|
||||
@@ -253,16 +276,8 @@ impl Launcher {
|
||||
thread::Builder::new()
|
||||
.name("desktopini".to_string())
|
||||
.spawn(move || {
|
||||
let mut options = options.lock().unwrap();
|
||||
*options = match load_desktop_cache() {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
let v = load_desktop_files();
|
||||
write_desktop_cache(&v).unwrap();
|
||||
v
|
||||
}
|
||||
};
|
||||
drop(options);
|
||||
let loaded = load_desktop_files();
|
||||
*options.lock().unwrap() = loaded;
|
||||
let mut events = events.lock().unwrap();
|
||||
events.add_event(Event::LauncherUpdate);
|
||||
})
|
||||
@@ -306,9 +321,19 @@ impl InterfaceWidget for Launcher {
|
||||
fn trigger(&mut self, intf: &mut InnerInterface) {
|
||||
if self.matches.len() > intf.selection {
|
||||
let d = &self.matches[intf.selection];
|
||||
if let Some(exec) = &d.exec {
|
||||
self.exec(&strip_field_codes(exec));
|
||||
}
|
||||
let cmd = match (&d.exec, &d.url) {
|
||||
(Some(exec), _) => strip_field_codes(exec),
|
||||
// Type=Link entries have no Exec; open the URL instead.
|
||||
(None, Some(url)) => format!("xdg-open '{}'", url.replace('\'', "'\\''")),
|
||||
(None, None) => return,
|
||||
};
|
||||
// Terminal=true: run inside the user's terminal when one is
|
||||
// configured, otherwise as before.
|
||||
let cmd = match (d.term, std::env::var("TERMINAL")) {
|
||||
(true, Ok(term)) if !term.is_empty() => format!("{} -e {}", term, cmd),
|
||||
_ => cmd,
|
||||
};
|
||||
self.exec(&cmd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,9 +527,9 @@ impl Calc {
|
||||
thread::Builder::new()
|
||||
.name("calc".to_string())
|
||||
.spawn(move || {
|
||||
let s = match read_to_string(format!("{}/wldash/calc", xdg::cache_folder())) {
|
||||
Ok(s) => s,
|
||||
_ => return,
|
||||
let s = match read_state("calc") {
|
||||
Some(s) => s,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut old = old_local.lock().unwrap();
|
||||
@@ -524,9 +549,9 @@ impl Calc {
|
||||
}
|
||||
|
||||
fn sync(&self) {
|
||||
let mut f = match File::create(format!("{}/wldash/calc", xdg::cache_folder())) {
|
||||
Ok(f) => f,
|
||||
_ => return,
|
||||
let mut f = match create_state("calc") {
|
||||
Some(f) => f,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let old = self.old.lock().unwrap();
|
||||
@@ -713,9 +738,9 @@ impl Interface {
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
if let Ok(mut f) = File::create(format!("{}/wldash/prompt", xdg::cache_folder())) {
|
||||
write!(f, "{}", self.inner.prompt.input).unwrap();
|
||||
f.sync_data().unwrap();
|
||||
if let Some(mut f) = create_state("prompt") {
|
||||
let _ = write!(f, "{}", self.inner.prompt.input);
|
||||
let _ = f.sync_data();
|
||||
}
|
||||
std::process::exit(0);
|
||||
}
|
||||
@@ -744,7 +769,7 @@ impl Interface {
|
||||
widget.update(&self.inner);
|
||||
}
|
||||
keysyms::KEY_r if event.modifiers.ctrl => {
|
||||
if let Ok(s) = read_to_string(format!("{}/wldash/prompt", xdg::cache_folder())) {
|
||||
if let Some(s) = read_state("prompt") {
|
||||
self.inner.prompt.set(&s);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user