Skip to content

fix(daemon): wire GUI to daemon, fix metrics loop and fork-bomb#163

Open
TuxLux40 wants to merge 6 commits into
Lexonight1:mainfrom
TuxLux40:fix/daemon-core
Open

fix(daemon): wire GUI to daemon, fix metrics loop and fork-bomb#163
TuxLux40 wants to merge 6 commits into
Lexonight1:mainfrom
TuxLux40:fix/daemon-core

Conversation

@TuxLux40

@TuxLux40 TuxLux40 commented Jun 2, 2026

Copy link
Copy Markdown

Summary

  • Wire GUI to daemon via TrccProxy — daemon-mode pass that lets the GUI launch in TRCC_DAEMON=1 mode without crashing
  • Route control_center.snapshot() through _dispatch_meta so status/info endpoints work correctly over IPC
  • Use descriptors() for device count in daemon mode (trcc status now reports correct device count)
  • Start metrics loop in daemon + bypass TRCC_DAEMON fork-bomb: run_daemon() pops TRCC_DAEMON before spawning so the daemon doesn't recursively spawn itself

Part 1 of 3 — see also: fix/daemon-lcd (LCD controls), fix/daemon-led (LED controls). Merge in order.

Test plan

  • TRCC_DAEMON=1 trcc gui launches without exception
  • trcc status reports correct device count when daemon running
  • trcc daemon does not fork-bomb when TRCC_DAEMON=1 is set in environment
  • Metrics (CPU temp/load, GPU temp/load) appear in daemon log within a few seconds of start

If this project helps you, consider buying me a beer 🍺 or Ko-fi

TuxLux40 and others added 2 commits May 14, 2026 13:00
Add new section under Display Issues:
'trccd service running but segment/LED display is completely blank'

Covers the bug where trcc daemon discovers the device but never calls
start_metrics_loop(), leaving the display dark even though trccd is
active and the hardware is connected. Includes:
- Clear symptom checklist to distinguish this from other blank-display causes
- Explanation of root cause (missing start_metrics_loop() call in run_daemon())
- Copy-paste fix that auto-locates daemon.py regardless of Python version
- Verification step (tail trcc.log for 'Frame sent: LED')
- Note that the patch is overwritten on upgrades

Confirmed on: Thermalright Assassin X 120R Digital (0416:8001),
trcc-linux v9.5.7 and v9.5.11, CachyOS.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR improves daemon-mode support across the GUI/CLI by routing device identity/state through IPC descriptors/snapshots, ensures the daemon actually drives hardware updates, and hardens IPC event forwarding/serialization.

Changes:

  • Add daemon-mode GUI initialization that builds LED handlers from IPC descriptors (no live device objects in the GUI process).
  • Start the daemon metrics loop so LED/LCD frames and segment updates are actually pushed to hardware.
  • Improve IPC event forwarding (JSON fallback encoding, better subscription cleanup) and extend proxy/meta APIs with snapshots.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/trcc/ui/gui/trcc_app.py Adds handler population from daemon-provided descriptors; builds SystemService locally when running via TrccProxy.
