Add configurable window icons to workspace indicators#495
Add configurable window icons to workspace indicators#495gwiazdorrr wants to merge 14 commits intoMalpenZibo:mainfrom
Conversation
|
I like the idea of being able to see the what windows are open on which workspace. I feel like that goes against the idea of ashell being a "ready to go" status bar that works out of the box without any configuration. |
|
Agreeing with @1randomguy |
77b03fd to
6ef65bb
Compare
|
I tried it (niri), and I noticed that not all programs are shown, for example nautilus, gnome-calendar, and spotify do not show an icon. One more thing that I noticed is that it shows only one icon per program per workspace, I think this is something worth thinking about whether we could show that there are multiple windows open. Lastly, I think that the icons making the indicators bigger makes it kind of difficult to distinguishe the currently active workspace from the others. |
|
@1randomguy @gwiazdorrr potentially #475 has some fixes in icon discovery that also help here. Not sure if what you mention is an icon or process discovery issue though |
| if !seen.insert(class_lower.clone()) { | ||
| continue; | ||
| } | ||
| if let Some(xdg) = xdg_icons::get_icon_from_name(&class_lower) { |
There was a problem hiding this comment.
if we can't get an icon, we might want to put a placeholder?
|
I had a look at #475 , and it seems like he uses the statusnotifier icon first and falls back to the For this one, I'd argue that using the |
6a26771 to
ae0e0e6
Compare
From the list I can verify that spotify works here, but the icon for nautilus is missing. I'll investigate. In the meantime, I rebased and pushed support for both fallback icon (using |
That's very interesting, still doesn't work here :P The fallback icon and color for active workspaces (#347 ) work great though 👍
|
|
One more thing that I was wondering about is whether or not a small space or visual divider between the workspace name and the icons would help with legibility, as I feel like the numbers of the workspaces can get a bit harder to make out when there are a lot of icons. But thats just an idea |
ae0e0e6 to
e3aed91
Compare
|
Fixed the missing nautilus icon, hopefully spotify now also works for you :) |
|
I've just added parsing of .desktop files that fixed the last "dot icon" for me (cura). |
clotodex
left a comment
There was a problem hiding this comment.
As far as I got today with review
|
please fix the conflict :) |
|
also generally check out #537 for performance, maybe this PR can adopt some of the same improvements |
clotodex
left a comment
There was a problem hiding this comment.
We are getting there! Thank you for the contributions
Looking at the code this feels in many cases AI generated. This is not a problem, but makes it REALLY hard to understand the code quickly, and every change and review changes a lot of things again needing to understand the code from scratch. Not sure how to deal with this yes.
@romanstingler could you do me a favor and check out the xdg_icons.rs file, since you worked on the tray icons and icon discovery (if not, feel free to tag who did if I am mistaken)
| static BROADCASTER: OnceCell<broadcast::Sender<ServiceEvent<CompositorService>>> = | ||
| OnceCell::const_new(); | ||
|
|
||
| static COLLECT_WINDOW_CLASSES: AtomicBool = AtomicBool::new(false); |
There was a problem hiding this comment.
I feel weird about using an atomic boolean to essentially communicate a config field
There was a problem hiding this comment.
Need to familiarize myself more with tokio. Or just make hyprland/niri always collect windows classes and process them in workspaces.rs. Not sure which is the right call atm.
There was a problem hiding this comment.
So every kind of configuration should be sent in the subscribe() service function. In theory, it should also be used in the Subscription::run_with(TypeId::of::<Self>() as id, for example run_with((TypeId::of::<Self>(), configuration_x), .... This tells iced to recreate the subscription if the id changes.
TypeId::of::() is constant, so adding the configuration_x in the tuple should recreate the subscription only when the configuration_x value changes.
In this case, we should send an indicator_format == WorkspaceIndicatorFormat::NameAndIcons to know if we had to collect the class name, if I understand correctly the changes.
| .map(|class| class.to_lowercase()) | ||
| .unique() | ||
| .map(|class_lower| { | ||
| xdg_icons::get_icon_from_name(&class_lower).unwrap_or_else(xdg_icons::fallback_icon) |
There was a problem hiding this comment.
this swallows errors, is there a way to somehow only swallow "not found"?
There was a problem hiding this comment.
I don't think there anything other than "not found" at this stage; iced images are loaded lazily and all the debug logs are really just progress, not real failures.
|
|
||
| impl Workspaces { | ||
| pub fn new(config: WorkspacesModuleConfig) -> Self { | ||
| set_collect_window_classes( |
There was a problem hiding this comment.
as discussed with the atomic bool, this is a strong anti-pattern and a new messaging channel for config which we should not do
There was a problem hiding this comment.
This part should go in the subscription method of the module. We should pass config.indicator_format == WorkspaceIndicatorFormat::NameAndIcons as parameter to the service subscribe method
|
I applied my performance changes and a few other suggestions to the I think that we can rework the whole thing in future, There are things missing (also in the tray module) where we invalidate the caches if the user changes the theme of the system. |
This PR does contain some AI assistance. The "AI code review fatigue" is something I struggle with myself at work, sorry for bringing this upon you. Rebasing surely did not help the clarity, either. The solution is, well, higher standards on the submitter's end. |
Display application icons next to workspace names, similar to waybar's
window-rewrite feature.
New config options:
[workspaces]
indicator_format = "NameAndIcons" # or "Name" (default)
window_icons = { "firefox" = "", "code" = "" }
default_window_icon = ""
Window class to icon mappings use case-insensitive matching with an
optional default icon. Window class collection from compositors is gated
behind an AtomicBool flag so the feature has no cost when disabled.
Move the XDG icon resolution pipeline (direct lookup, fuzzy matching, prefix matching) from the tray module into a shared xdg_icons module. Workspace icons now resolve automatically via XDG without requiring manual glyph config, with priority: user config > XDG lookup > default.
Drop window_icons and default_window_icon config fields, remove WorkspaceIcon enum, and inline the tray get_icon_from_name wrapper. Workspace icons now resolve exclusively via XDG desktop icon lookup.
get_icon_from_name() is called on every compositor event for every unique window class. Add a process-wide HashMap cache so the expensive resolution chain (freedesktop lookup, similar names, prefix match) only runs once per icon name.
…lasses Add configurable active_workspace_colors to override workspace indicator colors for the active workspace, following the same per-monitor pattern as workspace_colors. Use Nerd Font point glyph as fallback when XDG icon lookup fails for a window class.
Replace collect-then-iterate with an eager find_similar_icon that resolves each candidate immediately and returns on first hit. The old 5-candidate collect limit could cut off valid matches that sorted alphabetically after earlier misses.
…dd debug logging Index all dot-suffixes of reverse-DNS desktop file stems (e.g. `com.ultimaker.cura` also registers `ultimaker.cura` and `cura` as keys), fixing lookup failures where `StartupWMClass` is absent Guard `get_icon_from_name` and `find_similar_icon` against empty/blank icon names Add `debug!` trace at each step of `lookup_icon` and `find_desktop_icon` to make resolution failures diagnosable via `RUST_LOG=ashell::services::xdg_icons=debug`
- UiWorkspace.icons: Vec -> Option<Vec> (None when feature is off) - resolve_workspace_icons: drop config param, gate at call sites with .then(|| ...) - Replace HashSet dedup loop with .unique().map().collect() iterator chain - Remove needs_window_classes helper, inline condition at both call sites - Import set_collect_window_classes via use instead of full path - Inline let i = i as usize into colors.get(i as usize) - Replace mut children + extend with iter::once().chain().collect()
85eb968 to
1e3ecd4
Compare
MalpenZibo
left a comment
There was a problem hiding this comment.
I see the new xdg_icons.rs. Please be sure to NOT introduce regression to the Tray module. I notice that you deleted some code from the tray module. I presume that xdg_icons is the new home of that code. Am I right, or are there some changes?
| pub text_color: AppearanceColor, | ||
| pub workspace_colors: Vec<AppearanceColor>, | ||
| pub special_workspace_colors: Option<Vec<AppearanceColor>>, | ||
| pub active_workspace_colors: Option<Vec<AppearanceColor>>, |
There was a problem hiding this comment.
This new config? Is it related to the window icon? Could be placed in a dedicated PR?
There was a problem hiding this comment.
I could extract it, but: it is only needed for when icons are enabled, as then with the visual clutter you might not notice the "capsule" resizing.
| static BROADCASTER: OnceCell<broadcast::Sender<ServiceEvent<CompositorService>>> = | ||
| OnceCell::const_new(); | ||
|
|
||
| static COLLECT_WINDOW_CLASSES: AtomicBool = AtomicBool::new(false); |
There was a problem hiding this comment.
So every kind of configuration should be sent in the subscribe() service function. In theory, it should also be used in the Subscription::run_with(TypeId::of::<Self>() as id, for example run_with((TypeId::of::<Self>(), configuration_x), .... This tells iced to recreate the subscription if the id changes.
TypeId::of::() is constant, so adding the configuration_x in the tuple should recreate the subscription only when the configuration_x value changes.
In this case, we should send an indicator_format == WorkspaceIndicatorFormat::NameAndIcons to know if we had to collect the class name, if I understand correctly the changes.
|
|
||
| impl Workspaces { | ||
| pub fn new(config: WorkspacesModuleConfig) -> Self { | ||
| set_collect_window_classes( |
There was a problem hiding this comment.
This part should go in the subscription method of the module. We should pass config.indicator_format == WorkspaceIndicatorFormat::NameAndIcons as parameter to the service subscribe method
MalpenZibo
left a comment
There was a problem hiding this comment.
Some changes are not directly related to this PR, but let's fix them anyway 😄
Co-authored-by: Simone Camito <32327779+MalpenZibo@users.noreply.github.com>




Display application icons next to workspace names, similar to waybar's window-rewrite feature.
New config options:
Window class to icon mappings use case-insensitive matching with an optional default icon. Window class collection from compositors is gated behind an AtomicBool flag so the feature has no cost when disabled.
This is how it looks on my setup. I find this extremely useful:
