dheraingoud's picture
feat: sync upstream commits up to f17c92bc
a1bab2d
Raw
History Blame Contribute Delete
4.23 kB
"""Platform-neutral lifecycle for the FCC desktop shell."""
from __future__ import annotations
import threading
from collections.abc import Callable
from typing import Protocol
from free_claude_code.cli.commands import (
ServerStatus,
ServerSupervisor,
load_server_settings,
open_admin_when_ready,
schedule_open_admin_browser,
)
from free_claude_code.cli.launchers.common import preflight_proxy
from free_claude_code.config.paths import config_dir_path
from free_claude_code.config.server_urls import local_proxy_root_url
from free_claude_code.config.settings import get_settings
from free_claude_code.core.interprocess_lock import InterprocessFileLock
class DesktopTray(Protocol):
"""UI loop owned by the platform tray adapter."""
def run(self) -> None: ...
def stop(self) -> None: ...
class DesktopTrayFactory(Protocol):
"""Construct a tray adapter around a desktop controller."""
def __call__(self, controller: DesktopController) -> DesktopTray: ...
class ServerOwner(Protocol):
"""Server lifecycle used by the desktop controller."""
@property
def status(self) -> ServerStatus: ...
def schedule_run(self) -> bool: ...
def run(self, *, open_admin_browser: bool | None = None) -> None: ...
def request_restart(self) -> bool: ...
def request_stop(self) -> None: ...
class DesktopController:
"""Coordinate one tray loop with one in-process FCC server owner."""
def __init__(
self,
supervisor: ServerOwner,
tray_factory: DesktopTrayFactory,
open_admin: Callable[[], None],
) -> None:
self._supervisor = supervisor
self._open_admin = open_admin
self._thread_lock = threading.Lock()
self._server_thread: threading.Thread | None = None
self._tray = tray_factory(self)
@property
def status(self) -> ServerStatus:
return self._supervisor.status
def run(self) -> None:
"""Run the tray on this thread and the FCC server on its owned worker."""
self._start_server()
try:
self._tray.run()
finally:
self._supervisor.request_stop()
self._tray.stop()
with self._thread_lock:
thread = self._server_thread
if thread is not None:
thread.join()
def open_admin(self) -> None:
self._open_admin()
def restart_server(self) -> None:
"""Restart an active server or relaunch one that exited unexpectedly."""
with self._thread_lock:
thread = self._server_thread
if thread is not None and thread.is_alive():
self._supervisor.request_restart()
return
self._start_server()
def quit(self) -> None:
"""Close the server gracefully and end the platform tray loop."""
self._supervisor.request_stop()
self._tray.stop()
def _start_server(self) -> None:
with self._thread_lock:
if self._server_thread is not None and self._server_thread.is_alive():
return
if not self._supervisor.schedule_run():
return
self._server_thread = threading.Thread(
target=self._run_server,
name="fcc-desktop-server",
)
self._server_thread.start()
def _run_server(self) -> None:
self._supervisor.run(open_admin_browser=False)
def launch_desktop(tray_factory: DesktopTrayFactory) -> None:
"""Start the singleton desktop host or focus the already running FCC UI."""
settings = load_server_settings()
instance_lock = InterprocessFileLock(config_dir_path() / "desktop.lock")
if not instance_lock.acquire():
open_admin_when_ready(settings)
return
try:
if preflight_proxy(local_proxy_root_url(settings)) is None:
open_admin_when_ready(settings)
return
supervisor = ServerSupervisor(console_logging=False)
def open_current_admin() -> None:
schedule_open_admin_browser(get_settings())
DesktopController(supervisor, tray_factory, open_current_admin).run()
finally:
instance_lock.release()