Repository navigation
feat(processes): show application icons in process table and tree - #74
Conversation
|
this look very nice I will review this and let you know, but the performance overhead is what I am most interested in, so you said there is some index? I am curious if you query all this icon information and data on each refresh or if there is some mechanism to speed this up. If there is a cache used, then that's great I would definitely recommend to cache these instead of loading on each refresh, but then - how large is the cache, how much does it increase the RAM usage? If you don't have this data it's fine, I will measure it myself. If the difference is too large or overhead too dramatic, I will see if we can make the toggle not only hide the icons but also drop the overhead caused by displaying them. |
|
there is a problem with Qt5 integration and you need to rebase this to make it possible to merge. I fixed both issues but I can't push commits into your branch: |
11d8b48 to
563ee0c
Compare
|
ok I fixed the auth issue |
|
it would be nice if we could also update that tree view screenshot in readme file, it should probably have same resolution / scale as other screens so the table is uniform |
|
anyway, before you do it, I think we should also swap that PID and name columns, so that name is actually used for the tree nodes instead of PID, same way as in that other tool you show, it looks better than PIDs |
|
BTW just interesting obsevation - this adds around 10MB of additional RAM usage and about 150% additional CPU time, I will see if I can optimize it a bit more. It should be possible to at least disable this feature in a way to bring the resource usage back to old levels - it's already possible to disable it, but it doesn't seem to release all resources, some of that /proc scanning that was added is probably still happening now |
it looks better with icons
QIcon::hasThemeIcon stats every directory of the theme chain on each miss, costing 3-18 ms per process name on the GUI thread and ~440 ms on the first refresh. Index the theme icon names once (hashes only) so misses are rejected with a binary search; hits are still confirmed by Qt. Resource themes (:/icons) are skipped because walking them faults in the whole icon library; if a theme exists only as a resource, fall back to QIcon::hasThemeIcon. Read /proc/<pid>/cgroup and /proc/<pid>/exe lazily in resolveOwn, once per process identity, instead of for every process on every refresh. Process, LoadOptions and the refresh worker are back to their master versions. Compare IconName in processDataEquals and include DecorationRole in dataChanged so table rows pick up icon changes, and repaint both views when the icon toggle changes.
786cdca to
a3bb28c
Compare
|
thanks for the measurements! I looked into it and measured more carefully it looks like almost all of the cost was at startup, not on each refresh. On the first refresh, QIcon::hasThemeIcon took ~440 ms on the GUI thread. Each process name without an icon in the theme costs 3-18 ms, because Qt retries with dash-separated prefixes and stats every directory of every theme in the chain, and hicolor alone declares hundreds of directories. and almost all of those lookups fail. On my machine, out of the 71 processes that reached the theme lookup, only 3 had an icon there. In total, the process used ~800 ms of CPU in the first few seconds (vs ~300 ms on master) measuring a short window right after startup, that gives the ~+150% (I think that's where your measurement came from). On top of that, the UI froze for ~450 ms after startup, the other waste was reading about the toggle, the code doesn't do any extra /proc reads when it's off, and AppRegistry isn't even created. What doesn't come back after it was on is the already loaded Qt SVG library and the glibc heap, which doesn't return memory to the OS (I tried QPixmapCache::clear() + malloc_trim(0) when turning it off, but they only free ~0.3–0.6 MB, so I left them out) some optimizations I made now: I added an index of the theme's icon names. the program walks the current theme, the themes it inherits from, hicolor and the pixmaps directories once, and stores only the name hashes (~7k names in ~60 KB). If a name isn't in the index, it's rejected right away. If it is, Qt confirms it, which is cheap when the icon exists. The result is exactly the same as Qt's. Themes compiled in as resources (like the KDE platform theme registering Breeze under :/icons) are skipped, because walking them loads the whole libKF6BreezeIcons.so (~+22 MB of RSS). If a theme only exists as a resource, the code falls back to QIcon::hasThemeIcon I also made the cgroup/exe reads on demand. they now happen inside resolveOwn, only the first time a process shows up. Process, LoadOptions and the refresh worker are back to the same as on master. Only the qHash for Qt 5 remains I fixed a bug where the table view didn't update rows when only the icon changed (processDataEquals ignored IconName). Turning icons on while the app was running left idle processes without an icon measurementsMeasured on CachyOS with KDE Plasma (Wayland), ~425 processes, refreshing every 500 ms, showing kernel and other users' processes with the window open, process table view (one 40s run):
without a window (QT_QPA_PLATFORM=offscreen), average of 3 runs of 60s:
"CPU at startup" is the CPU time spent in the first 10s. "CPU after" is the average usage with the app already open I also compared, process by process, the icons picked before and after the changes, across 4 themes (breeze-dark, char-white, Adwaita and hicolor). Nothing changed in any of them, so it just got faster, at least as far as I could see about the +10 MB you mentioned, I couldn't get to that number here. Before the changes I measured ~+4 MB. If you can tell me how you measured it, I can try to reproduce it |
so that it's consistent with the tree view
the way it was before prevented users from setting PID as first column
you can change theme runtime, apps should be able to handle that


Based on the icon resolution systems used by Mission Center and KDE System Monitor, I added icons to the process list and process tree
Closes #60
I originally started this for personal use. I came across TuxManager while looking for a task manager I liked. I really liked it, but the lack of process icons bothered me, so I implemented them just for myself. When I checked whether there was already something about this in the issues, I decided to investigate how other programs resolve process icons and bring the same approach to TuxManager.
Everything was implemented using only Qt and Linux APIs, with no dependency on KDE, GNOME, or any other DE. All credit for the approach goes to Mission Center (the
app-rummagecrate, which in turn credits Resources) and libksysguard/KDE System Monitor!Screenshots
CachyOS (KDE Plasma). Process tree, Mission Center, and TuxManager:
CachyOS (KDE Plasma). Process icons, KDE System Monitor, Mission Center, and TuxManager:
CachyOS (KDE Plasma). char-white icon theme:
Fedora Workstation 44 (GNOME). Compared with Resources:
Alpine Linux (XFCE, OpenRC, no systemd). Process tree, Mission Center, and TuxManager:
How it works
Details
In very broad terms, this is a mix of what Mission Center and KDE System Monitor do. I brought the approach to TuxManager and made a few additional modifications. All credit goes to Mission Center and KDE System Monitor!
1. Per-process data collection
When
LoadOptions::CollectAppInfois enabled,Process::LoadAll()reads two additional pieces of information:CGroup: the0::line from/proc/<pid>/cgroup(the unified hierarchy, cgroup v2). It is empty on cgroup v1, so this does not work there.ExePath: the target of/proc/<pid>/exe, for example/usr/lib/firefox/firefoxThe cgroup is useful because desktops following the systemd desktop environment specification (https://systemd.io/DESKTOP_ENVIRONMENTS/) place each launched application in its own unit, such as
app-org.kde.konsole-1234.scope, with all child processes inside it. The unit name identifies the application even when the process itself would not reveal much, such as azshshell running inside Konsole or a Chrome rendererThe Users tab requests snapshots without
CollectAppInfo, so none of this applies thereExePathis the path to the executable that is actually running. For example,/proc/1234/exe->/usr/lib/firefox/firefox2. Installed application index
AppRegistryscans*.desktopfiles in the following order:$XDG_DATA_HOME/applications$XDG_DATA_DIRS/applications~/.local/share/flatpak/exports/share/applications/var/lib/flatpak/exports/share/applications/var/lib/snapd/desktop/applicationsWhen two
.desktopfiles have the same ID, the first one found winsOnly the
[Desktop Entry]group is read. The entry must haveType=ApplicationandIcon=, whileHidden=trueentries are ignored.NoDisplay=trueentries are intentionally kept (unlike Mission Center). Background helpers can still have useful icons in a process monitorThe parser is custom and intentionally small because
QSettingsdoes not handleName[pt_BR]=, semicolon-separated lists, or the%field codes used byExec=correctly for this use case3. Resolving
Exec=to the real executableThe goal is to determine which binary the
.desktopfile actually launches. For example,Exec=firefox %Ubecomes/usr/bin/firefox, which is then canonicalized to/usr/lib/firefox/firefox(following symlinks) so it can be matched against/proc/<pid>/exeThe following cases are handled, identically to Mission Center:
env,distrobox,toolbox) hand off to the next tokenflatpak,snap) stop without resolving an executable, because the target is not part of the host filesystembash,python3,java,node, ...) stop without resolving an executable. Without this, a singleExec=python3 foo.pyentry would claim every Python process on the system4. Ambiguous executables
Multiple
.desktopfiles may launch the same executable (kcm_*.desktopentries that runsystemsettings, Chrome PWAs, etc.). If their icons differ, the executable is marked ambiguous and executable-based matching is disabled. If they all agree on the same icon (google-chrome.desktopandcom.google.Chrome.desktop), the executable association is kept5. Updating the index
The directories are monitored with
QFileSystemWatcher. Installing or removing an application rebuilds the index and clears the caches, with a 2-second debounce. The index is created only on the first refresh of the Processes tab6. Resolving the application for each process
There are six methods, in this order. The first one that produces a valid result is used:
cgroup. Path components are tested from the inside out against the systemd unit naming pattern (regex ported from libksysguard). For example,
app-org.kde.okular@12345.servicebecomesorg.kde.okular, which is then looked up in the index. Nested units are supported (app-org.kde.konsole-1234.scope/tab(5678).scope). Theapp-,apps-,flatpak-, anddbus-prefixes are accepted, and systemd's\xZZescapes are decoded. Snap does not follow this specification, so it is handled differently:snap.firefox.firefox-<uuid>.scopeis converted tofirefox_firefox, which is the name of the.desktopfile installed by snapdFlatpak. If the executable is under
/app/or/usr/,/proc/<pid>/root/.flatpak-infois read. Inside the sandbox, this file contains the application ID. This is necessary on systems without systemd, whereapp-flatpak-*units do not existExecutable.
ExePathis looked up in the index. If/proc/<pid>/execannot be read (for example, for a process belonging to another user), the process name is looked up inPATHas a fallback. There are also exceptions for binaries whose names do not match the.desktopfile (as in Mission Center):firefox-bin,soffice.bin,chrome,gnome-terminal-server. Binaries under/snap/<snap>/or/var/lib/snapd/snap/<snap>/are mapped to the snap's.desktopfile. This covers snaps launched without a scope, which snapd does not always createIcon theme. The first token of the process name, lowercased, is checked against the icon theme using
QIcon::hasThemeIcon. For example, processfirefox-> iconfirefox. This is essentially the strategy used by KDE System Monitor's process tableParent process. The icon of the nearest ancestor that has one is inherited via PPID, up to 64 levels
Generic.
application-x-executable. Every row ends up with an icon, even when it is only the generic oneDesign decision: cgroup > executable. For example,
sh -c 'exec kate'running inside Konsole shows the Konsole icon, not the Kate icon, because it is still part of the terminal's unit. This matches the behavior of KDE and Mission Center and answers "which application does this process belong to?" rather than "which executable is this?"7. Converting the result to a
QIconAppRegistry::IconFor()converts the resolved icon into aQIcon, with a name-based cache:<datadir>/pixmaps/<name>.{png,svg,xpm}(some applications, such as VS Code, install their icons here)QStyle::SP_FileIconto keep row alignment consistentQIconrasterizes on demand and handles HiDPI automatically8. Cache
Methods 1 through 4 from section 6 are cached by PID + start time, so each process is resolved only once. PID alone would not be enough because PIDs can be reused. The cache is pruned to only live processes on each refresh. Method 5 (parent inheritance) is recalculated on every refresh based on the cache, at the cost of a few hash lookups
9. SVG support in the AppImage
Most modern icon themes use only SVG, and Qt needs the
qsvgiconplugin to load them. Without it, SVG icons are silently ignored and only hicolor PNGs remain. The binary does not link against Qt Svg, solinuxdeploy-plugin-qtdid not include the plugin.package-appimage.shnow passesEXTRA_QT_MODULES=svg. I discovered this while testing the AppImage on FedoraToggle
Added a "Show icons" item to the View submenu of the context menus, persisted as
Processes/ShowIcons. It is enabled by defaultCost
/proc/<pid>/cgroupread and onereadlink, inside the loop that already opens 4 to 5 files per PID.desktopfiles, around 100 KB in sizeQIconcache size is bounded by the number of distinct icon namesAppRegistryis not created and the additional reads do not happenLimitations
/proc/<pid>/exeand/proc/<pid>/root/.flatpak-infoare unreadable without root. cgroup, process name, and inheritance are still availableExec=goes through/snap/bin, so only cgroup and executable-path matching can resolve itI also added Czech (
cs_CZ) and Chinese (zh_CN) translations for the "Show icons" string (it's only two words, so native-speaker review is very welcome!)I'm open to questions and feedback! More testing on different machines is very welcome!