feat(tray-tray): Implement tray system for Chobit/Dev trays

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-03-29 06:03:17 -07:00
parent 0ba291796f
commit 603b47e3a2
2 changed files with 28 additions and 11 deletions

View file

@ -134,6 +134,7 @@ class ChobitTray(TrayApp):
menu: list[TrayMenuItem] = [
TrayMenuItem.action("Settings", self._open_board),
TrayMenuItem.action("Camera Settings", self._open_camera_settings),
TrayMenuItem.separator(),
TrayMenuItem.action("Toggle Snap", self._toggle_snap),
]
@ -193,12 +194,6 @@ class ChobitTray(TrayApp):
self._select_camera(index)
elif cmd == "list_cameras":
self._send_camera_list()
elif cmd == "open_camera_preview":
try:
from gi.repository import GLib
GLib.idle_add(self._open_camera_panel)
except ImportError:
pass
def _send_camera_list(self) -> None:
"""Send enumerated camera list to Godot via UDP."""
@ -206,6 +201,7 @@ class ChobitTray(TrayApp):
{"index": c["index"], "name": c["name"], "path": c.get("path", "")}
for c in self._cameras
]
fr.record("tray.camera_list", "Camera list sent to Godot", {"count": len(cameras)})
msg = {"cmd": "camera_list", "cameras": cameras}
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
@ -332,6 +328,7 @@ class ChobitTray(TrayApp):
):
return
if not VISION_SCRIPT.exists():
fr.record("tray.vision.error", "Vision script not found", {"path": str(VISION_SCRIPT)})
return
LOG_DIR.mkdir(parents=True, exist_ok=True)
log_file = VISION_LOG.open("a")
@ -341,19 +338,23 @@ class ChobitTray(TrayApp):
str(VISION_SCRIPT),
"--camera",
str(self._active_camera),
"--preview-port",
"19703",
],
stdout=log_file,
stderr=log_file,
)
fr.record("tray.vision.start", "Vision sidecar started", {"camera": self._active_camera})
def _stop_vision(self) -> None:
"""Stop the vision sidecar subprocess."""
if self._vision_process is not None:
fr.record("tray.vision.stop", "Vision sidecar stopped", {"camera": self._active_camera})
self._terminate(self._vision_process)
self._vision_process = None
def _cleanup(self) -> None:
"""Stop managed subprocesses."""
fr.record("tray.quit", "Tray exiting — stopping Godot and vision")
_send_command("quit")
self._stop_vision()
@ -398,6 +399,10 @@ class ChobitTray(TrayApp):
fr.record("tray.open_settings", "Settings panel opened")
_send_command("open_settings")
def _open_camera_settings(self) -> None:
fr.record("tray.open_camera_settings", "Camera settings panel opened")
_send_command("open_settings_page", page="camera")
def create_app(dev_mode: bool = False) -> ChobitTray:
"""Create and return a ChobitTray instance (without running the main loop)."""

View file

@ -213,8 +213,10 @@ class FaceDevTray(DevServiceTray):
def __init__(self, main_tray: ChobitTray) -> None:
self._main_tray = main_tray
menu = [
TrayMenuItem.action("Toggle Camera", self._toggle_camera),
TrayMenuItem.action("Camera Settings", self._open_camera_settings),
TrayMenuItem.separator(),
TrayMenuItem.action("Toggle Face", self._toggle_face),
TrayMenuItem.action("Toggle Gaze", self._toggle_gaze),
TrayMenuItem.action("Toggle Halo", self._toggle_gaze_halo),
TrayMenuItem.quit("Quit Face:dev"),
]
@ -261,12 +263,22 @@ class FaceDevTray(DevServiceTray):
labels["Halo"] = ""
return ("warning" if redis_ok else "stopped"), labels
def _toggle_camera(self) -> None:
def _open_camera_settings(self) -> None:
fr.record("tray.open_camera_settings", "Camera settings panel opened")
_send_command("open_settings_page", page="camera")
def _toggle_face(self) -> None:
fr.record("tray.toggle_face", "Vision sidecar toggled via Face:dev")
self._main_tray.toggle_camera()
self.set_status_labels(self.get_status_labels())
def _toggle_face(self) -> None:
fr.record("tray.toggle_face", "Face/gaze mode toggled")
def _toggle_gaze(self) -> None:
fr.record("tray.toggle_gaze", "Gaze mode toggled via Face:dev")
_send_command("toggle_gaze")
self.set_status_labels(self.get_status_labels())
def _toggle_gaze(self) -> None:
fr.record("tray.toggle_gaze", "Godot gaze mode toggled")
_send_command("toggle_gaze")
self.set_status_labels(self.get_status_labels())