Upload
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user