performance improvements
This commit is contained in:
parent
1cf6fa5f13
commit
8673ef4d13
6 changed files with 312 additions and 183 deletions
118
Cargo.lock
generated
118
Cargo.lock
generated
|
|
@ -458,6 +458,21 @@ dependencies = [
|
|||
"xdgkit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
|
|
@ -465,6 +480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -511,6 +527,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
|
|
@ -523,9 +545,13 @@ version = "0.3.31"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
|
|
@ -872,15 +898,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyprland"
|
||||
version = "0.4.0-beta.2"
|
||||
|
|
@ -1004,6 +1021,16 @@ version = "0.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
|
|
@ -1137,6 +1164,29 @@ dependencies = [
|
|||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
|
|
@ -1280,6 +1330,35 @@ version = "0.6.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.0"
|
||||
|
|
@ -1354,6 +1433,12 @@ version = "1.0.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
|
|
@ -1412,6 +1497,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
|
|
@ -1536,7 +1630,9 @@ dependencies = [
|
|||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
|
|
@ -1786,19 +1882,21 @@ dependencies = [
|
|||
"dirs",
|
||||
"env_logger",
|
||||
"freedesktop-file-parser",
|
||||
"futures",
|
||||
"gdk4",
|
||||
"gtk4",
|
||||
"gtk4-layer-shell",
|
||||
"home",
|
||||
"hyprland",
|
||||
"libc",
|
||||
"log",
|
||||
"meval",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strsim 0.11.1",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tree_magic_mini",
|
||||
"which",
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ gtk4-layer-shell = "0.5.0"
|
|||
gdk4 = "0.9.6"
|
||||
anyhow = "1.0.97"
|
||||
env_logger = "0.11.8"
|
||||
home = "0.5.11"
|
||||
log = "0.4.27"
|
||||
regex = "1.11.1"
|
||||
hyprland = "0.4.0-beta.2"
|
||||
|
|
@ -44,3 +43,6 @@ dirs = "6.0.0"
|
|||
which = "7.0.3"
|
||||
meval = "0.2.0"
|
||||
tree_magic_mini = "3.1.6"
|
||||
rayon = "1.10.0"
|
||||
tokio = { version = "1.44.2", features = ["full"] }
|
||||
futures = "0.3.31"
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ use std::path::PathBuf;
|
|||
use std::{env, fs, string};
|
||||
|
||||
use freedesktop_file_parser::DesktopFile;
|
||||
use futures::stream;
|
||||
use gdk4::Display;
|
||||
use gtk4::prelude::*;
|
||||
use gdk4::prelude::FileExt;
|
||||
use gtk4::{IconLookupFlags, IconTheme, TextDirection};
|
||||
use home::home_dir;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use futures::StreamExt;
|
||||
use log;
|
||||
use regex::Regex;
|
||||
|
||||
|
|
@ -16,50 +19,50 @@ pub enum DesktopError {
|
|||
MissingIcon,
|
||||
ParsingError(String),
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
///
|
||||
/// Will return `Err` if no icon can be found
|
||||
pub fn default_icon() -> Result<String, DesktopError> {
|
||||
fetch_icon_from_theme("image-missing").map_err(|_| DesktopError::MissingIcon)
|
||||
}
|
||||
|
||||
fn fetch_icon_from_theme(icon_name: &str) -> Result<String, DesktopError> {
|
||||
let display = gtk4::gdk::Display::default();
|
||||
if display.is_none() {
|
||||
log::error!("Failed to get display");
|
||||
}
|
||||
|
||||
let display = Display::default().expect("Failed to get default display");
|
||||
let theme = IconTheme::for_display(&display);
|
||||
|
||||
let icon = theme.lookup_icon(
|
||||
icon_name,
|
||||
&[],
|
||||
32,
|
||||
1,
|
||||
TextDirection::None,
|
||||
IconLookupFlags::empty(),
|
||||
);
|
||||
|
||||
match icon
|
||||
.file()
|
||||
.and_then(|file| file.path())
|
||||
.and_then(|path| path.to_str().map(string::ToString::to_string))
|
||||
{
|
||||
None => {
|
||||
let path = PathBuf::from("/usr/share/icons")
|
||||
.join(theme.theme_name())
|
||||
.join(format!("{icon_name}.svg"));
|
||||
if path.exists() {
|
||||
Ok(path.display().to_string())
|
||||
} else {
|
||||
Err(DesktopError::MissingIcon)
|
||||
}
|
||||
}
|
||||
Some(i) => Ok(i),
|
||||
}
|
||||
}
|
||||
//
|
||||
// /// # Errors
|
||||
// ///
|
||||
// /// Will return `Err` if no icon can be found
|
||||
// pub fn default_icon() -> Result<String, DesktopError> {
|
||||
// fetch_icon_from_theme("image-missing").map_err(|_| DesktopError::MissingIcon)
|
||||
// }
|
||||
//
|
||||
// fn fetch_icon_from_theme(icon_name: &str) -> Result<String, DesktopError> {
|
||||
// let display = Display::default();
|
||||
// if display.is_none() {
|
||||
// log::error!("Failed to get display");
|
||||
// }
|
||||
//
|
||||
// let display = Display::default().expect("Failed to get default display");
|
||||
// let theme = IconTheme::for_display(&display);
|
||||
//
|
||||
// let icon = theme.lookup_icon(
|
||||
// icon_name,
|
||||
// &[],
|
||||
// 32,
|
||||
// 1,
|
||||
// TextDirection::None,
|
||||
// IconLookupFlags::empty(),
|
||||
// );
|
||||
//
|
||||
// match icon
|
||||
// .file()
|
||||
// .and_then(|file| file.path())
|
||||
// .and_then(|path| path.to_str().map(string::ToString::to_string))
|
||||
// {
|
||||
// None => {
|
||||
// let path = PathBuf::from("/usr/share/icons")
|
||||
// .join(theme.theme_name())
|
||||
// .join(format!("{icon_name}.svg"));
|
||||
// if path.exists() {
|
||||
// Ok(path.display().to_string())
|
||||
// } else {
|
||||
// Err(DesktopError::MissingIcon)
|
||||
// }
|
||||
// }
|
||||
// Some(i) => Ok(i),
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn known_image_extension_regex_pattern() -> Regex {
|
||||
Regex::new(&format!(
|
||||
|
|
@ -80,7 +83,7 @@ pub fn fetch_icon_from_common_dirs(icon_name: &str) -> Result<String, DesktopErr
|
|||
PathBuf::from("/usr/share/pixmaps"),
|
||||
];
|
||||
|
||||
if let Some(home) = home_dir() {
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
paths.push(home.join(".local/share/icons"));
|
||||
}
|
||||
|
||||
|
|
@ -124,6 +127,7 @@ fn find_file_case_insensitive(folder: &Path, file_name: &Regex) -> Option<Vec<Pa
|
|||
///
|
||||
/// When it cannot parse the internal regex
|
||||
#[must_use]
|
||||
|
||||
pub fn find_desktop_files() -> Vec<DesktopFile> {
|
||||
let mut paths = vec![
|
||||
PathBuf::from("/usr/share/applications"),
|
||||
|
|
@ -131,27 +135,28 @@ pub fn find_desktop_files() -> Vec<DesktopFile> {
|
|||
PathBuf::from("/var/lib/flatpak/exports/share/applications"),
|
||||
];
|
||||
|
||||
if let Some(home) = home_dir() {
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
paths.push(home.join(".local/share/applications"));
|
||||
}
|
||||
|
||||
if let Ok(xdg_data_home) = env::var("XDG_DATA_HOME") {
|
||||
// todo use dirs:: instead
|
||||
paths.push(PathBuf::from(xdg_data_home).join(".applications"));
|
||||
}
|
||||
|
||||
if let Ok(xdg_data_dir) = env::var("XDG_DATA_DIRS") {
|
||||
paths.push(PathBuf::from(xdg_data_dir).join(".applications"));
|
||||
if let Ok(xdg_data_dirs) = env::var("XDG_DATA_DIRS") {
|
||||
for dir in xdg_data_dirs.split(':') {
|
||||
paths.push(PathBuf::from(dir).join(".applications"));
|
||||
}
|
||||
}
|
||||
|
||||
let regex = &Regex::new("(?i).*\\.desktop$").unwrap();
|
||||
|
||||
let p: Vec<_> = paths
|
||||
.into_iter()
|
||||
.into_par_iter()
|
||||
.filter(|desktop_dir| desktop_dir.exists())
|
||||
.filter_map(|icon_dir| find_file_case_insensitive(&icon_dir, regex))
|
||||
.flat_map(|desktop_files| {
|
||||
desktop_files.into_iter().filter_map(|desktop_file| {
|
||||
desktop_files.into_par_iter().filter_map(|desktop_file| {
|
||||
fs::read_to_string(desktop_file)
|
||||
.ok()
|
||||
.and_then(|content| freedesktop_file_parser::parse(&content).ok())
|
||||
|
|
@ -161,6 +166,26 @@ pub fn find_desktop_files() -> Vec<DesktopFile> {
|
|||
p
|
||||
}
|
||||
|
||||
pub fn lookup_icon(name: &str, size: i32) -> gtk4::Image {
|
||||
let img_regex = Regex::new(&format!(
|
||||
r"((?i).*{})|(^/.*)",
|
||||
known_image_extension_regex_pattern()
|
||||
));
|
||||
let image = if img_regex.unwrap().is_match(name) {
|
||||
if let Ok(img) = fetch_icon_from_common_dirs(&name) {
|
||||
gtk4::Image::from_file(img)
|
||||
} else {
|
||||
gtk4::Image::from_icon_name(name)
|
||||
}
|
||||
} else {
|
||||
gtk4::Image::from_icon_name(name)
|
||||
};
|
||||
|
||||
image.set_pixel_size(size);
|
||||
|
||||
image
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_locale_variants() -> Vec<String> {
|
||||
let locale = env::var("LC_ALL")
|
||||
|
|
|
|||
|
|
@ -251,17 +251,18 @@ fn build_ui<T, P>(
|
|||
|
||||
window.set_child(Some(&outer_box));
|
||||
|
||||
let window_show = Instant::now();
|
||||
window.show();
|
||||
let window_done = Instant::now();
|
||||
|
||||
window.show();
|
||||
animate_window_show(config, window.clone());
|
||||
let animation_done = Instant::now();
|
||||
|
||||
log::debug!(
|
||||
"Building UI took {:?}, window creation {:?}, animation {:?}",
|
||||
"Building UI took {:?}, window show {:?}, animation {:?}",
|
||||
start.elapsed(),
|
||||
window_done - start,
|
||||
animation_done - start
|
||||
window_done - window_show,
|
||||
animation_done - window_done
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -300,7 +301,7 @@ fn build_ui_from_menu_items<T: Clone + 'static>(
|
|||
|
||||
let created_ui = Instant::now();
|
||||
log::debug!(
|
||||
"Creating UI took {:?}, got locker after {:?}, cleared box after {:?}, created UI after {:?}",
|
||||
"Creating UI took {:?}, got lock after {:?}, cleared box after {:?}, created UI after {:?}",
|
||||
start.elapsed(),
|
||||
got_lock - start,
|
||||
cleared_box - start,
|
||||
|
|
@ -482,6 +483,7 @@ fn animate_window_show(config: &Config, window: ApplicationWindow) {
|
|||
let monitor = display.monitor_at_surface(&surface);
|
||||
if let Some(monitor) = monitor {
|
||||
let geometry = monitor.geometry();
|
||||
log::debug!("monitor geometry: {:?}", geometry);
|
||||
let Some(target_width) = percent_or_absolute(config.width.as_ref(), geometry.width())
|
||||
else {
|
||||
return;
|
||||
|
|
@ -536,10 +538,17 @@ fn animate_window<Func>(
|
|||
) where
|
||||
Func: Fn() + 'static,
|
||||
{
|
||||
if animation_time == 0 {
|
||||
window.set_width_request(target_width);
|
||||
window.set_height_request(target_height);
|
||||
on_done_func();
|
||||
return;
|
||||
}
|
||||
|
||||
let allocation = window.allocation();
|
||||
|
||||
// Define animation parameters
|
||||
let animation_step_length = Duration::from_millis(16); // ~60 FPS
|
||||
let animation_step_length = Duration::from_millis(8); // ~120 FPS
|
||||
|
||||
// Start positions (initial window dimensions)
|
||||
let start_width = allocation.width() as f32;
|
||||
|
|
@ -552,32 +561,29 @@ fn animate_window<Func>(
|
|||
// Start the animation timer
|
||||
let start_time = Instant::now();
|
||||
|
||||
// Start the animation loop (runs at ~60 FPS)
|
||||
let mut last_t = 0.0;
|
||||
|
||||
timeout_add_local(animation_step_length, move || {
|
||||
// Get the elapsed time in milliseconds since the animation started
|
||||
let elapsed_ms = start_time.elapsed().as_millis() as f32; // Elapsed time in milliseconds
|
||||
let elapsed_us = start_time.elapsed().as_micros() as f32;
|
||||
let t = (elapsed_us / (animation_time * 1000) as f32).min(1.0);
|
||||
|
||||
// Calculate the progress (t) from 0.0 to 1.0 based on elapsed time and animation duration
|
||||
let t = (elapsed_ms / animation_time as f32).min(1.0);
|
||||
// Skip if there's no meaningful change in progress
|
||||
if (t - last_t).abs() < 0.001 && t < 1.0 {
|
||||
return ControlFlow::Continue;
|
||||
}
|
||||
last_t = t;
|
||||
|
||||
// Apply easing function for smoother transition
|
||||
// could be other easing function, but this looked best
|
||||
// todo make other easing types configurable?
|
||||
let eased_t = ease_in_out_cubic(t);
|
||||
|
||||
// Calculate the new width and height based on easing
|
||||
let current_width = start_width + delta_width * eased_t;
|
||||
let current_height = start_height + delta_height * eased_t;
|
||||
|
||||
// Round the dimensions to nearest integers
|
||||
let rounded_width = current_width.round() as i32;
|
||||
let rounded_height = current_height.round() as i32;
|
||||
|
||||
// Perform the resizing of the window based on the current width and height
|
||||
window.set_width_request(rounded_width);
|
||||
window.set_height_request(rounded_height);
|
||||
|
||||
// If the animation is complete (t >= 1.0), set final size and break the loop
|
||||
if t >= 1.0 {
|
||||
window.set_width_request(target_width);
|
||||
window.set_height_request(target_height);
|
||||
|
|
@ -774,20 +780,23 @@ fn create_menu_row<T: Clone + 'static>(
|
|||
label.set_xalign(0.0);
|
||||
}
|
||||
|
||||
log::debug!(
|
||||
"Creating menu took {:?}, ui created after {:?}, icon found after {:?}",
|
||||
start.elapsed(),
|
||||
ui_created - start,
|
||||
icon_found - start
|
||||
);
|
||||
// log::debug!(
|
||||
// "Creating menu took {:?}, ui created after {:?}, icon found after {:?}",
|
||||
// start.elapsed(),
|
||||
// ui_created - start,
|
||||
// icon_found - start
|
||||
// );
|
||||
|
||||
row.upcast()
|
||||
}
|
||||
|
||||
fn lookup_icon<T: Clone>(menu_item: &MenuItem<T>, config: &Config) -> Option<Image> {
|
||||
if let Some(image_path) = &menu_item.icon_path {
|
||||
let img_regex = Regex::new(&format!(r"((?i).*{})|(^/.*)", known_image_extension_regex_pattern()));
|
||||
let image = if img_regex.unwrap().is_match(image_path) {
|
||||
let img_regex = Regex::new(&format!(
|
||||
r"((?i).*{})|(^/.*)",
|
||||
known_image_extension_regex_pattern()
|
||||
));
|
||||
let image = if img_regex.unwrap().is_match(image_path) {
|
||||
if let Ok(img) = desktop::fetch_icon_from_common_dirs(&image_path) {
|
||||
Image::from_file(img)
|
||||
} else {
|
||||
|
|
|
|||
180
src/lib/mode.rs
180
src/lib/mode.rs
|
|
@ -1,5 +1,10 @@
|
|||
use crate::config::{Config, expand_path};
|
||||
use crate::desktop::{find_desktop_files, get_locale_variants, lookup_name_with_locale};
|
||||
use crate::gui;
|
||||
use crate::gui::{ItemProvider, MenuItem};
|
||||
use anyhow::Context;
|
||||
use freedesktop_file_parser::EntryType;
|
||||
use rayon::prelude::*;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -10,13 +15,6 @@ use std::process::{Command, Stdio};
|
|||
use std::time::Instant;
|
||||
use std::{env, fmt, fs, io};
|
||||
|
||||
use crate::config::{Config, expand_path};
|
||||
use crate::desktop::{
|
||||
default_icon, find_desktop_files, get_locale_variants, lookup_name_with_locale,
|
||||
};
|
||||
use crate::gui;
|
||||
use crate::gui::{ItemProvider, MenuItem};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ModeError {
|
||||
UpdateCacheError(String),
|
||||
|
|
@ -53,103 +51,101 @@ struct DRunProvider<T: Clone> {
|
|||
cache: HashMap<String, i64>,
|
||||
}
|
||||
|
||||
impl<T: Clone> DRunProvider<T> {
|
||||
impl<T: Clone + std::marker::Send + std::marker::Sync> DRunProvider<T> {
|
||||
fn new(menu_item_data: T) -> Self {
|
||||
let locale_variants = get_locale_variants();
|
||||
let default_icon = default_icon().unwrap_or_default();
|
||||
let default_icon = "application-x-executable".to_string();
|
||||
|
||||
let (cache_path, d_run_cache) = load_d_run_cache();
|
||||
|
||||
let start = Instant::now();
|
||||
let mut entries: Vec<MenuItem<T>> = Vec::new();
|
||||
for file in find_desktop_files().iter().filter(|f| {
|
||||
f.entry.hidden.is_none_or(|hidden| !hidden)
|
||||
&& f.entry.no_display.is_none_or(|no_display| !no_display)
|
||||
}) {
|
||||
let Some(name) = lookup_name_with_locale(
|
||||
&locale_variants,
|
||||
&file.entry.name.variants,
|
||||
&file.entry.name.default,
|
||||
) else {
|
||||
log::warn!("Skipping desktop entry without name {file:?}");
|
||||
continue;
|
||||
};
|
||||
|
||||
let (action, working_dir) = match &file.entry.entry_type {
|
||||
EntryType::Application(app) => (app.exec.clone(), app.path.clone()),
|
||||
_ => (None, None),
|
||||
};
|
||||
|
||||
let cmd_exists = action
|
||||
.as_ref()
|
||||
.and_then(|a| {
|
||||
a.split(' ')
|
||||
.next()
|
||||
.map(|cmd| cmd.replace('"', ""))
|
||||
.map(|cmd| PathBuf::from(&cmd).exists() || which::which(&cmd).is_ok())
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if !cmd_exists {
|
||||
log::warn!(
|
||||
"Skipping desktop entry for {name:?} because action {action:?} does not exist"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let icon = file
|
||||
.entry
|
||||
.icon
|
||||
.as_ref()
|
||||
.map(|s| s.content.clone())
|
||||
.or(Some(default_icon.clone()));
|
||||
log::debug!("file, name={name:?}, icon={icon:?}, action={action:?}");
|
||||
let sort_score = d_run_cache.get(&name).unwrap_or(&0);
|
||||
|
||||
let mut entry: MenuItem<T> = MenuItem {
|
||||
label: name,
|
||||
icon_path: icon.clone(),
|
||||
action,
|
||||
sub_elements: Vec::default(),
|
||||
working_dir: working_dir.clone(),
|
||||
initial_sort_score: *sort_score,
|
||||
search_sort_score: 0.0,
|
||||
data: Some(menu_item_data.clone()),
|
||||
visible: true,
|
||||
};
|
||||
|
||||
file.actions.iter().for_each(|(_, action)| {
|
||||
if let Some(action_name) = lookup_name_with_locale(
|
||||
let mut entries: Vec<MenuItem<T>> = find_desktop_files()
|
||||
.into_par_iter()
|
||||
.filter_map(|file| {
|
||||
let name = lookup_name_with_locale(
|
||||
&locale_variants,
|
||||
&action.name.variants,
|
||||
&action.name.default,
|
||||
) {
|
||||
let action_icon = action
|
||||
.icon
|
||||
.as_ref()
|
||||
.map(|s| s.content.clone())
|
||||
.or(icon.clone())
|
||||
.unwrap_or("application-x-executable".to_string());
|
||||
&file.entry.name.variants,
|
||||
&file.entry.name.default,
|
||||
)?;
|
||||
|
||||
log::debug!("sub, action_name={action_name:?}, action_icon={action_icon:?}");
|
||||
let (action, working_dir) = match &file.entry.entry_type {
|
||||
EntryType::Application(app) => (app.exec.clone(), app.path.clone()),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let sub_entry = MenuItem {
|
||||
label: action_name,
|
||||
icon_path: Some(action_icon),
|
||||
action: action.exec.clone(),
|
||||
sub_elements: Vec::default(),
|
||||
working_dir: working_dir.clone(),
|
||||
initial_sort_score: 0, // subitems are never sorted right now.
|
||||
search_sort_score: 0.0,
|
||||
data: None,
|
||||
visible: true,
|
||||
};
|
||||
entry.sub_elements.push(sub_entry);
|
||||
let cmd_exists = action
|
||||
.as_ref()
|
||||
.and_then(|a| {
|
||||
a.split(' ')
|
||||
.next()
|
||||
.map(|cmd| cmd.replace('"', ""))
|
||||
.map(|cmd| PathBuf::from(&cmd).exists() || which::which(&cmd).is_ok())
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if !cmd_exists {
|
||||
log::warn!(
|
||||
"Skipping desktop entry for {name:?} because action {action:?} does not exist"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
});
|
||||
|
||||
entries.push(entry);
|
||||
}
|
||||
let icon = file
|
||||
.entry
|
||||
.icon
|
||||
.as_ref()
|
||||
.map(|s| s.content.clone())
|
||||
.or(Some(default_icon.clone()));
|
||||
|
||||
log::debug!("file, name={name:?}, icon={icon:?}, action={action:?}");
|
||||
|
||||
let sort_score = *d_run_cache.get(&name).unwrap_or(&0);
|
||||
|
||||
let mut entry = MenuItem {
|
||||
label: name.clone(),
|
||||
icon_path: icon.clone(),
|
||||
action: action.clone(),
|
||||
sub_elements: Vec::new(),
|
||||
working_dir: working_dir.clone(),
|
||||
initial_sort_score: sort_score,
|
||||
search_sort_score: 0.0,
|
||||
data: Some(menu_item_data.clone()),
|
||||
visible: true,
|
||||
};
|
||||
|
||||
for (_, action) in &file.actions {
|
||||
if let Some(action_name) = lookup_name_with_locale(
|
||||
&locale_variants,
|
||||
&action.name.variants,
|
||||
&action.name.default,
|
||||
) {
|
||||
let action_icon = action
|
||||
.icon
|
||||
.as_ref()
|
||||
.map(|s| s.content.clone())
|
||||
.or(icon.clone())
|
||||
.unwrap_or("application-x-executable".to_string());
|
||||
|
||||
log::debug!("sub, action_name={action_name:?}, action_icon={action_icon:?}");
|
||||
|
||||
entry.sub_elements.push(MenuItem {
|
||||
label: action_name,
|
||||
icon_path: Some(action_icon),
|
||||
action: action.exec.clone(),
|
||||
sub_elements: Vec::new(),
|
||||
working_dir: working_dir.clone(),
|
||||
initial_sort_score: 0,
|
||||
search_sort_score: 0.0,
|
||||
data: None,
|
||||
visible: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Some(entry)
|
||||
})
|
||||
.collect();
|
||||
|
||||
log::info!(
|
||||
"parsing desktop files took {}ms",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ fn main() -> anyhow::Result<()> {
|
|||
gtk4::init()?;
|
||||
|
||||
env_logger::Builder::new()
|
||||
// todo change to error as default
|
||||
.parse_filters(&env::var("RUST_LOG").unwrap_or_else(|_| "error".to_owned()))
|
||||
.init();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue