mirror of
https://github.com/danbulant/icon
synced 2026-05-19 04:08:36 +00:00
Fix icon finding and add extensive test
This commit is contained in:
parent
b1c9668dfe
commit
1ac1003d31
5 changed files with 291 additions and 31 deletions
199
Cargo.lock
generated
199
Cargo.lock
generated
|
|
@ -2,6 +2,44 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "freedesktop-desktop-entry"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb78ccb4eb670a9c659f1c61e709d41fd6401cddf562f14cac1a47077918d3"
|
||||
dependencies = [
|
||||
"gettext-rs",
|
||||
"log",
|
||||
"memchr",
|
||||
"thiserror 2.0.12",
|
||||
"unicase",
|
||||
"xdg 2.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "freedesktop_entry_parser"
|
||||
version = "1.3.0"
|
||||
|
|
@ -12,14 +50,60 @@ dependencies = [
|
|||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gettext-rs"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a44e92f7dc08430aca7ed55de161253a22276dfd69c5526e5c5e95d1f7cf338a"
|
||||
dependencies = [
|
||||
"gettext-sys",
|
||||
"locale_config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gettext-sys"
|
||||
version = "0.22.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb45773f5b8945f12aecd04558f545964f943dacda1b1155b3d738f5469ef661"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"temp-dir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icon"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"freedesktop-desktop-entry",
|
||||
"freedesktop_entry_parser",
|
||||
"log",
|
||||
"thiserror 2.0.12",
|
||||
"xdg",
|
||||
"xdg 3.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||
|
||||
[[package]]
|
||||
name = "locale_config"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d2c35b16f4483f6c26f0e4e9550717a2f6575bcd6f12a53ff0c490a94a6934"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
"regex",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -28,6 +112,15 @@ version = "0.4.27"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
|
|
@ -50,6 +143,35 @@ dependencies = [
|
|||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
||||
dependencies = [
|
||||
"malloc_buf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc-foundation"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
|
||||
dependencies = [
|
||||
"block",
|
||||
"objc",
|
||||
"objc_id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_id"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
|
||||
dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
|
|
@ -68,6 +190,41 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
|
|
@ -79,6 +236,12 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "temp-dir"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83176759e9416cf81ee66cb6508dbfe9c96f20b8b56265a39917551c23c70964"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
|
|
@ -119,12 +282,46 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "xdg"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
|
||||
|
||||
[[package]]
|
||||
name = "xdg"
|
||||
version = "3.0.0"
|
||||
|
|
|
|||
|
|
@ -11,3 +11,6 @@ log = { version = "0.4.27", optional = true }
|
|||
|
||||
[features]
|
||||
"log" = ["dep:log"]
|
||||
|
||||
[dev-dependencies]
|
||||
freedesktop-desktop-entry = "0.7.13"
|
||||
|
|
|
|||
|
|
@ -39,16 +39,15 @@ impl FileType {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn ext(&self) -> &str {
|
||||
match self {
|
||||
FileType::Png => "png",
|
||||
FileType::Xmp => "xmp",
|
||||
FileType::Svg => "svg",
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
pub const fn types() -> [FileType; 3] {
|
||||
[FileType::Png, FileType::Xmp, FileType::Svg]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ pub mod states {
|
|||
|
||||
/// Icons and icon themes are looked for in a set of directories.
|
||||
///
|
||||
/// By default, that is `$HOME/.icons`, `$XDG_DATA_DIRS/icons` and `/usr/share/pixmaps`.
|
||||
/// By default, that is `$HOME/.icons`, `$XDG_DATA_HOME/icons`, `$XDG_DATA_DIRS/icons` and `/usr/share/pixmaps`.
|
||||
/// Applications may further add their own icon directories to this list, and users may extend or change the list.
|
||||
/// The default list may be obtained using the `Default` implementation on `IconSearch` or its `default` method.
|
||||
///
|
||||
|
|
@ -512,8 +512,9 @@ impl Default for IconSearch {
|
|||
directories.push(home.join(".icons"));
|
||||
}
|
||||
|
||||
xdg.data_dirs
|
||||
xdg.data_home
|
||||
.into_iter()
|
||||
.chain(xdg.data_dirs.into_iter())
|
||||
.map(|data_dir| data_dir.join("icons"))
|
||||
.for_each(|dir| directories.push(dir));
|
||||
|
||||
|
|
@ -535,6 +536,8 @@ mod test {
|
|||
.add_directories(["/this/path/probably/doesnt/exist/but/who/cares/"])
|
||||
.search()
|
||||
.icons();
|
||||
|
||||
// no panic
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -549,21 +552,4 @@ mod test {
|
|||
let icon = locations.standalone_icon("htop").unwrap();
|
||||
assert_eq!(icon.path.file_name(), Some("htop.png".as_ref()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_2() {
|
||||
let result = IconSearch::default()
|
||||
.find_icon_locations()
|
||||
.load_single_theme("breeze")
|
||||
.unwrap();
|
||||
|
||||
println!("{:?}", result.index.inherits);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let _dirs = IconSearch::default().find_icon_locations().resolve();
|
||||
|
||||
// it didn't panic.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
91
src/theme.rs
91
src/theme.rs
|
|
@ -1,6 +1,6 @@
|
|||
use crate::IconSearch;
|
||||
use crate::icon::IconFile;
|
||||
use crate::theme::ThemeParseError::MissingRequiredAttribute;
|
||||
use crate::IconSearch;
|
||||
use freedesktop_entry_parser::low_level::{EntryIter, SectionBytes};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
|
@ -30,6 +30,10 @@ impl Icons {
|
|||
self.find_icon(icon_name, size, scale, "hicolor")
|
||||
}
|
||||
|
||||
/// Look up an icon by name, size, scale and theme.
|
||||
///
|
||||
/// If the icon is not found in the theme, its parents are checked.
|
||||
/// If no theme by the given name exists, the `"hicolor"` theme (default theme) is checked.
|
||||
pub fn find_icon(
|
||||
&self,
|
||||
icon_name: &str,
|
||||
|
|
@ -37,8 +41,10 @@ impl Icons {
|
|||
scale: u32,
|
||||
theme: &str,
|
||||
) -> Option<IconFile> {
|
||||
let theme = self.theme(theme)?;
|
||||
theme.find_icon(icon_name, size, scale)
|
||||
let theme = self.theme(theme).or_else(|| self.theme("hicolor"))?;
|
||||
theme
|
||||
.find_icon(icon_name, size, scale)
|
||||
.or_else(|| self.find_standalone_icon(icon_name))
|
||||
}
|
||||
|
||||
pub fn find_standalone_icon(&self, icon_name: &str) -> Option<IconFile> {
|
||||
|
|
@ -60,6 +66,15 @@ impl Theme {
|
|||
}
|
||||
|
||||
pub fn find_icon(&self, icon_name: &str, size: u32, scale: u32) -> Option<IconFile> {
|
||||
self.find_icon_here(icon_name, size, scale).or_else(|| {
|
||||
// or find it in one of our parents
|
||||
self.inherits_from
|
||||
.iter()
|
||||
.find_map(|theme| theme.find_icon_here(icon_name, size, scale))
|
||||
})
|
||||
}
|
||||
|
||||
fn find_icon_here(&self, icon_name: &str, size: u32, scale: u32) -> Option<IconFile> {
|
||||
const EXTENSIONS: [&'static str; 3] = ["png", "xmp", "svg"];
|
||||
let file_names = EXTENSIONS.map(|ext| format!("{icon_name}.{ext}"));
|
||||
|
||||
|
|
@ -394,24 +409,84 @@ fn find_attr_req<'a>(
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::Icons;
|
||||
use crate::icon::{FileType, IconFile};
|
||||
use crate::theme::{DirectoryType, ThemeIndex};
|
||||
use crate::Icons;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_find_icon() {
|
||||
fn test_find_firefox() {
|
||||
let icons = Icons::new();
|
||||
|
||||
let option = icons.find_default_icon("firefox", 128, 1);
|
||||
let ico = icons.find_default_icon("firefox", 128, 1);
|
||||
|
||||
assert_eq!(
|
||||
option,
|
||||
ico,
|
||||
Some(IconFile {
|
||||
path: "/usr/share/icons/hicolor/128x128/apps/firefox.png".into(),
|
||||
file_type: FileType::Png
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// we should be able to find an icon for a bunch of different sizes
|
||||
for size in (16u32..=64).step_by(8) {
|
||||
assert!(icons.find_default_icon("firefox", size, 1).is_some());
|
||||
}
|
||||
|
||||
assert!(icons.find_default_icon("firefox", 64, 2).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_all_desktop_entry_icons() {
|
||||
let icons = Icons::new();
|
||||
|
||||
// some desktop files are just packaged poorly.
|
||||
// if a test fails here, and you are certain that the icon just straight up doesn't exist,
|
||||
// or is in an unfindable place by normal means,
|
||||
// disallow it in this list.
|
||||
static DISALLOW_LIST: &[&str] = &[
|
||||
"imv-dir",
|
||||
"imv",
|
||||
"io.elementary.granite.demo",
|
||||
"java-java-openjdk",
|
||||
"jconsole-java-openjdk",
|
||||
"jshell-java-openjdk",
|
||||
"lstopo",
|
||||
"signon-ui",
|
||||
];
|
||||
|
||||
for entry in
|
||||
freedesktop_desktop_entry::Iter::new(freedesktop_desktop_entry::default_paths())
|
||||
.entries(None::<&[&str]>)
|
||||
{
|
||||
let Some(icon_name) = entry.icon() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if Path::new(icon_name).exists() {
|
||||
continue; // absolute URLs to icons are OK
|
||||
}
|
||||
|
||||
if DISALLOW_LIST
|
||||
.iter()
|
||||
.any(|x| Some(x.as_ref()) == entry.path.file_stem())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: perhaps our system should expose a way to construct a "composed theme" filter,
|
||||
// for cases where you want to search a multitude (or all) themes
|
||||
let icon = icons
|
||||
.find_icon(icon_name, 32, 1, "gnome")
|
||||
.or_else(|| icons.find_icon(icon_name, 32, 1, "breeze"));
|
||||
|
||||
assert!(
|
||||
icon.is_some(),
|
||||
"Icon {icon_name} from desktop entry {:?} missing!!",
|
||||
entry.path
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue