Spaces:
Sleeping
Sleeping
File size: 4,230 Bytes
c817fe8 a1bab2d c817fe8 a1bab2d c817fe8 a1bab2d c817fe8 a1bab2d c817fe8 a1bab2d c817fe8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | """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()
|