src/trcc/ui/gui/led_handler.py Adds “daemon mode” path that routes UI actions through TrccProxy and syncs initial UI state via snapshot.
src/trcc/ui/gui/base_handler.py Allows handlers without a live device and adds device_info override for descriptor-backed handlers.
src/trcc/ui/gui/init.py Launch logic updated to avoid rebinding IPC socket in daemon mode and to populate GUI from descriptors.
src/trcc/ui/cli/_status.py Makes trcc status work in daemon-proxy mode by using descriptors rather than live device lists.
src/trcc/ipc.py Adds JSON default encoder, improves subscription cleanup, and adds _meta snapshot endpoints.
src/trcc/daemon.py Starts metrics loop in daemon and forces explicit platform to avoid spawning recursive daemons.
src/trcc/core/trcc_proxy.py Adds snapshot APIs and deserializes metrics payloads back into dataclasses for parity with in-proc mode.
packaging/systemd/trccd-system.service Adds a systemd unit for running the daemon as a background service.
doc/GUIDE_TROUBLESHOOTING.md Documents the “blank display” symptom and workaround/fix for missing metrics loop.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +464 to +470
for idx, desc in enumerate(led_descs):
path = desc.path
if path not in self._handlers:
handler = LEDHandler(
None, self.uc_led_control, self._on_temp_unit_changed,
trcc=self._trcc, led_idx=idx, descriptor=desc,
)
Comment on lines +318 to +325
def _restore(item: Any) -> Any:
if topic == Topic.METRICS and isinstance(item, dict):
from .models.sensor import HardwareMetrics
import dataclasses as _dc
known = {f.name for f in _dc.fields(HardwareMetrics)}
d = {k: (set(v) if k == '_populated' and isinstance(v, list) else v)
for k, v in item.items() if k in known}
return HardwareMetrics(**d)
Comment thread src/trcc/ipc.py
Comment on lines +358 to +363
client.setblocking(False)
data = client.recv(1)
client.setblocking(True)
if data == b'':
log.debug("subscribe forwarder: remote closed (EOF on recv) — cleaning up")
_cleanup_sub()
Comment thread src/trcc/ipc.py
Comment on lines +48 to +57
def _json_default(obj: Any) -> Any:
"""JSON serializer fallback for types not handled by the default encoder.

Handles: set → sorted list, dataclass → dict. Keeps wire payloads
clean without requiring callers to enumerate every edge-case type.
"""
if isinstance(obj, set):
return sorted(obj)
if dataclasses.is_dataclass(obj) and not isinstance(obj, type):
return dataclasses.asdict(obj)
Comment on lines +259 to +269
if self._daemon_mode:
self._trcc.led.select_zone(self._led_idx, zone_index)
try:
snap = self._trcc.led.snapshot(self._led_idx)
if snap.zones and 0 <= zone_index < len(snap.zones):
z = snap.zones[zone_index]
self._panel.load_zone_state(
zone_index, z['mode'], tuple(z['color']),
z['brightness'], z.get('on', True))
except Exception:
pass
Comment thread packaging/systemd/trccd-system.service Outdated
TuxLux40 and others added 4 commits June 3, 2026 07:22
GUI in TRCC_DAEMON=1 mode previously opened USB directly instead of
routing through the daemon, so live changes from the GUI were
overwritten every 50ms by the daemon's metrics loop.

Changes:
- ipc.py: JSON serializer fallback (_json_default) for set/dataclass;
  subscription forwarder cleanup helper; guard non-serializable payloads
- trcc_proxy.py: LEDFacadeProxy.snapshot() fetches LED state over IPC;
  EventBusProxy._desanitize_payload() reconstructs HardwareMetrics for
  metrics topic so callers see typed objects not raw dicts; TrccProxy
  led_descriptors() stub for populate_from_descriptors
- trcc_app.py: daemon-mode branch in _setup_trcc() builds local
  SystemService when proxy has none; populate_from_descriptors() builds
  LEDHandlers from IPC descriptors instead of live device objects
- base_handler.py, led_handler.py: pass trcc/led_idx/descriptor kwargs
  through to daemon-mode handler construction path

Companion to issue Lexonight1#148 (start_metrics_loop fix) and issue Lexonight1#162
(TRCC_DAEMON fork-bomb).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AppSnapshot is not an OpResult subclass, so _dispatch_manifold was
rejecting it with 'did not return an OpResult' and `trcc status`
crashed with AttributeError on snap.version.

Fix mirrors the existing led_snapshot pattern exactly:
- server: _dispatch_meta handles 'app_snapshot', serialises AppSnapshot
  via dataclasses.asdict (tuples→lists for JSON, client restores them)
- client: ControlCenterFacadeProxy.snapshot() routes via _meta instead
  of manifold, reconstructs AppSnapshot from the wire dict

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
discovery.lcd_devices / led_devices are always empty through TrccProxy
because live device objects don't travel over the wire. Both Trcc and
TrccProxy expose lcd_descriptors() / led_descriptors() which do the
right thing in both in-process and daemon-proxy mode.

Fixes 'No devices connected.' in `trcc status` when daemon holds USB.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
run_daemon() now calls trcc.start_metrics_loop() so the 50ms tick
drives LCD/LED frames — without it the device shows a frozen/blank
screen in daemon mode.

_build_trcc() passes an explicit PlatformFactory.current() to
_boot_trcc() so the daemon-proxy short-circuit is skipped. With
TRCC_DAEMON=1 inherited and no explicit platform, _boot.trcc() called
ensure_daemon() before the socket was bound, spawning another daemon
process — infinite fork chain (upstream issue Lexonight1#162).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants