This commit is contained in:
nora 2025-12-31 16:43:01 +01:00
parent bee16cced1
commit eaaa474eb5
2 changed files with 49 additions and 13 deletions

View file

@ -1,8 +1,36 @@
use eyre::{Context, Result}; use eyre::{Context, Result};
use freedesktop_file_parser::{DesktopFile, EntryType}; use freedesktop_file_parser::{DesktopFile, EntryType};
use palette::{IntoColor, Oklab, Oklaba}; use palette::{IntoColor, Oklab, Oklaba, color_difference::EuclideanDistance};
use std::{collections::HashMap, ffi::OsStr, fs::DirEntry, path::Path}; use std::{collections::HashMap, ffi::OsStr, fs::DirEntry, path::Path};
pub struct DesktopEntries {
entries: Vec<DesktopEntry>,
}
pub struct DesktopEntry {
pub _id: String,
pub file: DesktopFile,
pub avg_icon_color: Oklab,
}
impl DesktopEntries {
pub fn count(&self) -> usize {
self.entries.len()
}
pub fn find_entry(&self, color: Oklab) -> Option<&DesktopEntry> {
self.entries.iter().min_by(|x, y| {
f32::total_cmp(
&diff_color(x.avg_icon_color, color),
&diff_color(y.avg_icon_color, color),
)
})
}
}
fn diff_color(icon: Oklab, color: Oklab) -> f32 {
icon.distance_squared(color)
}
fn walkdir(path: &Path, f: &mut impl FnMut(&DirEntry) -> Result<()>) -> Result<()> { fn walkdir(path: &Path, f: &mut impl FnMut(&DirEntry) -> Result<()>) -> Result<()> {
for entry in path.read_dir()? { for entry in path.read_dir()? {
let entry = entry?; let entry = entry?;
@ -14,7 +42,7 @@ fn walkdir(path: &Path, f: &mut impl FnMut(&DirEntry) -> Result<()>) -> Result<(
Ok(()) Ok(())
} }
pub(crate) fn find_desktop_files() -> Result<Vec<(DesktopFile, Oklab)>> { pub(crate) fn find_desktop_files() -> Result<DesktopEntries> {
// https://specifications.freedesktop.org/desktop-entry/latest/file-naming.html // https://specifications.freedesktop.org/desktop-entry/latest/file-naming.html
let paths = std::env::var("XDG_DATA_DIRS").unwrap_or("/usr/local/share/:/usr/share/".into()); let paths = std::env::var("XDG_DATA_DIRS").unwrap_or("/usr/local/share/:/usr/share/".into());
let paths = std::env::split_paths(&paths); let paths = std::env::split_paths(&paths);
@ -55,7 +83,14 @@ pub(crate) fn find_desktop_files() -> Result<Vec<(DesktopFile, Oklab)>> {
.decode() .decode()
.wrap_err_with(|| format!("decoding {}", icon.display()))?; .wrap_err_with(|| format!("decoding {}", icon.display()))?;
let color = average_color(&icon); let color = average_color(&icon);
results.insert(id, (file, color)); results.insert(
id.clone(),
DesktopEntry {
_id: id,
file,
avg_icon_color: color,
},
);
} }
Ok(()) Ok(())
@ -63,7 +98,9 @@ pub(crate) fn find_desktop_files() -> Result<Vec<(DesktopFile, Oklab)>> {
.wrap_err_with(|| format!("{}", base.display()))?; .wrap_err_with(|| format!("{}", base.display()))?;
} }
Ok(results.into_values().collect()) Ok(DesktopEntries {
entries: results.into_values().collect(),
})
} }
fn average_color(image: &image::DynamicImage) -> palette::Oklab { fn average_color(image: &image::DynamicImage) -> palette::Oklab {

View file

@ -6,9 +6,9 @@ use std::{
}; };
use eyre::{Context, Result, bail}; use eyre::{Context, Result, bail};
use freedesktop_file_parser::{DesktopFile, EntryType}; use freedesktop_file_parser::EntryType;
use log::{error, info, warn}; use log::{error, info, warn};
use palette::{FromColor, IntoColor, Oklab, color_difference::EuclideanDistance}; use palette::{FromColor, IntoColor, Oklab};
use smithay_client_toolkit::{ use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState}, compositor::{CompositorHandler, CompositorState},
output::{OutputHandler, OutputState}, output::{OutputHandler, OutputState},
@ -33,6 +33,8 @@ use wayland_client::{
protocol::{wl_buffer, wl_output::WlOutput, wl_pointer::WlPointer, wl_seat::WlSeat, wl_shm}, protocol::{wl_buffer, wl_output::WlOutput, wl_pointer::WlPointer, wl_seat::WlSeat, wl_shm},
}; };
use crate::desktop::DesktopEntries;
fn main() -> Result<()> { fn main() -> Result<()> {
env_logger::builder() env_logger::builder()
.filter(None, log::LevelFilter::Info) .filter(None, log::LevelFilter::Info)
@ -42,7 +44,7 @@ fn main() -> Result<()> {
let desktop_files = desktop::find_desktop_files().wrap_err("loading .desktop files")?; let desktop_files = desktop::find_desktop_files().wrap_err("loading .desktop files")?;
info!( info!(
"Loaded {} desktop icons in {:?}", "Loaded {} desktop icons in {:?}",
desktop_files.len(), desktop_files.count(),
now.elapsed() now.elapsed()
); );
@ -89,7 +91,7 @@ struct App {
shm: Shm, shm: Shm,
seat_state: SeatState, seat_state: SeatState,
desktop_files: Vec<(DesktopFile, Oklab)>, desktop_files: DesktopEntries,
pointers: HashMap<WlSeat, WlPointer>, pointers: HashMap<WlSeat, WlPointer>,
layer_surfaces: Vec<OutputSurface>, layer_surfaces: Vec<OutputSurface>,
} }
@ -400,13 +402,10 @@ impl PointerHandler for App {
let oklab: Oklab = srgb.into_format::<f32>().into_color(); let oklab: Oklab = srgb.into_format::<f32>().into_color();
let best_match = self let best_match = self.desktop_files.find_entry(oklab);
.desktop_files
.iter()
.min_by_key(|(_, icon_color)| (oklab.distance(*icon_color) * 1000000.0) as u32);
if let Some(best_match) = best_match if let Some(best_match) = best_match
&& let EntryType::Application(app) = &best_match.0.entry.entry_type && let EntryType::Application(app) = &best_match.file.entry.entry_type
&& let Some(exec) = &app.exec && let Some(exec) = &app.exec
{ {
// lol terrible implementation that works well enough // lol terrible implementation that works well enough