diff --git a/Cargo.lock b/Cargo.lock index 9ff8788..c20a4b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,7 +270,7 @@ checksum = "b01fe135c0bd16afe262b6dea349bd5ea30e6de50708cec639aae7c5c14cc7e4" dependencies = [ "bitflags", "cairo-sys-rs", - "glib", + "glib 0.21.5", "libc", ] @@ -280,7 +280,7 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06c28280c6b12055b5e39e4554271ae4e6630b27c0da9148c4cf6485fc6d245c" dependencies = [ - "glib-sys", + "glib-sys 0.21.5", "libc", "system-deps", ] @@ -700,8 +700,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "debb0d39e3cdd84626edfd54d6e4a6ba2da9a0ef2e796e691c4e9f8646fda00c" dependencies = [ "gdk-pixbuf-sys", - "gio", - "glib", + "gio 0.21.5", + "glib 0.21.5", "libc", ] @@ -711,9 +711,9 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd95ad50b9a3d2551e25dd4f6892aff0b772fe5372d84514e9d0583af60a0ce7" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.21.5", + "glib-sys 0.21.5", + "gobject-sys 0.21.5", "libc", "system-deps", ] @@ -727,9 +727,9 @@ dependencies = [ "cairo-rs", "gdk-pixbuf", "gdk4-sys", - "gio", + "gio 0.21.5", "gl", - "glib", + "glib 0.21.5", "libc", "pango", ] @@ -742,9 +742,9 @@ checksum = "a6d4e5b3ccf591826a4adcc83f5f57b4e59d1925cb4bf620b0d645f79498b034" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.21.5", + "glib-sys 0.21.5", + "gobject-sys 0.21.5", "libc", "pango-sys", "pkg-config", @@ -808,6 +808,23 @@ dependencies = [ "temp-dir", ] +[[package]] +name = "gio" +version = "0.20.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e27e276e7b6b8d50f6376ee7769a71133e80d093bdc363bd0af71664228b831" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys 0.20.10", + "glib 0.20.12", + "libc", + "pin-project-lite", + "smallvec", +] + [[package]] name = "gio" version = "0.21.5" @@ -818,21 +835,34 @@ dependencies = [ "futures-core", "futures-io", "futures-util", - "gio-sys", - "glib", + "gio-sys 0.21.5", + "glib 0.21.5", "libc", "pin-project-lite", "smallvec", ] +[[package]] +name = "gio-sys" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83" +dependencies = [ + "glib-sys 0.20.10", + "gobject-sys 0.20.10", + "libc", + "system-deps", + "windows-sys 0.59.0", +] + [[package]] name = "gio-sys" version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22" dependencies = [ - "glib-sys", - "gobject-sys", + "glib-sys 0.21.5", + "gobject-sys 0.21.5", "libc", "system-deps", "windows-sys 0.61.2", @@ -858,6 +888,27 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glib" +version = "0.20.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc4b6e352d4716d84d7dde562dd9aee2a7d48beb872dd9ece7f2d1515b2d683" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys 0.20.10", + "glib-macros 0.20.12", + "glib-sys 0.20.10", + "gobject-sys 0.20.10", + "libc", + "memchr", + "smallvec", +] + [[package]] name = "glib" version = "0.21.5" @@ -870,15 +921,37 @@ dependencies = [ "futures-executor", "futures-task", "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", + "gio-sys 0.21.5", + "glib-macros 0.21.5", + "glib-sys 0.21.5", + "gobject-sys 0.21.5", "libc", "memchr", "smallvec", ] +[[package]] +name = "glib-build-tools" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7029c2651d9b5d5a3eea93ec8a1995665c6d3a69ce9bf6042ad9064d134736d8" +dependencies = [ + "gio 0.20.12", +] + +[[package]] +name = "glib-macros" +version = "0.20.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8084af62f09475a3f529b1629c10c429d7600ee1398ae12dd3bf175d74e7145" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "glib-macros" version = "0.21.5" @@ -892,6 +965,16 @@ dependencies = [ "syn", ] +[[package]] +name = "glib-sys" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215" +dependencies = [ + "libc", + "system-deps", +] + [[package]] name = "glib-sys" version = "0.21.5" @@ -902,13 +985,24 @@ dependencies = [ "system-deps", ] +[[package]] +name = "gobject-sys" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda" +dependencies = [ + "glib-sys 0.20.10", + "libc", + "system-deps", +] + [[package]] name = "gobject-sys" version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294" dependencies = [ - "glib-sys", + "glib-sys 0.21.5", "libc", "system-deps", ] @@ -919,7 +1013,7 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2730030ac9db663fd8bfe1e7093742c1cafb92db9c315c9417c29032341fe2f9" dependencies = [ - "glib", + "glib 0.21.5", "graphene-sys", "libc", ] @@ -930,7 +1024,7 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915e32091ea9ad241e4b044af62b7351c2d68aeb24f489a0d7f37a0fc484fd93" dependencies = [ - "glib-sys", + "glib-sys 0.21.5", "libc", "pkg-config", "system-deps", @@ -944,7 +1038,7 @@ checksum = "e755de9d8c5896c5beaa028b89e1969d067f1b9bf1511384ede971f5983aa153" dependencies = [ "cairo-rs", "gdk4", - "glib", + "glib 0.21.5", "graphene-rs", "gsk4-sys", "libc", @@ -959,8 +1053,8 @@ checksum = "7ce91472391146f482065f1041876d8f869057b195b95399414caa163d72f4f7" dependencies = [ "cairo-sys-rs", "gdk4-sys", - "glib-sys", - "gobject-sys", + "glib-sys 0.21.5", + "gobject-sys 0.21.5", "graphene-sys", "libc", "pango-sys", @@ -978,8 +1072,8 @@ dependencies = [ "futures-channel", "gdk-pixbuf", "gdk4", - "gio", - "glib", + "gio 0.21.5", + "glib 0.21.5", "graphene-rs", "gsk4", "gtk4-macros", @@ -996,8 +1090,8 @@ checksum = "c1d422cce9367945916b7a5083eedf67b0a5380d326af1943a0b5cef9afb6e48" dependencies = [ "bitflags", "gdk4", - "glib", - "glib-sys", + "glib 0.21.5", + "glib-sys 0.21.5", "gtk4", "gtk4-layer-shell-sys", "libc", @@ -1010,7 +1104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e386481f3d83ab32e4ee457d9706d4ebbafa29ea013f9ae5066070713d2efacc" dependencies = [ "gdk4-sys", - "glib-sys", + "glib-sys 0.21.5", "gtk4-sys", "libc", "system-deps", @@ -1037,9 +1131,9 @@ dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gdk4-sys", - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.21.5", + "glib-sys 0.21.5", + "gobject-sys 0.21.5", "graphene-sys", "gsk4-sys", "libc", @@ -1575,6 +1669,7 @@ dependencies = [ "env_logger", "freedesktop-desktop-entry", "fuzzy-matcher", + "glib-build-tools", "gtk4", "gtk4-layer-shell", "libc", @@ -1595,8 +1690,8 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1d85e2078077a065bb7fc072783d5bcd4e51b379f22d67107d0a16937eb69" dependencies = [ - "gio", - "glib", + "gio 0.21.5", + "glib 0.21.5", "libc", "pango-sys", ] @@ -1607,8 +1702,8 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4f06627d36ed5ff303d2df65211fc2e52ba5b17bf18dd80ff3d9628d6e06cfd" dependencies = [ - "glib-sys", - "gobject-sys", + "glib-sys 0.21.5", + "gobject-sys 0.21.5", "libc", "system-deps", ] diff --git a/Cargo.toml b/Cargo.toml index d062591..6da1a77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,10 @@ zbus = { version = "4", default-features = false, features = ["tokio"] } # HTTP client for weather API reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json", "blocking"] } +[build-dependencies] +# GResource compilation for bundled icons +glib-build-tools = "0.20" + [features] default = [] # Enable verbose debug logging (for development/testing builds) diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..ed3e8b7 --- /dev/null +++ b/build.rs @@ -0,0 +1,12 @@ +fn main() { + // Compile GResource bundle for icons + glib_build_tools::compile_resources( + &["src/resources/icons"], + "src/resources/icons.gresource.xml", + "icons.gresource", + ); + + // Rerun if icon files change + println!("cargo:rerun-if-changed=src/resources/icons.gresource.xml"); + println!("cargo:rerun-if-changed=src/resources/icons/"); +} diff --git a/src/app.rs b/src/app.rs index f4a1b8f..56d781c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -40,6 +40,10 @@ impl OwlryApp { fn on_activate(app: &Application, args: &CliArgs) { debug!("Activating Owlry"); + // Register bundled icon resources + gio::resources_register_include!("icons.gresource") + .expect("Failed to register icon resources"); + let config = Rc::new(RefCell::new(Config::load_or_default())); let search_engine = config.borrow().providers.search_engine.clone(); let terminal = config.borrow().general.terminal_command.clone(); diff --git a/src/providers/media.rs b/src/providers/media.rs index 3784639..262ce1b 100644 --- a/src/providers/media.rs +++ b/src/providers/media.rs @@ -213,7 +213,7 @@ impl MediaProvider { id: "media-now-playing".to_string(), name, description: Some(format!("{} · Press Enter to {}", player_display, action)), - icon: None, // Using emoji in name instead (▶/⏸) + icon: Some("/org/owlry/launcher/icons/media/music-note.svg".to_string()), provider: ProviderType::MediaPlayer, command, terminal: false, diff --git a/src/providers/pomodoro.rs b/src/providers/pomodoro.rs index ace67e5..dc96dec 100644 --- a/src/providers/pomodoro.rs +++ b/src/providers/pomodoro.rs @@ -236,11 +236,13 @@ impl PomodoroProvider { self.config.work_mins, self.config.break_mins)) }; + // Use bundled tomato icon for the timer display, ignore phase_icon + let _ = phase_icon; // Suppress unused warning self.items.push(LaunchItem { id: "pomo-timer".to_string(), name, description, - icon: Some(phase_icon.to_string()), + icon: Some("/org/owlry/launcher/icons/pomodoro/tomato.svg".to_string()), provider: ProviderType::Pomodoro, command: String::new(), // Info only terminal: false, diff --git a/src/providers/weather.rs b/src/providers/weather.rs index d3841f8..68ba072 100644 --- a/src/providers/weather.rs +++ b/src/providers/weather.rs @@ -367,7 +367,7 @@ impl WeatherProvider { id: "weather-current".to_string(), name, description: Some(details.join(" | ")), - icon: None, // Use emoji in name instead + icon: Some(Self::icon_to_resource_path(&data.icon)), provider: ProviderType::Weather, command: format!("xdg-open 'https://wttr.in/{}'", encoded_location), terminal: false, @@ -375,7 +375,7 @@ impl WeatherProvider { }); } - /// Convert icon name to emoji for display + /// Convert icon name to emoji for display in name text fn weather_icon_to_emoji(icon: &str) -> &'static str { if icon.contains("clear") { "☀️" @@ -395,6 +395,28 @@ impl WeatherProvider { "🌡️" } } + + /// Convert freedesktop-style icon name to bundled GResource path + fn icon_to_resource_path(icon: &str) -> String { + let weather_icon = if icon.contains("clear") { + "wi-day-sunny" + } else if icon.contains("few-clouds") { + "wi-day-cloudy" + } else if icon.contains("overcast") || icon.contains("clouds") { + "wi-cloudy" + } else if icon.contains("fog") { + "wi-fog" + } else if icon.contains("showers") || icon.contains("rain") { + "wi-rain" + } else if icon.contains("snow") { + "wi-snow" + } else if icon.contains("storm") { + "wi-thunderstorm" + } else { + "wi-thermometer" + }; + format!("/org/owlry/launcher/icons/weather/{}.svg", weather_icon) + } } impl Provider for WeatherProvider { diff --git a/src/resources/base.css b/src/resources/base.css index 4197be1..5121fa8 100644 --- a/src/resources/base.css +++ b/src/resources/base.css @@ -67,13 +67,6 @@ opacity: 1; } -/* Emoji icon for widgets (weather, media, pomodoro) */ -.owlry-emoji-icon { - font-size: 24px; - min-width: 32px; - min-height: 32px; -} - /* Result name */ .owlry-result-name { font-size: var(--owlry-font-size, 14px); diff --git a/src/resources/icons.gresource.xml b/src/resources/icons.gresource.xml new file mode 100644 index 0000000..6853565 --- /dev/null +++ b/src/resources/icons.gresource.xml @@ -0,0 +1,21 @@ + + + + + weather/wi-day-sunny.svg + weather/wi-day-cloudy.svg + weather/wi-cloudy.svg + weather/wi-fog.svg + weather/wi-rain.svg + weather/wi-snow.svg + weather/wi-thunderstorm.svg + weather/wi-thermometer.svg + weather/wi-night-clear.svg + + + media/music-note.svg + + + pomodoro/tomato.svg + + diff --git a/src/resources/icons/media/music-note.svg b/src/resources/icons/media/music-note.svg new file mode 100644 index 0000000..d1ec14b --- /dev/null +++ b/src/resources/icons/media/music-note.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/resources/icons/pomodoro/tomato.svg b/src/resources/icons/pomodoro/tomato.svg new file mode 100644 index 0000000..9c1bd93 --- /dev/null +++ b/src/resources/icons/pomodoro/tomato.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/resources/icons/weather/wi-cloudy.svg b/src/resources/icons/weather/wi-cloudy.svg new file mode 100644 index 0000000..c3eaf41 --- /dev/null +++ b/src/resources/icons/weather/wi-cloudy.svg @@ -0,0 +1,18 @@ + + + + + diff --git a/src/resources/icons/weather/wi-day-cloudy.svg b/src/resources/icons/weather/wi-day-cloudy.svg new file mode 100644 index 0000000..cf63543 --- /dev/null +++ b/src/resources/icons/weather/wi-day-cloudy.svg @@ -0,0 +1,26 @@ + + + + + diff --git a/src/resources/icons/weather/wi-day-sunny.svg b/src/resources/icons/weather/wi-day-sunny.svg new file mode 100644 index 0000000..eafcd77 --- /dev/null +++ b/src/resources/icons/weather/wi-day-sunny.svg @@ -0,0 +1,27 @@ + + + + + diff --git a/src/resources/icons/weather/wi-fog.svg b/src/resources/icons/weather/wi-fog.svg new file mode 100644 index 0000000..cf06f8f --- /dev/null +++ b/src/resources/icons/weather/wi-fog.svg @@ -0,0 +1,18 @@ + + + + + diff --git a/src/resources/icons/weather/wi-night-clear.svg b/src/resources/icons/weather/wi-night-clear.svg new file mode 100644 index 0000000..053b2de --- /dev/null +++ b/src/resources/icons/weather/wi-night-clear.svg @@ -0,0 +1,13 @@ + + + + + diff --git a/src/resources/icons/weather/wi-rain.svg b/src/resources/icons/weather/wi-rain.svg new file mode 100644 index 0000000..7ecef3f --- /dev/null +++ b/src/resources/icons/weather/wi-rain.svg @@ -0,0 +1,23 @@ + + + + + diff --git a/src/resources/icons/weather/wi-snow.svg b/src/resources/icons/weather/wi-snow.svg new file mode 100644 index 0000000..56afebf --- /dev/null +++ b/src/resources/icons/weather/wi-snow.svg @@ -0,0 +1,27 @@ + + + + + diff --git a/src/resources/icons/weather/wi-thermometer.svg b/src/resources/icons/weather/wi-thermometer.svg new file mode 100644 index 0000000..a0fa8d0 --- /dev/null +++ b/src/resources/icons/weather/wi-thermometer.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/src/resources/icons/weather/wi-thunderstorm.svg b/src/resources/icons/weather/wi-thunderstorm.svg new file mode 100644 index 0000000..52555ec --- /dev/null +++ b/src/resources/icons/weather/wi-thunderstorm.svg @@ -0,0 +1,21 @@ + + + + + diff --git a/src/ui/result_row.rs b/src/ui/result_row.rs index 2684644..124a98d 100644 --- a/src/ui/result_row.rs +++ b/src/ui/result_row.rs @@ -25,53 +25,46 @@ impl ResultRow { .margin_end(12) .build(); - // Icon - handle file paths, icon names, emoji widgets, and fallbacks - let icon_widget: Widget = if let Some(icon_name) = &item.icon { - let img = if icon_name.starts_with('/') { + // Icon - handle GResource paths, file paths, icon names, and fallbacks + let icon_widget: Widget = if let Some(icon_path) = &item.icon { + let img = if icon_path.starts_with("/org/owlry/launcher/icons/") { + // GResource path - load from bundled resources + Image::from_resource(icon_path) + } else if icon_path.starts_with('/') { // Absolute file path - Image::from_file(icon_name) + Image::from_file(icon_path) } else { - Image::from_icon_name(icon_name) + // Icon theme name + Image::from_icon_name(icon_path) }; img.set_pixel_size(32); img.add_css_class("owlry-result-icon"); img.upcast() } else { // Default icon based on provider type - // For widgets, use emoji labels (more reliable than icon themes) - match item.provider { - crate::providers::ProviderType::Weather => { - Self::create_emoji_icon("🌤️") - } - crate::providers::ProviderType::MediaPlayer => { - Self::create_emoji_icon("🎵") - } - crate::providers::ProviderType::Pomodoro => { - Self::create_emoji_icon("🍅") - } - _ => { - let default_icon = match item.provider { - crate::providers::ProviderType::Application => "application-x-executable", - crate::providers::ProviderType::Bookmarks => "user-bookmarks", - crate::providers::ProviderType::Calculator => "accessories-calculator", - crate::providers::ProviderType::Clipboard => "edit-paste", - crate::providers::ProviderType::Command => "utilities-terminal", - crate::providers::ProviderType::Dmenu => "view-list-symbolic", - crate::providers::ProviderType::Emoji => "face-smile", - crate::providers::ProviderType::Files => "folder", - crate::providers::ProviderType::Scripts => "application-x-executable", - crate::providers::ProviderType::Ssh => "network-server", - crate::providers::ProviderType::System => "system-shutdown", - crate::providers::ProviderType::Uuctl => "system-run", - crate::providers::ProviderType::WebSearch => "web-browser", - _ => "application-x-executable", - }; - let img = Image::from_icon_name(default_icon); - img.set_pixel_size(32); - img.add_css_class("owlry-result-icon"); - img.upcast() - } - } + let default_icon = match item.provider { + crate::providers::ProviderType::Application => "application-x-executable", + crate::providers::ProviderType::Bookmarks => "user-bookmarks", + crate::providers::ProviderType::Calculator => "accessories-calculator", + crate::providers::ProviderType::Clipboard => "edit-paste", + crate::providers::ProviderType::Command => "utilities-terminal", + crate::providers::ProviderType::Dmenu => "view-list-symbolic", + crate::providers::ProviderType::Emoji => "face-smile", + crate::providers::ProviderType::Files => "folder", + crate::providers::ProviderType::Scripts => "application-x-executable", + crate::providers::ProviderType::Ssh => "network-server", + crate::providers::ProviderType::System => "system-shutdown", + crate::providers::ProviderType::Uuctl => "system-run", + crate::providers::ProviderType::WebSearch => "web-browser", + // Widget providers now have icons set, but keep fallbacks + crate::providers::ProviderType::Weather => "weather-clear-symbolic", + crate::providers::ProviderType::MediaPlayer => "media-playback-start-symbolic", + crate::providers::ProviderType::Pomodoro => "alarm-symbolic", + }; + let img = Image::from_icon_name(default_icon); + img.set_pixel_size(32); + img.add_css_class("owlry-result-icon"); + img.upcast() }; // Text container @@ -142,16 +135,4 @@ impl ResultRow { row } - - /// Create an emoji-based icon widget for widgets (weather, media, pomodoro) - /// More reliable than icon themes which may not have all icons - fn create_emoji_icon(emoji: &str) -> Widget { - let label = Label::builder() - .label(emoji) - .width_request(32) - .height_request(32) - .build(); - label.add_css_class("owlry-emoji-icon"); - label.upcast() - } }