fix(daemon): wire GUI to daemon, fix metrics loop and fork-bomb#163
Open
TuxLux40 wants to merge 6 commits into
Open
fix(daemon): wire GUI to daemon, fix metrics loop and fork-bomb#163TuxLux40 wants to merge 6 commits into
TuxLux40 wants to merge 6 commits into
Conversation
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>
This was referenced Jun 2, 2026
There was a problem hiding this comment.
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 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 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 |
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TrccProxy— daemon-mode pass that lets the GUI launch inTRCC_DAEMON=1mode without crashingcontrol_center.snapshot()through_dispatch_metaso status/info endpoints work correctly over IPCdescriptors()for device count in daemon mode (trcc statusnow reports correct device count)TRCC_DAEMONfork-bomb:run_daemon()popsTRCC_DAEMONbefore spawning so the daemon doesn't recursively spawn itselfTest plan
TRCC_DAEMON=1 trcc guilaunches without exceptiontrcc statusreports correct device count when daemon runningtrcc daemondoes not fork-bomb whenTRCC_DAEMON=1is set in environmentIf this project helps you, consider buying me a beer 🍺 or Ko-fi ☕