diff --git a/CHANGELOG.md b/CHANGELOG.md index 3afbea3..2b7e2bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +- No more desktop-file cache: wldash scanned `~/.cache/wldash/desktop.json` (on NFS for shared homes, and stale after every package install) and needed a manual `--desktop-refresh`. Desktop files are now scanned on a background thread at every start, a few milliseconds over local `/usr/share`. `--desktop-refresh` is a no-op kept for key bindings; `simd-json` is gone. +- Calculator history and the Escape-saved prompt moved from `$XDG_CACHE_HOME/wldash` to `$XDG_STATE_HOME/wldash` (`~/.local/state`), where small roaming state belongs; old files are still read. +- Launcher: `Type=Link` entries open their URL with `xdg-open`; `Terminal=true` entries run inside `$TERMINAL -e` when that variable is set. + ## 0.4.0 (2026-10-03) First release of the project.uhhm.no fork, packaged for the `[uhhm]` Arch registry. Numbering: upstream's old code base was tagged up to v0.3.0 and the rewrite called itself 0.2.0; this fork continues above both so pacman sees a monotonic version. diff --git a/Cargo.lock b/Cargo.lock index fa6cc30..a189cdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,18 +8,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" -[[package]] -name = "ahash" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -188,15 +176,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" -dependencies = [ - "num-traits", -] - [[package]] name = "fontconfig" version = "0.8.0" @@ -225,38 +204,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getrandom" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "halfbrown" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5681137554ddff44396e5f149892c769d45301dd9aa19c51602a89ee214cb0ec" -dependencies = [ - "hashbrown 0.13.2", - "serde", -] - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.14.3" @@ -299,7 +246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown", ] [[package]] @@ -334,70 +281,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lexical-core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" -dependencies = [ - "lexical-parse-float", - "lexical-parse-integer", - "lexical-util", - "lexical-write-float", - "lexical-write-integer", -] - -[[package]] -name = "lexical-parse-float" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" -dependencies = [ - "lexical-parse-integer", - "lexical-util", - "static_assertions", -] - -[[package]] -name = "lexical-parse-integer" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" -dependencies = [ - "lexical-util", - "static_assertions", -] - -[[package]] -name = "lexical-util" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" -dependencies = [ - "static_assertions", -] - -[[package]] -name = "lexical-write-float" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" -dependencies = [ - "lexical-util", - "lexical-write-integer", - "static_assertions", -] - -[[package]] -name = "lexical-write-integer" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" -dependencies = [ - "lexical-util", - "static_assertions", -] - [[package]] name = "libc" version = "0.2.152" @@ -693,26 +576,6 @@ dependencies = [ "pest_derive", ] -[[package]] -name = "ref-cast" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - [[package]] name = "rusttype" version = "0.9.3" @@ -764,17 +627,6 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "serde_json" -version = "1.0.113" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" -dependencies = [ - "itoa", - "ryu", - "serde", -] - [[package]] name = "serde_yaml" version = "0.9.31" @@ -799,28 +651,6 @@ dependencies = [ "digest", ] -[[package]] -name = "simd-json" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2faf8f101b9bc484337a6a6b0409cf76c139f2fb70a9e3aee6b6774be7bfbf76" -dependencies = [ - "getrandom", - "halfbrown", - "lexical-core", - "ref-cast", - "serde", - "serde_json", - "simdutf8", - "value-trait", -] - -[[package]] -name = "simdutf8" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" - [[package]] name = "slotmap" version = "1.0.7" @@ -836,12 +666,6 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "syn" version = "1.0.109" @@ -930,18 +754,6 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" -[[package]] -name = "value-trait" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad8db98c1e677797df21ba03fca7d3bf9bec3ca38db930954e4fe6e1ea27eb4" -dependencies = [ - "float-cmp", - "halfbrown", - "itoa", - "ryu", -] - [[package]] name = "vec_map" version = "0.8.2" @@ -964,12 +776,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "wasm-bindgen" version = "0.2.90" @@ -1279,7 +1085,6 @@ dependencies = [ "rusttype", "serde", "serde_yaml", - "simd-json", "unicode-segmentation", "walkdir", "wayland-client", @@ -1316,23 +1121,3 @@ dependencies = [ "once_cell", "pkg-config", ] - -[[package]] -name = "zerocopy" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] diff --git a/Cargo.toml b/Cargo.toml index 4c0d486..3e9dac9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,5 +38,4 @@ libpulse-binding = "2.28" serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9" -simd-json = "0.13" diff --git a/README.md b/README.md index e5d33ca..5ed6758 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ per-host layouts apart on NFS-shared homes. To generate a new default configuration, run `wldash --write-default-config`. To configure, edit `$XDG_CONFIG_HOME/wldash/config.yml` (or if `XDG_CONFIG_HOME` is not set, `~/.config/wldash/config.yml`). If `/etc/wldash/config.yml` exists it takes precedence over the per-user file; `--config FILE` overrides both. -To regenerate the desktop file cache, run `wldash --desktop-refresh`. +Desktop files are scanned every time wldash starts (on a background thread), so there is no cache to refresh; `--desktop-refresh` is kept as a no-op for old key bindings. Calculator history and the prompt saved on Escape live in `$XDG_STATE_HOME/wldash` (`~/.local/state/wldash`); older installs' files under `~/.cache/wldash` are still read. For more info, look in `src/config.rs`. diff --git a/src/main.rs b/src/main.rs index b7f0a8a..2a4367a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,10 +23,7 @@ use event::{Event, Events}; use fonts::{FontMap, MaybeFontMap}; use keyboard::{KeyRepeatSource, RepeatMessage}; use state::State; -use utils::{ - desktop::{load_desktop_files, write_desktop_cache}, - xdg, -}; +use utils::xdg; use widgets::{Geometry, Widget}; use std::{ @@ -50,7 +47,8 @@ OPTIONS: else ~/.config/wldash/config.yml) --write-default-config [v1|v2|toplevel] generate and write a new config file (v2 by default) - --desktop-refresh refresh desktop file cache + --desktop-refresh no-op kept for old key bindings + (desktop files are scanned at start) " ); } @@ -68,9 +66,7 @@ fn ensure_dir_exists(path: &str) { fn create_required_directories() { let dir_config_wldash = format!("{}/wldash", xdg::config_folder()); - let dir_cache_wldash = format!("{}/wldash", xdg::cache_folder()); ensure_dir_exists(&dir_config_wldash); - ensure_dir_exists(&dir_cache_wldash); } /// Default config path. On a shared (e.g. NFS-roamed) home, a host-specific @@ -119,10 +115,7 @@ fn main() { std::process::exit(0); } Some(ref s) if s == "--desktop-refresh" => { - let v = load_desktop_files(); - if let Err(e) = write_desktop_cache(&v) { - die(format!("could not write desktop cache: {}", e)); - } + eprintln!("wldash: desktop files are scanned at every start; nothing to refresh"); std::process::exit(0); } Some(ref s) if s == "--help" => { diff --git a/src/utils/desktop.rs b/src/utils/desktop.rs index a336b52..c14fef8 100644 --- a/src/utils/desktop.rs +++ b/src/utils/desktop.rs @@ -2,20 +2,17 @@ use std::{ cmp::Ordering, collections::HashSet, error::Error, - fs::{File, read_to_string}, + fs::read_to_string, hash::{Hash, Hasher}, io::{Error as io_error, ErrorKind}, path::Path, }; -use serde::{self, Deserialize, Serialize}; -use simd_json; use walkdir::WalkDir; use crate::utils::{inish, xdg}; -#[derive(Serialize, Deserialize, Clone, Debug, Eq)] -#[serde(rename_all = "snake_case")] +#[derive(Clone, Debug, Eq)] pub struct Desktop { pub entry_type: String, pub name: String, @@ -83,20 +80,13 @@ impl Hash for Desktop { } } -pub fn load_desktop_cache() -> Result, Box> { - match File::open(format!("{}/wldash/desktop.json", xdg::cache_folder())) { - Ok(f) => simd_json::from_reader(f).map_err(|e| e.into()), - Err(e) => Err(e.into()), - } -} - -pub fn write_desktop_cache(v: &Vec) -> Result<(), Box> { - match File::create(format!("{}/wldash/desktop.json", xdg::cache_folder())) { - Ok(f) => simd_json::to_writer(f, v).map_err(|e| e.into()), - Err(e) => Err(e.into()), - } -} - +/// Scan every XDG applications directory for launchable entries. +/// +/// This used to be cached as JSON under XDG_CACHE_HOME and refreshed by +/// hand with `--desktop-refresh`. The scan is a few milliseconds over +/// local /usr/share and runs on a background thread, while the cache sat +/// on the (possibly NFS-mounted) home directory and went stale whenever a +/// package was installed. So: no cache. pub fn load_desktop_files() -> Vec { let dirs = xdg::data_folders(); diff --git a/src/utils/xdg.rs b/src/utils/xdg.rs index 9146912..df12f88 100644 --- a/src/utils/xdg.rs +++ b/src/utils/xdg.rs @@ -10,6 +10,18 @@ pub fn cache_folder() -> String { } } +/// XDG_STATE_HOME: small persistent state (calculator history, saved +/// prompt). Unlike a cache this is worth roaming with the home directory. +pub fn state_folder() -> String { + match env::var_os("XDG_STATE_HOME") { + Some(s) => s.into_string().unwrap(), + None => { + let home = env::var_os("HOME").unwrap().into_string().unwrap(); + format!("{}/.local/state", home) + } + } +} + pub fn config_folder() -> String { match env::var_os("XDG_CONFIG_HOME") { Some(s) => s.into_string().unwrap(), diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index fbed4a5..fbc5dff 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -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 { + 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 { + 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); } }