diff --git a/Cargo.lock b/Cargo.lock index cce3da8..1967632 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,6 +117,17 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -138,6 +149,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.0" @@ -176,7 +193,7 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae50b5510d86cf96ac2370e66d8dc960882f3df179d6a5a1e52bd94a1416c0f7" dependencies = [ - "bitflags", + "bitflags 2.9.0", "cairo-sys-rs", "glib", "libc", @@ -199,7 +216,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags", + "bitflags 2.9.0", "log", "polling", "rustix", @@ -213,7 +230,7 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10929724661d1c43856fd87c7a127ae944ec55579134fb485e4136fb6a46fdcb" dependencies = [ - "bitflags", + "bitflags 2.9.0", "polling", "rustix", "slab", @@ -257,6 +274,22 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex 0.2.4", + "indexmap 1.9.3", + "strsim 0.10.0", + "termcolor", + "textwrap", + "yaml-rust", +] + [[package]] name = "clap" version = "4.5.35" @@ -275,8 +308,8 @@ checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" dependencies = [ "anstream", "anstyle", - "clap_lex", - "strsim", + "clap_lex 0.7.4", + "strsim 0.11.1", ] [[package]] @@ -291,6 +324,15 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "clap_lex" version = "0.7.4" @@ -462,6 +504,16 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "freedesktop-file-parser" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6059d3997cc694ec3e9a378db855866233ef7edfeafd85afcb2239fd130e6e6b" +dependencies = [ + "thiserror 2.0.12", + "xdgkit", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -655,7 +707,7 @@ version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707b819af8059ee5395a2de9f2317d87a53dbad8846a2f089f0bb44703f37686" dependencies = [ - "bitflags", + "bitflags 2.9.0", "futures-channel", "futures-core", "futures-executor", @@ -785,7 +837,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aec4fd3226bb6aa8dda5370142e14a4d15f00bba99bfb355b6ef7bb49d100758" dependencies = [ - "bitflags", + "bitflags 2.9.0", "gdk4", "glib", "glib-sys", @@ -838,6 +890,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.2" @@ -850,6 +908,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.4.0" @@ -898,6 +965,16 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.9.0" @@ -905,7 +982,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -965,6 +1042,12 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -1076,7 +1159,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] @@ -1094,6 +1177,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + [[package]] name = "pango" version = "0.20.9" @@ -1192,7 +1281,7 @@ checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi", + "hermit-abi 0.4.0", "pin-project-lite", "rustix", "tracing", @@ -1256,6 +1345,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0452695941410a58c8ce4391707ba9bad26a247173bd9886a05a5e8a8babec75" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quick-xml" version = "0.37.4" @@ -1339,7 +1438,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys", @@ -1443,7 +1542,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags", + "bitflags 2.9.0", "bytemuck", "calloop 0.13.0", "calloop-wayland-source", @@ -1475,6 +1574,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" @@ -1535,6 +1640,21 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" + [[package]] name = "thiserror" version = "1.0.69" @@ -1575,6 +1695,12 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "tini" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004df4c5f0805eb5f55883204a514cfa43a6d924741be29e871753a53d5565a" + [[package]] name = "tokio" version = "1.44.2" @@ -1629,7 +1755,7 @@ version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap", + "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", @@ -1708,7 +1834,7 @@ version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" dependencies = [ - "bitflags", + "bitflags 2.9.0", "rustix", "wayland-backend", "wayland-scanner", @@ -1720,7 +1846,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags", + "bitflags 2.9.0", "cursor-icon", "wayland-backend", ] @@ -1742,7 +1868,7 @@ version = "0.32.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" dependencies = [ - "bitflags", + "bitflags 2.9.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -1754,7 +1880,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2" dependencies = [ - "bitflags", + "bitflags 2.9.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -1768,7 +1894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" dependencies = [ "proc-macro2", - "quick-xml", + "quick-xml 0.37.4", "quote", ] @@ -1797,6 +1923,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1953,9 +2088,10 @@ version = "0.1.0" dependencies = [ "anyhow", "calloop 0.14.2", - "clap", + "clap 4.5.35", "crossbeam", "env_logger", + "freedesktop-file-parser", "gdk4", "gtk4", "gtk4-layer-shell", @@ -1982,6 +2118,18 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" +[[package]] +name = "xdgkit" +version = "3.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeac9c0125f3c131c6a2898d2a9f25c11b7954c3ff644a018cb9e06fa92919b" +dependencies = [ + "clap 3.2.25", + "quick-xml 0.21.0", + "serde", + "tini", +] + [[package]] name = "xkbcommon" version = "0.7.0" @@ -2008,6 +2156,15 @@ version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 9f84b39..eec2443 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,3 +27,4 @@ smithay-client-toolkit = { version = "0.19.2", features = ["calloop"]} calloop = "0.14.2" crossbeam = "0.8.4" libc = "0.2.171" +freedesktop-file-parser = "0.1.0" diff --git a/src/args.rs b/src/args.rs index 81391ae..1c19175 100644 --- a/src/args.rs +++ b/src/args.rs @@ -37,7 +37,7 @@ impl FromStr for Mode { } #[derive(Parser, Debug, Deserialize, Serialize)] -#[clap(about = "Ravi is a wofi clone written in rust, it aims to be a drop in replacement")] +#[clap(about = "Worf is a wofi clone written in rust, it aims to be a drop in replacement")] pub struct Args { /// Forks the menu so you can close the terminal #[clap(short = 'f', long = "fork")] diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs index c3120c2..eb539c0 100644 --- a/src/desktop/mod.rs +++ b/src/desktop/mod.rs @@ -1,8 +1,9 @@ +use freedesktop_file_parser::DesktopFile; use gtk4::prelude::*; use gtk4::{IconLookupFlags, IconTheme, TextDirection}; use home::home_dir; use ini::configparser::ini::Ini; -use log::{info, warn}; +use log::{debug, info, warn}; use regex::Regex; use std::collections::HashMap; use std::path::Path; @@ -46,23 +47,25 @@ pub fn default_icon() -> String { } fn fetch_icon_from_desktop_file(icon_name: &str) -> Option { - find_desktop_files().into_iter().find_map(|desktop_file| { - desktop_file - .get("Desktop Entry") - .filter(|desktop_entry| { - desktop_entry - .get("Exec") - .and_then(|opt| opt.as_ref()) - .is_some_and(|exec| exec.to_lowercase().contains(icon_name)) - }) - .map(|desktop_entry| { - desktop_entry - .get("Icon") - .and_then(|opt| opt.as_ref()) - .map(ToOwned::to_owned) - .unwrap_or_default() - }) - }) + // find_desktop_files().into_iter().find_map(|desktop_file| { + // desktop_file + // .get("Desktop Entry") + // .filter(|desktop_entry| { + // desktop_entry + // .get("Exec") + // .and_then(|opt| opt.as_ref()) + // .is_some_and(|exec| exec.to_lowercase().contains(icon_name)) + // }) + // .map(|desktop_entry| { + // desktop_entry + // .get("Icon") + // .and_then(|opt| opt.as_ref()) + // .map(ToOwned::to_owned) + // .unwrap_or_default() + // }) + // }) + //todo + None } fn fetch_icon_from_theme(icon_name: &str) -> Option { @@ -135,8 +138,11 @@ fn find_file_case_insensitive(folder: &Path, file_name: &Regex) -> Option Vec>>> { - let mut paths = vec![PathBuf::from("/usr/share/applications")]; +pub(crate) fn find_desktop_files() -> Vec { + let mut paths = vec![ + PathBuf::from("/usr/share/applications"), + PathBuf::from("/usr/local/share/applications"), + ]; if let Some(home) = home_dir() { paths.push(home.join(".local/share/applications")); @@ -150,8 +156,10 @@ pub(crate) fn find_desktop_files() -> Vec) -> anyhow::Result<(i32) ); // No need for application_id unless you want portal support - let app = Application::builder().application_id("ravi").build(); + let app = Application::builder().application_id("worf").build(); let (sender, receiver) = channel::bounded(1); app.connect_activate(move |app| { @@ -185,7 +185,7 @@ fn setup_key_event_handler( let key_controller = EventControllerKey::new(); key_controller.connect_key_pressed(move |_, key_value, _, _| { match key_value { - Key::Escape => exit(1), + Key::Escape => exit(1), // todo better way to do this? Key::Return => { for s in &inner_box.selected_children() { // let element : &Option<&EntryElement> = &elements.get(s.index() as usize); diff --git a/src/main.rs b/src/main.rs index 3d71c81..4df93ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,14 +12,16 @@ use gtk4::prelude::{ FlowBoxChildExt, GtkWindowExt, ListBoxRowExt, NativeExt, ObjectExt, SurfaceExt, WidgetExt, }; use gtk4_layer_shell::LayerShell; +use log::{debug, warn}; use merge::Merge; +use std::collections::HashMap; use std::ops::Deref; use std::os::unix::process::CommandExt; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::sync::Arc; use std::thread::sleep; -use std::{fs, time}; +use std::{env, fs, time}; mod args; mod config; @@ -80,43 +82,139 @@ fn main() -> anyhow::Result<()> { Ok(()) } +fn get_locale_variants() -> Vec { + let locale = env::var("LC_ALL") + .or_else(|_| env::var("LC_MESSAGES")) + .or_else(|_| env::var("LANG")) + .unwrap_or_else(|_| "c".to_string()); + + let lang = locale.split('.').next().unwrap_or(&locale).to_lowercase(); + let mut variants = vec![]; + + if let Some((lang_part, region)) = lang.split_once('_') { + variants.push(format!("{}_{region}", lang_part)); // en_us + variants.push(lang_part.to_string()); // en + } else { + variants.push(lang.clone()); // e.g. "fr" + } + + variants +} + +fn extract_desktop_fields( + category: &str, + //keys: Vec, + desktop_map: &HashMap>>, +) -> HashMap { + let mut result: HashMap = HashMap::new(); + let category_map = desktop_map.get(category); + if category_map.is_none() { + debug!("No desktop map for category {category}, map data: {desktop_map:?}"); + return result; + } + + let keys_needed = ["name", "exec", "icon"]; + let locale_variants = get_locale_variants(); + + for (map_key, map_value) in category_map.unwrap() { + for key in keys_needed { + if result.contains_key(key) || map_value.is_none() { + continue; + } + + let (k, v) = locale_variants + .iter() + .find(|locale| { + let localized_key = format!("{}[{}]", key, locale); + key == localized_key + }) + .map(|_| (Some(key), map_value)) + .unwrap_or_else(|| { + if key == map_key { + (Some(key), map_value) + } else { + (None, &None) + } + }); + if let Some(k) = k { + if let Some(v) = v { + result.insert(k.to_owned(), v.clone()); + } + } + } + + if result.len() == keys_needed.len() { + break; + } + } + + result +} fn drun(mut config: Config) -> anyhow::Result<()> { let mut entries: Vec = Vec::new(); for file in &find_desktop_files() { - if let Some(desktop_entry) = file.get("desktop entry") { - let icon = desktop_entry - .get("icon") - .and_then(|x| x.as_ref().map(|x| x.to_owned())); - let Some(exec) = desktop_entry.get("exec").and_then(|x| x.as_ref().cloned()) else { - continue; - }; + let n = get_locale_variants() + .iter() + .filter_map(|local| file.entry.name.variants.get(local)) + .next() + .map(|name| name.deref().clone()) + .or_else(|| Some(&file.entry.name.default)); - if let Some((cmd, _)) = exec.split_once(' ') { - if !PathBuf::from(cmd).exists() { - continue; - } - } + debug!("{n:?}") - let name = desktop_entry - .get("name") - .and_then(|x| x.as_ref().map(|x| x.to_owned())); - if let Some(name) = name { - entries.push({ - EntryElement { - label: name, - icon_path: icon, - action: Some(exec), - sub_elements: None, - } - }) - } - } + // let desktop = Some("desktop entry"); + // let locale = + // env::var("LC_ALL") + // .or_else(|_| env::var("LC_MESSAGES")) + // .or_else(|_| env::var("LANG")) + // .unwrap_or_else(|_| "en_US.UTF-8".to_string()).split_once(".").map(|(k,_)| k.to_owned().to_lowercase()); + // + // + // + // + // if let Some(desktop_entry) = file.get("desktop entry") { + // let icon = desktop_entry + // .get("icon") + // .and_then(|x| x.as_ref().map(|x| x.to_owned())); + // + // + // let Some(exec) = desktop_entry.get("exec") + // + // + // + // .and_then(|x| x.as_ref()) else { + // warn!("Skipping desktop file {file:#?}"); + // continue; + // }; + // + // if let Some((cmd, _)) = exec.split_once(' ') { + // if !PathBuf::from(cmd).exists() { + // continue; + // } + // } + // + // let name = desktop_entry + // .get("name") + // .and_then(|x| x.as_ref().map(|x| x.to_owned())); + // + // if let Some(name) = name { + // entries.push({ + // EntryElement { + // label: name, + // icon_path: icon, + // action: Some(exec.clone()), + // sub_elements: None, + // } + // }) + // } + // } } entries.sort_by(|l, r| l.label.cmp(&r.label)); if config.prompt.is_none() { config.prompt = Some("drun".to_owned()); } + // todo ues a arc instead of cloning the config let selected_index = gui::show(config.clone(), entries.clone())?; entries.get(selected_index as usize).map(|e| { @@ -130,6 +228,7 @@ fn drun(mut config: Config) -> anyhow::Result<()> { fn spawn_fork(cmd: &str) { // todo fork this for real + // todo probably remove arguments? // Unix-like systems (Linux, macOS) let _ = Command::new(cmd) .stdin(Stdio::null()) // Disconnect stdin