support sub elements, improve desktop file parsing
This commit is contained in:
parent
8ebefeffe6
commit
eb2d59070c
3 changed files with 134 additions and 86 deletions
|
|
@ -8,7 +8,7 @@ use regex::Regex;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{fs, string};
|
use std::{env, fs, string};
|
||||||
|
|
||||||
pub struct IconResolver {
|
pub struct IconResolver {
|
||||||
cache: HashMap<String, String>,
|
cache: HashMap<String, String>,
|
||||||
|
|
@ -165,3 +165,73 @@ pub(crate) fn find_desktop_files() -> Vec<DesktopFile> {
|
||||||
.collect();
|
.collect();
|
||||||
p
|
p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get_locale_variants() -> Vec<String> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_desktop_fields(
|
||||||
|
category: &str,
|
||||||
|
//keys: Vec<String>,
|
||||||
|
desktop_map: &HashMap<String, HashMap<String, Option<String>>>,
|
||||||
|
) -> HashMap<String, String> {
|
||||||
|
let mut result: HashMap<String, String> = 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
|
||||||
|
}
|
||||||
|
|
|
||||||
15
src/gui.rs
15
src/gui.rs
|
|
@ -20,14 +20,14 @@ use log::{debug, error, info};
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EntryElement {
|
pub struct MenuItem {
|
||||||
pub label: String, // todo support empty label?
|
pub label: String, // todo support empty label?
|
||||||
pub icon_path: Option<String>,
|
pub icon_path: Option<String>,
|
||||||
pub action: Option<String>,
|
pub action: Option<String>,
|
||||||
pub sub_elements: Option<Vec<EntryElement>>,
|
pub sub_elements: Vec<MenuItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show(config: Config, elements: Vec<EntryElement>) -> anyhow::Result<(i32)> {
|
pub fn show(config: Config, elements: Vec<MenuItem>) -> anyhow::Result<(i32)> {
|
||||||
// Load CSS
|
// Load CSS
|
||||||
let provider = CssProvider::new();
|
let provider = CssProvider::new();
|
||||||
let css_file_path = File::for_path("/home/me/.config/wofi/style.css");
|
let css_file_path = File::for_path("/home/me/.config/wofi/style.css");
|
||||||
|
|
@ -118,7 +118,7 @@ pub fn show(config: Config, elements: Vec<EntryElement>) -> anyhow::Result<(i32)
|
||||||
inner_box.set_activate_on_single_click(true);
|
inner_box.set_activate_on_single_click(true);
|
||||||
|
|
||||||
for entry in &elements {
|
for entry in &elements {
|
||||||
add_entry_element(&inner_box, &entry);
|
add_menu_item(&inner_box, &entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set focus after everything is realized
|
// Set focus after everything is realized
|
||||||
|
|
@ -216,8 +216,8 @@ fn setup_key_event_handler(
|
||||||
window.add_controller(key_controller);
|
window.add_controller(key_controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_entry_element(inner_box: &FlowBox, entry_element: &EntryElement) {
|
fn add_menu_item(inner_box: &FlowBox, entry_element: &MenuItem) {
|
||||||
let parent: Widget = if entry_element.sub_elements.is_some() {
|
let parent: Widget = if !entry_element.sub_elements.is_empty() {
|
||||||
let expander = Expander::new(None);
|
let expander = Expander::new(None);
|
||||||
|
|
||||||
// Inline label as expander label
|
// Inline label as expander label
|
||||||
|
|
@ -228,11 +228,12 @@ fn add_entry_element(inner_box: &FlowBox, entry_element: &EntryElement) {
|
||||||
// todo subelements do not fill full space yet.
|
// todo subelements do not fill full space yet.
|
||||||
// todo multi nesting is not supported yet.
|
// todo multi nesting is not supported yet.
|
||||||
|
|
||||||
for x in entry_element.sub_elements.iter().flatten() {
|
for x in entry_element.sub_elements.iter(){
|
||||||
let row = ListBoxRow::new();
|
let row = ListBoxRow::new();
|
||||||
row.set_widget_name("entry");
|
row.set_widget_name("entry");
|
||||||
|
|
||||||
let label = Label::new(Some(&x.label));
|
let label = Label::new(Some(&x.label));
|
||||||
|
label.set_halign(Align::Start);
|
||||||
row.set_child(Some(&label));
|
row.set_child(Some(&label));
|
||||||
list_box.append(&row);
|
list_box.append(&row);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
135
src/main.rs
135
src/main.rs
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
use crate::args::{Args, Mode};
|
use crate::args::{Args, Mode};
|
||||||
use crate::config::{Config, merge_config_with_args};
|
use crate::config::{Config, merge_config_with_args};
|
||||||
use crate::desktop::find_desktop_files;
|
use crate::desktop::{default_icon, find_desktop_files, get_locale_variants};
|
||||||
use crate::gui::EntryElement;
|
use crate::gui::MenuItem;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use gdk4::prelude::Cast;
|
use gdk4::prelude::Cast;
|
||||||
use gtk4::prelude::{
|
use gtk4::prelude::{
|
||||||
|
|
@ -12,7 +12,7 @@ use gtk4::prelude::{
|
||||||
FlowBoxChildExt, GtkWindowExt, ListBoxRowExt, NativeExt, ObjectExt, SurfaceExt, WidgetExt,
|
FlowBoxChildExt, GtkWindowExt, ListBoxRowExt, NativeExt, ObjectExt, SurfaceExt, WidgetExt,
|
||||||
};
|
};
|
||||||
use gtk4_layer_shell::LayerShell;
|
use gtk4_layer_shell::LayerShell;
|
||||||
use log::{debug, warn};
|
use log::{debug, info, warn};
|
||||||
use merge::Merge;
|
use merge::Merge;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
@ -22,6 +22,7 @@ use std::process::{Command, Stdio};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use std::{env, fs, time};
|
use std::{env, fs, time};
|
||||||
|
use freedesktop_file_parser::{DesktopAction, EntryType};
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
mod config;
|
mod config;
|
||||||
|
|
@ -82,85 +83,61 @@ fn main() -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_locale_variants() -> Vec<String> {
|
fn lookup_name_with_locale(
|
||||||
let locale = env::var("LC_ALL")
|
locale_variants: &Vec<String>,
|
||||||
.or_else(|_| env::var("LC_MESSAGES"))
|
variants: &HashMap<String, String>,
|
||||||
.or_else(|_| env::var("LANG"))
|
fallback: &str,
|
||||||
.unwrap_or_else(|_| "c".to_string());
|
) -> Option<String> {
|
||||||
|
locale_variants
|
||||||
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<String>,
|
|
||||||
desktop_map: &HashMap<String, HashMap<String, Option<String>>>,
|
|
||||||
) -> HashMap<String, String> {
|
|
||||||
let mut result: HashMap<String, String> = 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()
|
.iter()
|
||||||
.find(|locale| {
|
.filter_map(|local| variants.get(local))
|
||||||
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<EntryElement> = Vec::new();
|
|
||||||
for file in &find_desktop_files() {
|
|
||||||
let n = get_locale_variants()
|
|
||||||
.iter()
|
|
||||||
.filter_map(|local| file.entry.name.variants.get(local))
|
|
||||||
.next()
|
.next()
|
||||||
.map(|name| name.deref().clone())
|
.map(|name| name.to_owned())
|
||||||
.or_else(|| Some(&file.entry.name.default));
|
.or_else(|| Some(fallback.to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
debug!("{n:?}")
|
fn drun(mut config: Config) -> anyhow::Result<()> {
|
||||||
|
let mut entries: Vec<MenuItem> = Vec::new();
|
||||||
|
let locale_variants = get_locale_variants();
|
||||||
|
let default_icon = default_icon();
|
||||||
|
|
||||||
|
for file in find_desktop_files().iter().filter(|f| {
|
||||||
|
f.entry.hidden.map_or(true, |hidden| !hidden)
|
||||||
|
&& f.entry.no_display.map_or(true, |no_display| !no_display)
|
||||||
|
// todo handle not shown in?
|
||||||
|
}) {
|
||||||
|
let name = lookup_name_with_locale(
|
||||||
|
&locale_variants,
|
||||||
|
&file.entry.name.variants,
|
||||||
|
&file.entry.name.default,
|
||||||
|
);
|
||||||
|
if name.is_none() {
|
||||||
|
debug!("Skipping desktop entry without name {file:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut entry = MenuItem {
|
||||||
|
label: name.unwrap(),
|
||||||
|
icon_path: None,
|
||||||
|
action: None,
|
||||||
|
sub_elements: Vec::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
file.actions.iter().for_each(|(_, action)| {
|
||||||
|
let action_name = lookup_name_with_locale(
|
||||||
|
&locale_variants,
|
||||||
|
&action.name.variants,
|
||||||
|
&action.name.default,
|
||||||
|
);
|
||||||
|
let sub_entry = MenuItem {
|
||||||
|
label: action_name.unwrap().trim().to_owned(),
|
||||||
|
icon_path: None,
|
||||||
|
action: None,
|
||||||
|
sub_elements: Vec::default(),
|
||||||
|
};
|
||||||
|
entry.sub_elements.push(sub_entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
entries.push(entry);
|
||||||
|
|
||||||
// let desktop = Some("desktop entry");
|
// let desktop = Some("desktop entry");
|
||||||
// let locale =
|
// let locale =
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue