"""Wires everything together: QApplication, the pet window, the tray icon, the history / wake-tuner windows, the global push-to-talk hotkey, and the background PetController thread that owns the mic/wake/server/TTS pipeline. Everything the controller wants the UI to do arrives as a Qt signal, so the worker thread never touches a widget. """ from __future__ import annotations import sys from PySide6.QtCore import QThread from PySide6.QtWidgets import QApplication from .. import config from ..controller import PetController from ..hotkey import GlobalHotkey from ..state import PetState from .history_window import HistoryWindow from .pet_window import PetWindow from .tray import PetTray from .wake_tuner import WakeTunerWindow def _log(message: str) -> None: print(message, flush=True) def run() -> int: app = QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) # tray-driven app; closing the pet isn't "quit" window = PetWindow() window.show() controller = PetController() thread = QThread() controller.moveToThread(thread) thread.started.connect(controller.run) controller.state_changed.connect(lambda value: window.set_state(PetState(value))) controller.said.connect(window.say) controller.log.connect(_log) controller.action.connect(window.apply_action) # petctl move/emote/say/... controller.finished.connect(thread.quit) window.talk_requested.connect(controller.request_talk_now) window.copied.connect(lambda text: _log(f"Copied to clipboard: {text[:60]}")) history_window = HistoryWindow(controller.history) tuner_window = WakeTunerWindow( get_threshold=controller.wake_threshold, set_threshold=controller.set_wake_threshold, get_stats=controller.wake_stats, on_reset=controller.reset_wake_stats, ) def _set_nap(napping: bool) -> None: # Clicking the tray item pins the state; the schedule takes over again # only after a restart or a `petctl nap off`. controller.set_napping(napping) window.set_napping(napping) tray = PetTray( on_talk_now=controller.request_talk_now, on_toggle_mute=controller.toggle_mute, on_quit=app.quit, on_set_wander=window.set_wander_enabled, wander_enabled=config.PET_WANDER, on_set_click_through=window.set_click_through, click_through_enabled=config.PET_CLICK_THROUGH, on_set_nap=_set_nap, on_show_history=history_window.show_refreshed, on_show_wake_tuner=tuner_window.show_refreshed, ) def _handle_napping(napping: bool) -> None: window.set_napping(napping) tray.set_napping(napping) controller.napping.connect(_handle_napping) # Push-to-talk: a global hook, because the pet window never has focus. # request_talk_now() only sets a threading.Event, so it's safe to call # from pynput's listener thread. hotkey = GlobalHotkey(config.PUSH_TO_TALK_HOTKEY, controller.request_talk_now) problem = hotkey.start() if problem: _log(problem) elif hotkey.running: _log(f"Push-to-talk: {config.PUSH_TO_TALK_HOTKEY}") def _shutdown() -> None: hotkey.stop() controller.stop() thread.quit() thread.wait(5000) app.aboutToQuit.connect(_shutdown) thread.start() return app.exec()