diff --git a/Cargo.toml b/Cargo.toml index 23df9191b..958ac4aaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ zbus = { version = "5.11.0", default-features = false, features = ["tokio"] } tracing = "0.1" tracing-subscriber = { version = "0.3.20", features = ["env-filter"] } tracing-log = "0.2.0" +tracing-journald = "0.3" tokio = { version = "1.47.1", features = ["full"] } cosmic-config = { git = "https://github.com/pop-os/libcosmic" } serde = { version = "1.0.228", features = ["derive"] } @@ -70,14 +71,6 @@ lto = "thin" [workspace.metadata.cargo-machete] ignored = ["libcosmic"] -# [patch."https://github.com/pop-os/libcosmic"] -# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "" } -# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "" } -# iced_futures = { git = "https://github.com/pop-os/libcosmic//", branch = "" } -# cosmic-config = { path = "../libcosmic/cosmic-config" } -# libcosmic = { path = "../libcosmic" } -# iced_futures = { path = "../libcosmic/iced/futures" } - # [patch."https://github.com/pop-os/winit.git"] # winit = { git = "https://github.com/rust-windowing/winit.git", rev = "241b7a80bba96c91fa3901729cd5dec66abb9be4" } # winit = { path = "../winit" } diff --git a/cosmic-app-list/Cargo.toml b/cosmic-app-list/Cargo.toml index 17474d991..4cf2aa3c5 100644 --- a/cosmic-app-list/Cargo.toml +++ b/cosmic-app-list/Cargo.toml @@ -25,5 +25,6 @@ tokio.workspace = true tracing-log.workspace = true tracing-subscriber.workspace = true tracing.workspace = true +tracing-journald = "0.3" url = "2.5.7" zbus.workspace = true diff --git a/cosmic-app-list/src/app.rs b/cosmic-app-list/src/app.rs index 8fc6d7397..b3f332207 100755 --- a/cosmic-app-list/src/app.rs +++ b/cosmic-app-list/src/app.rs @@ -54,7 +54,16 @@ use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::Sta use futures::future::pending; use iced::{Alignment, Background, Length}; use rustc_hash::FxHashMap; -use std::{borrow::Cow, path::PathBuf, rc::Rc, str::FromStr, time::Duration}; +use std::{ + borrow::Cow, + cell::RefCell, + collections::HashMap, + path::{Path, PathBuf}, + rc::Rc, + str::FromStr, + time::Duration, + sync::{Mutex, OnceLock}, +}; use switcheroo_control::Gpu; use tokio::time::sleep; use url::Url; diff --git a/cosmic-app-list/src/main.rs b/cosmic-app-list/src/main.rs index d06ff7b02..9dbfe191a 100644 --- a/cosmic-app-list/src/main.rs +++ b/cosmic-app-list/src/main.rs @@ -1,10 +1,32 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only +use tracing_subscriber::{EnvFilter, fmt, prelude::*}; + const VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() -> cosmic::iced::Result { - tracing_subscriber::fmt::init(); + let fmt_layer = fmt::layer().with_target(false); + let filter_layer = EnvFilter::try_from_default_env().unwrap_or_else(|_| { + if cfg!(debug_assertions) { + EnvFilter::new(format!("warn,{}=debug", env!("CARGO_CRATE_NAME"))) + } else { + EnvFilter::new("warn") + } + }); + + if let Ok(journal_layer) = tracing_journald::layer() { + tracing_subscriber::registry() + .with(journal_layer) + .with(filter_layer.clone()) + .init(); + } else { + tracing_subscriber::registry() + .with(fmt_layer) + .with(filter_layer) + .init(); + } + let _ = tracing_log::LogTracer::init(); tracing::info!("Starting cosmic-app-list with version {VERSION}"); diff --git a/cosmic-applet-minimize/src/lib.rs b/cosmic-applet-minimize/src/lib.rs index e836290d9..3e684efce 100644 --- a/cosmic-applet-minimize/src/lib.rs +++ b/cosmic-applet-minimize/src/lib.rs @@ -16,7 +16,9 @@ use cosmic::{ sctk::reexports::calloop, toplevel_info::ToplevelInfo, wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, }, - desktop::fde, + desktop::{ + DesktopEntryCache, DesktopLookupContext, DesktopResolveOptions, fde, resolve_desktop_entry, + }, iced::{ self, Length, Limits, Subscription, id::Id as WidgetId, @@ -55,7 +57,7 @@ pub struct App { struct Minimize { core: cosmic::app::Core, locales: Vec, - desktop_entries: Vec, + desktop_entries: DesktopEntryCache, apps: Vec, tx: Option>, overflow_popup: Option, @@ -85,37 +87,26 @@ impl Minimize { index } - fn find_new_desktop_entry(&mut self, appid: &str) -> fde::DesktopEntry { - let unicase_appid = fde::unicase::Ascii::new(appid); - - let de = if let Some(de) = fde::find_app_by_id(&self.desktop_entries, unicase_appid) { - de - } else { - // Update desktop entries in case it was not found. - self.update_desktop_entries(); - if let Some(appid) = fde::find_app_by_id(&self.desktop_entries, unicase_appid) { - appid - } else { - tracing::warn!(appid, "could not find desktop entry for app"); - let mut entry = fde::DesktopEntry { - appid: appid.to_owned(), - groups: Default::default(), - path: Default::default(), - ubuntu_gettext_domain: None, - }; - entry.add_desktop_entry("Name".to_string(), appid.to_owned()); - return entry; - } - }; - - de.clone() + // Proton/Wine behavior: see dock applet notes — shared resolver in libcosmic + // applies the Proton/Wine title matching and Game-category restriction. + fn find_new_desktop_entry(&mut self, info: &ToplevelInfo) -> fde::DesktopEntry { + let mut ctx = DesktopLookupContext::new(info.app_id.clone()); + if !info.identifier.is_empty() { + ctx = ctx.with_identifier(info.identifier.clone()); + } + if !info.title.is_empty() { + ctx = ctx.with_title(info.title.clone()); + } + resolve_desktop_entry( + &mut self.desktop_entries, + &ctx, + &DesktopResolveOptions::default(), + ) } // Cache all desktop entries to use when new apps are added to the dock. fn update_desktop_entries(&mut self) { - self.desktop_entries = fde::Iter::new(fde::default_paths()) - .filter_map(|p| fde::DesktopEntry::from_path(p, Some(&self.locales)).ok()) - .collect::>(); + self.desktop_entries.refresh(); } } @@ -136,9 +127,11 @@ impl cosmic::Application for Minimize { const APP_ID: &'static str = "com.system76.CosmicAppletMinimize"; fn init(core: cosmic::app::Core, _flags: ()) -> (Self, app::Task) { + let locales = fde::get_languages_from_env(); let mut app = Self { core, - locales: fde::get_languages_from_env(), + desktop_entries: DesktopEntryCache::new(locales.clone()), + locales, ..Default::default() }; @@ -181,7 +174,7 @@ impl cosmic::Application for Minimize { }) { if apps[pos].toplevel_info.app_id != toplevel_info.app_id { apps[pos].desktop_entry = - self.find_new_desktop_entry(&toplevel_info.app_id); + self.find_new_desktop_entry(&toplevel_info); apps[pos].icon_source = fde::IconSource::from_unknown( apps[pos] .desktop_entry @@ -191,7 +184,7 @@ impl cosmic::Application for Minimize { } apps[pos].toplevel_info = toplevel_info; } else { - let desktop_entry = self.find_new_desktop_entry(&toplevel_info.app_id); + let desktop_entry = self.find_new_desktop_entry(&toplevel_info); apps.push(App { name: desktop_entry diff --git a/cosmic-applet-status-area/Cargo.toml b/cosmic-applet-status-area/Cargo.toml index 6880270b8..1e62b9e9d 100644 --- a/cosmic-applet-status-area/Cargo.toml +++ b/cosmic-applet-status-area/Cargo.toml @@ -14,3 +14,4 @@ tracing.workspace = true tracing-log.workspace = true tracing-subscriber.workspace = true zbus.workspace = true +tracing-journald.workspace = true diff --git a/cosmic-applet-status-area/src/components/app.rs b/cosmic-applet-status-area/src/components/app.rs index 224f43247..da8417b3c 100644 --- a/cosmic-applet-status-area/src/components/app.rs +++ b/cosmic-applet-status-area/src/components/app.rs @@ -95,23 +95,12 @@ impl App { let overflow_index = self.overflow_index().unwrap_or(0); let children = self.menus.iter().skip(overflow_index).map(|(id, menu)| { mouse_area( - match menu.icon_pixmap() { - Some(icon) if menu.icon_name() == "" => self - .core - .applet - .icon_button_from_handle(icon.clone().symbolic(true)), - _ => self.core.applet.icon_button(menu.icon_name()), - } - .on_press_down(Msg::TogglePopup(*id)), + self.icon_button_for_menu(menu) + .on_press_down(Msg::TogglePopup(*id)), ) .on_enter(Msg::Hovered(*id)) .into() }); - let theme = self.core.system_theme(); - let cosmic = theme.cosmic(); - let corners = cosmic.corner_radii; - let pad = corners.radius_m[0]; - self.core .applet .popup_container(container( @@ -126,6 +115,23 @@ impl App { )) .into() } + + fn icon_button_for_menu<'a>( + &self, + menu: &'a status_menu::State, + ) -> cosmic::widget::Button<'a, Msg> { + if let Some(icon) = menu.icon_pixmap() { + if menu.icon_name().is_empty() { + self.core + .applet + .icon_button_from_handle(icon.clone().symbolic(true)) + } else { + self.core.applet.icon_button_from_handle(icon.clone()) + } + } else { + self.core.applet.icon_button(menu.icon_name()) + } + } } impl cosmic::Application for App { @@ -453,14 +459,8 @@ impl cosmic::Application for App { .take(overflow_index.unwrap_or(self.menus.len())) .map(|(id, menu)| { mouse_area( - match menu.icon_pixmap() { - Some(icon) if menu.icon_name() == "" => self - .core - .applet - .icon_button_from_handle(icon.clone().symbolic(true)), - _ => self.core.applet.icon_button(menu.icon_name()), - } - .on_press_down(if menu.item.menu_proxy().is_some() { + self.icon_button_for_menu(menu).on_press_down(if menu.item.menu_proxy().is_some() + { Msg::TogglePopup(*id) } else { Msg::StatusMenu((*id, status_menu::Msg::Click(0, true))) diff --git a/cosmic-applet-status-area/src/components/status_menu.rs b/cosmic-applet-status-area/src/components/status_menu.rs index 5836510ae..5dc332936 100644 --- a/cosmic-applet-status-area/src/components/status_menu.rs +++ b/cosmic-applet-status-area/src/components/status_menu.rs @@ -8,9 +8,30 @@ use cosmic::{ iced, widget::icon, }; +use std::{ + cell::RefCell, + path::PathBuf, +}; +use tracing::warn; use crate::subscriptions::status_notifier_item::{IconUpdate, Layout, StatusNotifierItem}; +#[derive(Debug, Clone, PartialEq, Eq)] +enum IconLogState { + NamedResolved { + name: String, + path: PathBuf, + }, + NamedMissing { + name: String, + }, + Pixmap { + width: i32, + height: i32, + bytes: usize, + }, +} + #[derive(Clone, Debug)] pub enum Msg { Layout(Result), @@ -27,6 +48,7 @@ pub struct State { // TODO handle icon with multiple sizes? icon_pixmap: Option, click_event: Option<(i32, bool)>, + icon_log: RefCell>, } impl State { @@ -39,6 +61,7 @@ impl State { icon_name: String::new(), icon_pixmap: None, click_event: None, + icon_log: RefCell::new(None), }, iced::Task::none(), ) @@ -63,25 +86,68 @@ impl State { Msg::Icon(update) => { match update { IconUpdate::Name(name) => { - self.icon_name = name; + if self.icon_name != name { + self.icon_name = name; + self.log_named_icon(); + } } IconUpdate::Pixmap(icons) => { - self.icon_pixmap = icons - .into_iter() - .max_by_key(|i| (i.width, i.height)) - .map(|mut i| { - if i.width <= 0 || i.height <= 0 || i.bytes.is_empty() { - // App sent invalid icon data during initialization - show placeholder until NewIcon signal - eprintln!("Skipping invalid icon: {}x{} with {} bytes, app may still be initializing", - i.width, i.height, i.bytes.len()); - return icon::from_name("dialog-question").symbolic(true).handle(); - } - // Convert ARGB to RGBA - for pixel in i.bytes.chunks_exact_mut(4) { - pixel.rotate_left(1); - } - icon::from_raster_pixels(i.width as u32, i.height as u32, i.bytes) - }); + if icons.is_empty() { + self.record_icon_log( + IconLogState::Pixmap { + width: 0, + height: 0, + bytes: 0, + }, + || { + warn!( + target: "cosmic_panel::tray::icon", + item = %self.item.name(), + "Status notifier item sent empty icon pixmap list" + ); + }, + ); + self.icon_pixmap = None; + } else { + self.icon_pixmap = icons + .into_iter() + .max_by_key(|i| (i.width, i.height)) + .map(|mut i| { + if i.width <= 0 || i.height <= 0 || i.bytes.is_empty() { + warn!( + target: "cosmic_panel::tray::icon", + item = %self.item.name(), + width = i.width, + height = i.height, + bytes = i.bytes.len(), + "Skipping invalid tray icon pixmap dimensions" + ); + return icon::from_name("dialog-question") + .symbolic(true) + .handle(); + } + // Convert ARGB to RGBA + for pixel in i.bytes.chunks_exact_mut(4) { + pixel.rotate_left(1); + } + + // Do not log success for pixmap updates to reduce noise. + self.record_icon_log( + IconLogState::Pixmap { + width: i.width, + height: i.height, + bytes: i.bytes.len(), + }, + || {}, + ); + + icon::from_raster_pixels( + i.width as u32, + i.height as u32, + i.bytes, + ) + }); + } } } @@ -177,6 +243,69 @@ impl State { let _ = menu_proxy.event(0, "closed", &0i32.into(), 0).await; }); } + + fn record_icon_log(&self, new_state: IconLogState, log: impl FnOnce()) { + let mut current = self.icon_log.borrow_mut(); + if current.as_ref() != Some(&new_state) { + log(); + *current = Some(new_state); + } + } + + fn log_named_icon(&self) { + let name = self.icon_name.trim(); + if name.is_empty() { + self.record_icon_log( + IconLogState::NamedMissing { + name: String::new(), + }, + || { + warn!( + target: "cosmic_panel::tray::icon", + item = %self.item.name(), + "Status notifier icon name is empty" + ); + }, + ); + return; + } + + // Prefer SVG only for symbolic icons; otherwise allow raster picks. + let named = if name.ends_with("-symbolic") { + icon::from_name(name).prefer_svg(true) + } else { + icon::from_name(name) + }; + let path = named.clone().path(); + + match path { + Some(path) => { + // Success case: record state to avoid repeated warns, but do not log. + self.record_icon_log( + IconLogState::NamedResolved { + name: name.to_string(), + path: path.clone(), + }, + || {}, + ); + } + None => { + self.record_icon_log( + IconLogState::NamedMissing { + name: name.to_string(), + }, + || { + warn!( + target: "cosmic_panel::tray::icon", + item = %self.item.name(), + icon_name = name, + "Tray icon name missing from current icon themes" + ); + }, + ); + } + } + } } fn layout_view(layout: &Layout, expanded: Option) -> cosmic::Element<'_, Msg> { diff --git a/cosmic-applet-status-area/src/main.rs b/cosmic-applet-status-area/src/main.rs index 213110e4d..708d94fe7 100644 --- a/cosmic-applet-status-area/src/main.rs +++ b/cosmic-applet-status-area/src/main.rs @@ -1,10 +1,33 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only +use tracing_subscriber::{EnvFilter, fmt, prelude::*}; + const VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() -> cosmic::iced::Result { - tracing_subscriber::fmt::init(); + // Journald first; fallback to fmt. Use project defaults for filter. + let fmt_layer = fmt::layer().with_target(false); + let filter_layer = EnvFilter::try_from_default_env().unwrap_or_else(|_| { + if cfg!(debug_assertions) { + EnvFilter::new(format!("warn,{}=debug", env!("CARGO_CRATE_NAME"))) + } else { + EnvFilter::new("warn") + } + }); + + if let Ok(journal_layer) = tracing_journald::layer() { + tracing_subscriber::registry() + .with(journal_layer) + .with(filter_layer.clone()) + .init(); + } else { + tracing_subscriber::registry() + .with(fmt_layer) + .with(filter_layer) + .init(); + } + let _ = tracing_log::LogTracer::init(); tracing::info!("Starting status-area applet with version {VERSION}"); diff --git a/cosmic-applets/Cargo.toml b/cosmic-applets/Cargo.toml index c39ad7208..652edcfe2 100644 --- a/cosmic-applets/Cargo.toml +++ b/cosmic-applets/Cargo.toml @@ -24,3 +24,4 @@ libcosmic.workspace = true tracing.workspace = true tracing-subscriber.workspace = true tracing-log.workspace = true +tracing-journald.workspace = true diff --git a/cosmic-applets/src/main.rs b/cosmic-applets/src/main.rs index f62bbf210..c95cb3328 100644 --- a/cosmic-applets/src/main.rs +++ b/cosmic-applets/src/main.rs @@ -1,10 +1,32 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only +use tracing_subscriber::{EnvFilter, fmt, prelude::*}; + const VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() -> cosmic::iced::Result { - tracing_subscriber::fmt().with_env_filter("warn").init(); + // Journald first; fallback to fmt. Use project defaults for filter. + let fmt_layer = fmt::layer().with_target(false); + let filter_layer = EnvFilter::try_from_default_env().unwrap_or_else(|_| { + if cfg!(debug_assertions) { + EnvFilter::new(format!("warn,{}=debug", env!("CARGO_CRATE_NAME"))) + } else { + EnvFilter::new("warn") + } + }); + + if let Ok(journal_layer) = tracing_journald::layer() { + tracing_subscriber::registry() + .with(journal_layer) + .with(filter_layer.clone()) + .init(); + } else { + tracing_subscriber::registry() + .with(fmt_layer) + .with(filter_layer) + .init(); + } let _ = tracing_log::LogTracer::init(); let Some(applet) = std::env::args().next() else { diff --git a/cosmic-panel-button/src/lib.rs b/cosmic-panel-button/src/lib.rs index 8b94c8aa6..05129f0e5 100644 --- a/cosmic-panel-button/src/lib.rs +++ b/cosmic-panel-button/src/lib.rs @@ -2,7 +2,10 @@ // SPDX-License-Identifier: GPL-3.0-only use config::{CosmicPanelButtonConfig, IndividualConfig, Override}; -use cosmic::desktop::fde::{self, DesktopEntry, get_languages_from_env}; +use cosmic::desktop::{ + DesktopEntryData, + fde::{self, DesktopEntry, IconSource, get_languages_from_env}, +}; use cosmic::{ Task, app, applet::{ @@ -12,7 +15,7 @@ use cosmic::{ iced::{self, Length}, iced_widget::row, surface, - widget::{Id, autosize, vertical_space}, + widget::{self, Id, autosize, vertical_space}, }; use cosmic_config::{Config, CosmicConfigEntry}; use std::{env, fs, process::Command, sync::LazyLock}; @@ -24,7 +27,7 @@ static AUTOSIZE_MAIN_ID: LazyLock = LazyLock::new(|| Id::new("autosize-main" #[derive(Debug, Clone, Default)] struct Desktop { name: String, - icon: Option, + icon: Option, exec: String, } @@ -165,11 +168,8 @@ impl cosmic::Application for Button { { cosmic::Element::from( self.core.applet.applet_tooltip::( - self.icon_button_from_handle( - cosmic::widget::icon::from_name(self.desktop.icon.clone().unwrap()) - .handle(), - ) - .on_press_down(Msg::Press), + self.icon_button_from_handle(self.desktop.icon.clone().unwrap()) + .on_press_down(Msg::Press), self.desktop.name.clone(), false, Msg::Surface, @@ -219,16 +219,18 @@ pub fn run() -> iced::Result { path.push(&filename); if let Ok(bytes) = fs::read_to_string(&path) { if let Ok(entry) = DesktopEntry::from_str(&path, &bytes, Some(&locales)) { + let data = DesktopEntryData::from_desktop_entry(&locales, entry); + let icon = match data.icon { + IconSource::Name(name) => Some(widget::icon::from_name(name).handle()), + IconSource::Path(path) => Some(widget::icon::from_path(path)), + }; + let exec = data + .exec + .unwrap_or_else(|| panic!("Desktop file '{filename}' doesn't have `Exec`")); desktop = Some(Desktop { - name: entry.name(&locales).map_or_else( - || panic!("Desktop file '{filename}' doesn't have `Name`"), - |x| x.to_string(), - ), - icon: entry.icon().map(|x| x.to_string()), - exec: entry.exec().map_or_else( - || panic!("Desktop file '{filename}' doesn't have `Exec`"), - |x| x.to_string(), - ), + name: data.name, + icon, + exec, }); break; } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ff100edcb..f35f369cc 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,3 @@ [toolchain] channel = "1.90.0" +components = ["clippy", "rustfmt"]