Summary
StreamController (flathub, com.core447.StreamController 1.5.0-beta.16, flatpak 1.14.6) makes
flatpak-session-helper permanently burn ~19–22 % of one CPU core (mostly kernel/system time)
on an X11 desktop (Cinnamon). The helper also suffers a voluntary context-switch storm
(~26 000–45 000/s). The cause is the X11 WindowGrabber: it polls the active window every 0.2 s
by spawning xprop, and because the app is sandboxed each call goes through
flatpak-spawn --host xprop … → org.freedesktop.Flatpak.Development.HostCommand (implemented by
flatpak-session-helper, which fork()+exec()s and streams output back over D-Bus). It runs
forever, even when the active window never changes.
This is a follow-up to #457, which was closed without the X11 path actually being fixed. There is
no open issue tracking this — see the issue landscape below before closing this as a duplicate.
Environment
OS : Linux bro (X11 session), XDG_CURRENT_DESKTOP=X-Cinnamon
Flatpak : 1.14.6 (system installation)
StreamController: com.core447.StreamController 1.5.0-beta.16 (flathub)
Runtime : org.gnome.Platform/x86_64/50
CPU : 20 cores
Helper process : /usr/libexec/flatpak-session-helper (PID varies; 4292 in this report)
Steps to reproduce
- Run StreamController under any X11 desktop (Cinnamon here; flatpak-session-helper high CPU usage and excessive window monitoring whenStreamController running #457 was GNOME Shell/X11).
- Observe flatpak-session-helper CPU in top/htop — sustained double-digit % of one core,
mostly in sys/kernel time.
Reproduction without touching anything (works cross-user, read-only):
# helper PID (per-user): find via: ps -eo pid,comm | grep flatpak-session-helper
u1=$(awk '{print $14+$15}' /proc/4292/stat); sleep 8
u2=$(awk '{print $14+$15}' /proc/4292/stat)
echo "$(( (u2-u1) * 100 / 800 ))% of one core" # observed: 19
v1=$(awk '{print $23}' /proc/4292/stat); sleep 3
v2=$(awk '{print $23}' /proc/4292/stat)
echo "voluntary ctx switches/s: $(( (v2-v1) / 3 ))" # observed: ~45000
With access to the session bus, the D-Bus flood is directly visible:
timeout 12 dbus-monitor --session | grep -c 'member=HostCommand$'
# observed: 69 in 12 s (~5.75 Hz); message path/interface/member:
# path=/org/freedesktop/Flatpak/Development
# interface=org.freedesktop.Flatpak.Development member=HostCommand
# argv = ["xprop", "-id", "<active window id>", "WM_CLASS"] (plus fd passing)
Root cause (code level)
Installed app image: /var/lib/flatpak/app/com.core447.StreamController/x86_64/stable/<commit>/files/bin/StreamController/src/backend/WindowGrabber/Integrations/X11.py
(identical on main — diff-verified byte-for-byte).
- WatchForActiveWindowChange.run() loops time.sleep(0.2) → get_active_window() forever:
while gl.threads_running:
time.sleep(0.2)
new_active_window = self.x11.get_active_window()
- One poll runs 3 separate xprop subprocesses — parse_window_ids() (xprop -root _NET_ACTIVE_WINDOW),
get_title() (xprop -id <win> WM_NAME), get_class() (xprop -id <win> WM_CLASS).
All 3 run on every cycle — the code only compares after fetching everything, so it keeps
spawning even when the active window is unchanged (the if new_active_window == self.last_active_window: continue
check happens after the 3 subprocesses).
- _run_command() wraps every command for the sandbox:
if self.flatpak:
command.insert(0, "flatpak-spawn"); command.insert(1, "--host")
The manifest grants --talk-name=org.freedesktop.Flatpak, so each call lands on
flatpak-session-helper. Each flatpak-spawn invocation is a fresh D-Bus connection
(full AUTH/BEGIN handshake + Hello + per-connection proxy match rules) plus a fork()+exec()
and fd-streaming on the helper — the helper-side cost is a small fork/exec per call at 5 Hz+
(up to 3 xprop per poll = up to 15 host spawns/s in the worst case).
- The thread is started unconditionally when host xprop exists
(self.is_xprop_installed = self.get_is_xprop_installed(); then
self.start_active_window_change_thread()). There is no setting to disable it, and it runs
even if the user never uses any feature that consumes the data.
- Consumers of the data (i.e. why it is "needed"): on_active_window_changed() in
src/backend/WindowGrabber/WindowGrabber.py → notify_foreground_window_changed() → the D-Bus
API ForegroundWindow property + the "auto change page by active window" feature.
Attribution is unambiguous: a grep of the whole SC src/ shows xprop is used only by
X11.py, and the other flatpak-spawn users are one-shot (permission manager) or event-driven
(Hyprland). All 4 installed plugins were also checked during the original investigation — no
plugin does any of this (the only periodic plugin activity is a Prometheus fetch and a
screensaver D-Bus GetActive check, neither of which goes through HostCommand).
Evidence
Measured live on the affected machine (2026-08-30, helper PID 4292, SC running):
| Metric |
Value |
| Helper CPU in an 8 s window (/proc/4292/stat utime+stime) |
+152 ticks ≈ 19 % of one core |
| Voluntary context switches |
45 056/s (3 s sample); earlier capture: ~26 000/s in 5 s |
| D-Bus HostCommand calls (12 s capture) |
69 ≈ 5.75 Hz; 47 in the 8 s CPU sample (≈22 %) |
| xprop callers in SC core |
only X11.py (verified by grep) |
| Active window id being polled |
unchanged (same gnome-terminal window re-fetched every 0.2 s; SC log shows matching ForegroundWindow notifications) |
Additional captures from the original investigation (same machine; strace of the app's
xdg-dbus-proxy): 17 148 lines = 96 identical connection lifecycles, each = accept →
connect to the session bus → full handshake → Hello (:1.224080…) → ~50 per-connection
AddMatch/GetNameOwner (normal xdg-dbus-proxy filter setup) → exactly one HostCommand
with SCM_RIGHTS fds → EOF → next connection. Cumulative flatpak-session-helper I/O counters:
rchar ≈ 14.4 GB, wchar ≈ 114 MB, syscr ≈ 9.83 M, syscw ≈ 7.82 M.
Corroboration from the #457 thread: the reporter found CPU drops to normal after
flatpak override --user --no-talk-name=org.freedesktop.Flatpak …, i.e. blocking HostCommand —
the exact mechanism identified here.
Issue landscape — no open issue tracks this
Checked 2026-08-30 (search: xprop, flatpak-session-helper, CPU):
And the decisive fact: src/backend/WindowGrabber/Integrations/X11.py on main is
byte-for-byte identical to the file in the installed 1.5.0-beta.16 image (diff-verified). Git
history shows no rework ever — last commits are "Fix: xprop spamming when xprop not installed"
(2025-04) and "Chore: Allow start on macos" (2025-11).
Expected behavior
Near-zero flatpak-session-helper CPU when the active window does not change — same outcome PR
#580 achieved for Hyprland. (flatpak-session-helper itself is innocent; it just serves the
churn.)
Suggested fix (summary — see companion PR proposal)
Make the X11 integration event-driven, mirroring the Hyprland rewrite:
- Open a direct X11 connection from inside the sandbox (already permitted:
sockets=fallback-x11;wayland; / --socket=x11 in the manifest — no manifest change needed)
and wait for PropertyNotify of _NET_ACTIVE_WINDOW on the root window
(select() on the X socket, no polling).
- Read WM_CLASS/WM_NAME with XGetWindowProperty on the same connection — zero
subprocesses, zero flatpak-spawn, zero D-Bus, zero helper involvement.
- Implementation options, in order of preference:
- python-xlib (pure Python; SC already vendors all Python deps into the app image, so
bundling it is a one-liner) — the approach Rework WindowGrabber under x11 to not call xprop #233 proposed;
- ctypes over libX11.so.6, which is already shipped in org.gnome.Platform/50
(no new dependency at all);
- If the X connection is unavailable, disable window tracking with a log message —
no polling fallback (xprop without a working X connection can never succeed, so a
fallback would only reproduce the churn; the Hyprland fallback exists because
hyprctl works even when the IPC socket is temporarily down — that does not apply
here).
- Not viable (checked): org.freedesktop.portal.WindowManager — portals deliberately expose no
active-window query (that is why the GNOME integration uses a Shell extension instead); the
portal interface only supports CreateTransientFor/DestroyTransientFor.
System information
StreamController: 1.5.0-beta.16 (flathub)
Flatpak: 1.14.6
Desktop: Cinnamon, X11 (also reproduced/config-identical to #457 on GNOME 46/X11)
Runtime: org.gnome.Platform/x86_64/50
References
Summary
StreamController (flathub, com.core447.StreamController 1.5.0-beta.16, flatpak 1.14.6) makes
flatpak-session-helper permanently burn ~19–22 % of one CPU core (mostly kernel/system time)
on an X11 desktop (Cinnamon). The helper also suffers a voluntary context-switch storm
(~26 000–45 000/s). The cause is the X11 WindowGrabber: it polls the active window every 0.2 s
by spawning xprop, and because the app is sandboxed each call goes through
flatpak-spawn --host xprop … → org.freedesktop.Flatpak.Development.HostCommand (implemented by
flatpak-session-helper, which fork()+exec()s and streams output back over D-Bus). It runs
forever, even when the active window never changes.
This is a follow-up to #457, which was closed without the X11 path actually being fixed. There is
no open issue tracking this — see the issue landscape below before closing this as a duplicate.
Environment
Steps to reproduce
mostly in sys/kernel time.
Reproduction without touching anything (works cross-user, read-only):
With access to the session bus, the D-Bus flood is directly visible:
Root cause (code level)
Installed app image: /var/lib/flatpak/app/com.core447.StreamController/x86_64/stable/<commit>/files/bin/StreamController/src/backend/WindowGrabber/Integrations/X11.py
(identical on main — diff-verified byte-for-byte).
get_title() (xprop -id <win> WM_NAME), get_class() (xprop -id <win> WM_CLASS).
All 3 run on every cycle — the code only compares after fetching everything, so it keeps
spawning even when the active window is unchanged (the if new_active_window == self.last_active_window: continue
check happens after the 3 subprocesses).
flatpak-session-helper. Each flatpak-spawn invocation is a fresh D-Bus connection
(full AUTH/BEGIN handshake + Hello + per-connection proxy match rules) plus a fork()+exec()
and fd-streaming on the helper — the helper-side cost is a small fork/exec per call at 5 Hz+
(up to 3 xprop per poll = up to 15 host spawns/s in the worst case).
(self.is_xprop_installed = self.get_is_xprop_installed(); then
self.start_active_window_change_thread()). There is no setting to disable it, and it runs
even if the user never uses any feature that consumes the data.
src/backend/WindowGrabber/WindowGrabber.py → notify_foreground_window_changed() → the D-Bus
API ForegroundWindow property + the "auto change page by active window" feature.
Attribution is unambiguous: a grep of the whole SC src/ shows xprop is used only by
X11.py, and the other flatpak-spawn users are one-shot (permission manager) or event-driven
(Hyprland). All 4 installed plugins were also checked during the original investigation — no
plugin does any of this (the only periodic plugin activity is a Prometheus fetch and a
screensaver D-Bus GetActive check, neither of which goes through HostCommand).
Evidence
Measured live on the affected machine (2026-08-30, helper PID 4292, SC running):
Additional captures from the original investigation (same machine; strace of the app's
xdg-dbus-proxy): 17 148 lines = 96 identical connection lifecycles, each = accept →
connect to the session bus → full handshake → Hello (:1.224080…) → ~50 per-connection
AddMatch/GetNameOwner (normal xdg-dbus-proxy filter setup) → exactly one HostCommand
with SCM_RIGHTS fds → EOF → next connection. Cumulative flatpak-session-helper I/O counters:
rchar ≈ 14.4 GB, wchar ≈ 114 MB, syscr ≈ 9.83 M, syscw ≈ 7.82 M.
Corroboration from the #457 thread: the reporter found CPU drops to normal after
flatpak override --user --no-talk-name=org.freedesktop.Flatpak …, i.e. blocking HostCommand —
the exact mechanism identified here.
Issue landscape — no open issue tracks this
Checked 2026-08-30 (search: xprop, flatpak-session-helper, CPU):
the maintainer with "We are tracking high resource utilization in [QUESTION] Should StreamController use this much RAM? #123".
xprop mechanism was raised there once by bitclick (2025-12-23: "focus detection falls back to
polling via xprop … runs xprop every 200ms … i suspect the cpu usage in flatpak-session-helper
comes from this") but was never acknowledged or answered — all maintainer replies in that
thread concern memory profiling. The CPU/xprop issue is therefore effectively untracked.
com.core447.StreamController.yml (+--filesystem=xdg-run/hypr:ro),
src/backend/DeckManagement/DeckController.py (idle FPS) and
src/backend/WindowGrabber/Integrations/Hyprland.py (+101/−10) — it made Hyprland
event-driven via the compositor IPC socket (.socket2.sock). The X11 integration was not
touched.
— daemon-only mode, RAM/page-switch/background-video optimizations; no WindowGrabber files
touched. Installed 1.5.0-beta.16 includes this work and still shows ~19 % helper CPU.
event-driven approach) was closed 2025-05-05 without implementation.
flatpak memory-leak bug (flatpak/xdg-dbus-proxy#51) — that is a memory issue. The CPU
cost described here is caused by StreamController's own polling design and is a separate
problem.
And the decisive fact: src/backend/WindowGrabber/Integrations/X11.py on main is
byte-for-byte identical to the file in the installed 1.5.0-beta.16 image (diff-verified). Git
history shows no rework ever — last commits are "Fix: xprop spamming when xprop not installed"
(2025-04) and "Chore: Allow start on macos" (2025-11).
Expected behavior
Near-zero flatpak-session-helper CPU when the active window does not change — same outcome PR
#580 achieved for Hyprland. (flatpak-session-helper itself is innocent; it just serves the
churn.)
Suggested fix (summary — see companion PR proposal)
Make the X11 integration event-driven, mirroring the Hyprland rewrite:
sockets=fallback-x11;wayland; / --socket=x11 in the manifest — no manifest change needed)
and wait for PropertyNotify of _NET_ACTIVE_WINDOW on the root window
(select() on the X socket, no polling).
subprocesses, zero flatpak-spawn, zero D-Bus, zero helper involvement.
bundling it is a one-liner) — the approach Rework WindowGrabber under x11 to not call xprop #233 proposed;
(no new dependency at all);
no polling fallback (xprop without a working X connection can never succeed, so a
fallback would only reproduce the churn; the Hyprland fallback exists because
hyprctl works even when the IPC socket is temporarily down — that does not apply
here).
active-window query (that is why the GNOME integration uses a Shell extension instead); the
portal interface only supports CreateTransientFor/DestroyTransientFor.
System information
References
perf: eliminate idle CPU waste (socket events + adaptive FPS) #580 (Hyprland-only perf PR), Rework WindowGrabber under x11 to not call xprop #233 (X11 rework, never implemented), StreamController uses a lot of CPU #340 (older CPU report)