summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/app_settings.py19
-rw-r--r--core/debug_log.py65
-rw-r--r--core/profile.py9
3 files changed, 92 insertions, 1 deletions
diff --git a/core/app_settings.py b/core/app_settings.py
index e8bc7ae..ea3ac49 100644
--- a/core/app_settings.py
+++ b/core/app_settings.py
@@ -51,3 +51,22 @@ def save_settings(cfg: dict) -> None:
json.dump(cfg, f, indent=2)
except Exception:
pass
+
+
+# ── Developer mode ────────────────────────────────────────────────────────
+#
+# In-memory cache so widgets that build device/config UI (Add Device dialog,
+# per-device config panels, plugin panels) can check this without needing
+# the full settings dict threaded through their constructors. MainWindow
+# keeps it in sync with the persisted setting whenever settings are
+# loaded/applied — see set_developer_mode() calls in ui/main_window.py.
+_dev_mode = True
+
+
+def is_developer_mode() -> bool:
+ return _dev_mode
+
+
+def set_developer_mode(value: bool) -> None:
+ global _dev_mode
+ _dev_mode = value
diff --git a/core/debug_log.py b/core/debug_log.py
new file mode 100644
index 0000000..86a8a59
--- /dev/null
+++ b/core/debug_log.py
@@ -0,0 +1,65 @@
+"""
+core/debug_log.py
+
+Tees stdout/stderr into an in-memory ring buffer + Qt signal so the Debug
+window can show everything the app has printed since startup — including
+messages from background poll threads (e.g. "[CMLLayer] poll error: ...") —
+not just whatever gets printed while the window happens to be open.
+
+install() should be called once, early, before anything prints. The real
+streams are still written to, so running from a terminal is unaffected.
+"""
+
+from __future__ import annotations
+
+import sys
+from collections import deque
+from typing import Optional
+
+from PyQt6.QtCore import QObject, pyqtSignal
+
+
+class _Broadcaster(QObject):
+ line_written = pyqtSignal(str)
+
+
+class _StreamTee:
+ def __init__(self, real_stream, lines: deque, broadcaster: _Broadcaster):
+ self._real = real_stream
+ self._lines = lines
+ self._broadcaster = broadcaster
+
+ def write(self, text: str) -> None:
+ self._real.write(text)
+ if text:
+ self._lines.append(text)
+ self._broadcaster.line_written.emit(text)
+
+ def flush(self) -> None:
+ self._real.flush()
+
+ def isatty(self) -> bool:
+ return False
+
+
+_lines: Optional[deque] = None
+_broadcaster: Optional[_Broadcaster] = None
+
+
+def install(max_lines: int = 2000) -> None:
+ """Redirect sys.stdout/sys.stderr through the tee. Safe to call once."""
+ global _lines, _broadcaster
+ if _broadcaster is not None:
+ return
+ _lines = deque(maxlen=max_lines)
+ _broadcaster = _Broadcaster()
+ sys.stdout = _StreamTee(sys.stdout, _lines, _broadcaster)
+ sys.stderr = _StreamTee(sys.stderr, _lines, _broadcaster)
+
+
+def get_broadcaster() -> Optional[_Broadcaster]:
+ return _broadcaster
+
+
+def get_history() -> str:
+ return "".join(_lines) if _lines is not None else ""
diff --git a/core/profile.py b/core/profile.py
index d3c7e3a..7ede859 100644
--- a/core/profile.py
+++ b/core/profile.py
@@ -302,8 +302,15 @@ class ProfileManager:
print(f"[Profile] Unknown device type: {dev_type}")
continue
try:
- kwargs = {k: v for k, v in dev_cfg.items() if k != "device_type"}
+ # "name" is a display label, not a constructor arg — every device
+ # factory builds its own default name internally, so apply it
+ # after construction instead of passing it through.
+ custom_name = dev_cfg.get("name")
+ kwargs = {k: v for k, v in dev_cfg.items()
+ if k not in ("device_type", "name")}
dev = factory(**kwargs)
+ if custom_name:
+ dev.info.name = custom_name
registry.add_instance(dev)
dev.connect()
if engine is not None: