Repository navigation
X11: WindowGrabber still spikes flatpak-session-helper CPU — xprop @ 5 Hz via flatpak-spawn (Hyprland got fixed in #580, X11 did not) #644
Description
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-helperCPU intop/htop— sustained double-digit % of one core,
mostly insys/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: ~45000With 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()loopstime.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
xpropsubprocesses —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 (theif new_active_window == self.last_active_window: continue
check happens after the 3 subprocesses). _run_command()wraps every command for the sandbox:The manifest grantsif self.flatpak: command.insert(0, "flatpak-spawn"); command.insert(1, "--host")
--talk-name=org.freedesktop.Flatpak, so each call lands on
flatpak-session-helper. Eachflatpak-spawninvocation is a fresh D-Bus connection
(full AUTH/BEGIN handshake + Hello + per-connection proxy match rules) plus afork()+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
xpropexists
(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
APIForegroundWindowproperty + 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):
- flatpak-session-helper high CPU usage and excessive window monitoring whenStreamController running #457 (this exact bug; GNOME Shell 46, SC 1.5.0-beta.11, 33 % CPU) — closed 2025-10-13 by
the maintainer with "We are tracking high resource utilization in [QUESTION] Should StreamController use this much RAM? #123". - [QUESTION] Should StreamController use this much RAM? #123 — open, but titled "[QUESTION] Should StreamController use this much RAM?". The
xprop mechanism was raised there once bybitclick(2025-12-23: "focus detection falls back to
polling via xprop … runs xprop every 200ms … i suspect the cpu usage inflatpak-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. - PR perf: eliminate idle CPU waste (socket events + adaptive FPS) #580 ("perf: eliminate idle CPU waste", merged 2026-06-10) changed only
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. - PR Daemon-only mode with CPU/RAM reductions and page-switch/resume performance improvements #558 / commit
eb2bc60(2026-08-10, mentioned by the maintainer as "massively improved")
— 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. - Rework WindowGrabber under x11 to not call
xprop#233 ("Rework WindowGrabber under x11 to not callxprop", proposed python-xlib
event-driven approach) was closed 2025-05-05 without implementation. - Note: the maintainer attributed
flatpak-session-helper/xdg-dbus-proxyproblems to a
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:
- Open a direct X11 connection from inside the sandbox (already permitted:
sockets=fallback-x11;wayland;/--socket=x11in the manifest — no manifest change needed)
and wait forPropertyNotifyof_NET_ACTIVE_WINDOWon the root window
(select()on the X socket, no polling). - Read
WM_CLASS/WM_NAMEwithXGetWindowPropertyon the same connection — zero
subprocesses, zeroflatpak-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 callxprop#233 proposed; - ctypes over
libX11.so.6, which is already shipped inorg.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
hyprctlworks even when the IPC socket is temporarily down — that does not apply
here).
- python-xlib (pure Python; SC already vendors all Python deps into the app image, so
- 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 supportsCreateTransientFor/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
- flatpak-session-helper high CPU usage and excessive window monitoring whenStreamController running #457 (original report, closed without X11 fix), [QUESTION] Should StreamController use this much RAM? #123 (RAM question it was redirected to),
perf: eliminate idle CPU waste (socket events + adaptive FPS) #580 (Hyprland-only perf PR), Rework WindowGrabber under x11 to not callxprop#233 (X11 rework, never implemented), StreamController uses a lot of CPU #340 (older CPU report) X11.pyon main: https://github.com/StreamController/StreamController/blob/main/src/backend/WindowGrabber/Integrations/X11.py
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
jleaders commentedon Oct 11, 2026
I was running into this issue as well. (installed the flatpak version)