bgapp2tray creates a tray icon for each background Flatpak app that uses the XDG Background Portal, allowing the user to activate or quit the app on systems not supporting the Background Portal (essentially every one except GNOME and KDE).
Probably due to GNOME not having a system tray (systray), some Flatpak apps use XDG Background Portal) instead when running in background. (E.g., GNOME Clock and the music player Amberol.)
However, it seems that only GNOME and KDE support the Background Portal. In GNOME, Flatpak apps show in a menu in Quick Settings when running in background. In KDE, they appear in the system tray. In other desktop environments, these apps have no visible way to indicate that they are running in background, and the user has no way to interact with them, except launching the same app again.
Personally, I prefer systray to the Background Portal (some limitations of the latter explained later). But I do like some of those apps using the Background Portal (e.g., again, Amberol). So comes this project, which bridges between the two systems.
git clone https://github.com/jinliu/bgapp2tray.git
cd bgapp2tray
cargo build --releaseYou'll find the compiled binaries in the target/release directory:
bgapp2traybgapp2tray-helperxdg-desktop-portal-bgapp
First, copy the compiled binaries to a directory of your choice. They don't need to be in your $PATH, but bgapp2tray and bgapp2tray-helper need to be in the same directory.
You need to set up two services:
xdg-desktop-portal-bgapp, an XDG Desktop Portal backend, to enable the Background Portal for Flatpak apps.bgapp2tray, the tray icon manager, to provide a system tray icon for background Flatpak apps if they don't have one.
(These two are not tightly coupled. They only talk through the XDG Desktop Portal Background Apps Monitor interface. In theory, you could replace one without affecting the other. But in most cases, you will want to run both. More details in the Technical Details section.)
We'll cover both in the following sections:
- Copy
xdg-desktop-portalconfig files to your$HOME, for ease of modification:
cp -r /usr/share/xdg-desktop-portal $HOME/.local/share/(For alternate locations, see XDG Desktop Portal configuration.)
- Install the backend description file:
cp config-example/bgapp.portal $HOME/.local/share/xdg-desktop-portal/portals/- Modify the portal config file for your compositor. For example, if you are using
niri, then you need to modify$HOME/.local/share/xdg-desktop-portal/niri-portals.confto add the following line:
org.freedesktop.impl.portal.Background=bgapp;- Arrange for
xdg-desktop-portal-bgappto autostart. This varies depending on your compositor.
xdg-desktop-portal-bgapp uses the Wayland Foreign Toplevel List v1 protocol to get the list of all app windows. It's supported by Cosmic, Hyprland, Niri, and all wlroots-based compositors (e.g., Sway). (GNOME and KDE Plasma don't support the protocol. But their own portal backends use private protocols to talk with their compositors. So you don't need this project in this case.)
If your compositor does not support the protocol, but has a command to list the app ID of all open windows, you can use that as a fallback. Copy the config file:
cp config-example/xdg-desktop-portal-bgapp.toml $HOME/.config/then edit the update_cmdline= line in $HOME/.config/xdg-desktop-portal-bgapp.toml. The file originally has an example command for Niri (though Niri doesn't require it, because it supports the protocol). You should replace it with the appropriate command for your compositor. The command should output the list of all app IDs of currently open windows, one per line.
You can test your command by running it in the terminal:
sh -c "<your_command_here>"You can also modify update_interval_ms=1000 to change how frequently the command is run.
Make sure that bgapp2tray and bgapp2tray-helper are in the same directory. Then arrange for bgapp2tray to autostart. This varies depending on your compositor.
bgapp2tray uses a configuration file located at $HOME/.config/bgapp2tray.toml. You can copy the example configuration file and edit it according to your needs:
cp config-example/bgapp2tray.toml $HOME/.config/Then edit $HOME/.config/bgapp2tray.toml as needed. You can change tray_icon_status=1 to tray_icon_status=0 to change the tray icon status from ActiveStatus to PassiveStatus. Depending on your systray implementation, this may affect how the tray icon is displayed, or make them totally invisible.
Now everything is set up. Log out and log in again to apply the changes.
If everything is set up correctly, if you set a Flatpak app to run in the background (e.g., enable "Background Playback" in Amberol's menu), then close its window, a tray icon should appear (maybe after a few seconds) indicating that the app is still running in the background.
You can click on the tray icon to bring the app back to the foreground. Right-click on the tray icon would open a menu, where you can quit the app.
- Why doesn't the tray icon appear immediately after closing the app window?
The Background Apps Monitor has a pretty long delay, probably by design. More details in the Technical Details section.
- Why no icons for Native apps (e.g. GNOME Clock or Amberol installed via your distribution's package manager)?
This is a known limitation of the XDG Background Portal. More details in the Technical Details section.
When things don't work as expected, let's first check which of the two services isn't functioning correctly:
- Start a background app. E.g., enable "Background Playback" in Amberol's menu and then close its window. Run
flatpak psshould list an instance ofio.bassi.Amberol. - After a while (usually 20 seconds is enough), run
busctl --user get-property org.freedesktop.background.Monitor /org/freedesktop/background/monitor org.freedesktop.background.Monitor BackgroundAppsto check the list of background apps.
The output should look like:
aa{sv} 1 2 "app_id" s "io.bassi.Amberol" "instance" s "1090165318"(The number at the end doesn't matter.)
If the output is empty, then xdg-desktop-portal might not be running correctly. Otherwise, it's probably bgapp2tray. In either case, you might want to kill the service, and then run it with increased log level:
pkill -f xdg-desktop-portal-bgapp
RUST_LOG=debug xdg-desktop-portal-bgappxdg-desktop-portal is the service ("frontend") that provides the XDG Desktop Portal API to applications. It talks to desktop-environment-specific backends like xdg-desktop-portal-gnome or xdg-desktop-portal-gtk. For the Background Portal, the backend needs to provide these interfaces to the frontend:
-
GetAppState: Return a list of app ID for open windows. We implement this via Wayland Foreign Toplevel List v1 protocol, or a user-provided custom command for his compositor.
-
NotifyBackground: Notify the user that the app has moved to the background, and ask him to confirm. We implement this as always silently approving. GNOME does something similar. KDE Plasma shows a notification.
-
EnableAutostart: This is deprecated. We implement it as a no-op.
The Background Monitor uses the app ID list provided by GetAppState to infer background apps, by fetching the list of running Flatpak instances, and then removing those in the open-window list. To prevent newly-launched apps from being incorrectly marked as background when their windows have not yet appeared, it repeats the check a few times before making a final determination. Thus the pretty long and random delay in the monitor results. See comments in code.
bgapp2tray creates one tray icon for each app ID from Background Monitor. If the monitor reports multiple Flatpak instances for the same app ID, only one icon is created, because:
-
The usual case when multiple instances of the same background app, is between when the app starts a dedicated background instance, and then quits itself. The Background Monitor will report both instances only briefly, so creating multiple tray icons would be unnecessary and confusing.
-
Even if multiple icons were created for the same app ID, clicking on any of them would activate the same Flatpak instance, because we activate the app by the DBus name corresponding to the app ID, not using the Flatpak instance ID.
We don't create tray icons if the app already has one. (E.g., Flatpak KDE apps like Elisa.) This prevents duplicate icons. We do this by getting the DBus address of all tray icons from org.kde.StatusNotifierWatcher, then converting them to PIDs via org.freedesktop.DBus.GetConnectionUnixProcessID, and finally, getting the Flatpak instance IDs from /proc/<pid>/root/.flatpak-info. This is copied from the KDE Plasma code.
One interesting implementation detail is that we don't create tray icons directly in bgapp2tray, but spawn a separate bgapp2tray-helper process for each icon. There are multiple reasons:
-
Althought the libappindicator-zbus library we use allows creating multiple tray icons in the same process, it doesn't support removing the tray icon once it is created, so we shut down the helper instead.
-
Similarly, some tray implementations (e.g., the one in Noctalia which I'm using) also don't expect multiple tray icons under the same DBus name, so they only remove icons when the corresponding process exits.
In theory, you should be above to run bgapp2tray in GNOME, along with the appindicator GNOME extension, to show background apps in the tray, in addition to Quick Settings. However, for some unknown reason, the org.freedesktop.DBus.GetConnectionUnixProcessID DBus call fails in GNOME. So this doesn't work.
- XDG Background Portal API for app
- XDG Background Portal API for backend
- XDG Background Portal backend developer document
- XDG Desktop Portal configuration
- XDG Background Monitor
- Wayland Foreign Toplevel List v1 protocol
- GNOME Background Portal code
- GNOME Shell Background Apps code
- KDE Plasma Background Portal code
- KDE Plasma Background Apps tray icon code
- XDG Desktop Portal Background code
- libappindicator-zbus crate
- Noctalia - Wayland Shell