From c9c56ea53baef08fbf523690e9a0af618c127ddd Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 3 Oct 2026 18:49:04 +0200 Subject: [PATCH] Stufe 2 (#764): Huelle ClawdDotNet.Shell mit eingebettetem Chromium Avalonia 11.3.17 + CefGlue.Next 152 in eigenem Ordner shell/. Startet den Manager (ManagerStarter, nach Core gezogen), holt ueber den lokalen Socket einen Einmal-Link und zeigt die WebUI angemeldet; Taskleistensymbol mit Zustand. ClawdDotNet ohne Schalter startet die Huelle, sonst den Browser. publish.py baut sie mit (--no-shell zum Weglassen), unter Linux mit den Chromium-Ressourcen neben libcef.so. --selftest fuer die Pruefung ohne Blick. Co-Authored-By: Claude Opus 5.5 --- ClawdDotNet.slnx | 1 + deploy/publish.py | 48 ++++- .../Instances/ManagerStarter.cs | 165 ++++++++++++++++++ src/ClawdDotNet.Desktop/LauncherRun.cs | 161 +++-------------- src/ClawdDotNet.Shell/Assets/app.ico | Bin 0 -> 177470 bytes .../ClawdDotNet.Shell.csproj | 50 ++++++ src/ClawdDotNet.Shell/Program.cs | 124 +++++++++++++ src/ClawdDotNet.Shell/ShellApp.cs | 135 ++++++++++++++ src/ClawdDotNet.Shell/Texts.cs | 26 +++ src/ClawdDotNet.Shell/app.manifest | 10 ++ 10 files changed, 579 insertions(+), 141 deletions(-) create mode 100644 src/ClawdDotNet.Core/Instances/ManagerStarter.cs create mode 100644 src/ClawdDotNet.Shell/Assets/app.ico create mode 100644 src/ClawdDotNet.Shell/ClawdDotNet.Shell.csproj create mode 100644 src/ClawdDotNet.Shell/Program.cs create mode 100644 src/ClawdDotNet.Shell/ShellApp.cs create mode 100644 src/ClawdDotNet.Shell/Texts.cs create mode 100644 src/ClawdDotNet.Shell/app.manifest diff --git a/ClawdDotNet.slnx b/ClawdDotNet.slnx index 6507084..0b90662 100644 --- a/ClawdDotNet.slnx +++ b/ClawdDotNet.slnx @@ -12,6 +12,7 @@ + diff --git a/deploy/publish.py b/deploy/publish.py index da490d4..1dd2906 100644 --- a/deploy/publish.py +++ b/deploy/publish.py @@ -36,6 +36,7 @@ from pathlib import Path REPO = Path(__file__).resolve().parent.parent PROJECT = REPO / "src" / "ClawdDotNet.Desktop" / "ClawdDotNet.Desktop.csproj" MANAGER_PROJECT = REPO / "src" / "ClawdDotNet.ManagerApp" / "ClawdDotNet.ManagerApp.csproj" +SHELL_PROJECT = REPO / "src" / "ClawdDotNet.Shell" / "ClawdDotNet.Shell.csproj" CONFIG = REPO / "deploy" / "packager.config.json" PACKAGER = Path( "J:/Softwareprojekte/Deploymentcenter/client-dotnet/Deploymentcenter.Packager" @@ -59,7 +60,7 @@ def run(cmd: list[str], **kw) -> None: sys.exit(f"Abgebrochen (Rueckgabewert {result.returncode}): {cmd[0]}") -def publish(rid: str, out_dir: Path) -> None: +def publish(rid: str, out_dir: Path, include_shell: bool = True) -> None: if out_dir.exists(): shutil.rmtree(out_dir) out_dir.mkdir(parents=True) @@ -90,6 +91,9 @@ def publish(rid: str, out_dir: Path) -> None: ]) merge_manager(manager_dir, out_dir) + if include_shell: + publish_shell(rid, out_dir, stamp) + # #795: Die systemd-Vorlagen gehoeren ins Linux-Paket - sonst steht die Anleitung in # einem Repository, das auf der Zielmaschine niemand hat. if rid.startswith("linux"): @@ -100,6 +104,44 @@ def publish(rid: str, out_dir: Path) -> None: print(f" systemd-Vorlagen: {len(list(units.glob('*.service')))} nach systemd/") +def publish_shell(rid: str, out_dir: Path, stamp: str) -> None: + """#764: Die Huelle - Fenster mit eingebettetem Chromium - nach shell/. + + Eigener Ordner, weil sie Avalonia 11.3 mitbringt (CefGlue ist dagegen gebaut) und der + Instanzprozess weder Avalonia noch Chromium laden soll. Kein merge_manager: Die + Bibliotheken der Huelle sollen gerade NICHT neben denen der Instanz liegen. + + build statt publish: Das Chromium-Laufzeitpaket kopiert seine Dateien nur im + AfterBuild-Ziel mit gesetzter Plattform; ein publish liess sie aus (23 MB statt + ~370 MB, und kein libcef). Unter Linux sucht Chromium icudtl.dat und die .pak-Dateien + neben libcef.so, das Paket legt sie nach Resources/ - sie werden hochgezogen. + """ + shell_dir = out_dir / "shell" + if shell_dir.exists(): + shutil.rmtree(shell_dir) + + run([ + "dotnet", "build", str(SHELL_PROJECT), + "-c", "Release", "-r", rid, "--self-contained", "false", + "-o", str(shell_dir), "--nologo", "-v", "q", stamp, + ]) + + resources = shell_dir / "Resources" + if rid.startswith("linux") and resources.is_dir(): + for item in resources.iterdir(): + target = shell_dir / item.name + if target.exists(): + shutil.rmtree(target) if target.is_dir() else target.unlink() + shutil.move(str(item), str(target)) + resources.rmdir() + + for pdb in shell_dir.rglob("*.pdb"): + pdb.unlink() + + size = sum(f.stat().st_size for f in shell_dir.rglob("*") if f.is_file()) + print(f" Huelle: {size / 1_048_576:.1f} MB nach shell/") + + def merge_manager(src: Path, dst: Path) -> None: """Legt den Manager neben die Instanz. @@ -183,6 +225,8 @@ def main() -> None: parser.add_argument("--agent-file", type=Path, help="vorhandene Kopie des update-agent statt Download; " "die Pruefsumme wird trotzdem gegen installer.json geprueft") + parser.add_argument("--no-shell", action="store_true", + help="die Huelle (Fenster mit Chromium, ~370 MB) nicht mitliefern") parser.add_argument("--dry-run", action="store_true", help="nur bauen und pruefen, nicht hochladen") args = parser.parse_args() @@ -193,7 +237,7 @@ def main() -> None: out_dir = Path(tempfile.gettempdir()) / f"clawddotnet-publish-{args.rid}" print("\n[1/3] dotnet publish") - publish(args.rid, out_dir) + publish(args.rid, out_dir, include_shell=not args.no_shell) print("\n[2/3] update-agent") if args.no_agent: diff --git a/src/ClawdDotNet.Core/Instances/ManagerStarter.cs b/src/ClawdDotNet.Core/Instances/ManagerStarter.cs new file mode 100644 index 0000000..0dc6609 --- /dev/null +++ b/src/ClawdDotNet.Core/Instances/ManagerStarter.cs @@ -0,0 +1,165 @@ +using System.Diagnostics; +using System.Net.Sockets; +using System.Text.Json; +using ClawdDotNet.Core.Storage; + +namespace ClawdDotNet.Core.Instances; + +/// +/// #764: Finds the running instance manager, or starts it — for the two programs a person starts +/// by hand: ClawdDotNet without a switch and the shell (ClawdDotNet.Shell). +/// +/// Never a second manager. If the manager answers, it is left alone. If it runs as a +/// systemd service on this machine, the service is started — starting the program next to it +/// would put two managers on the same instances. Only otherwise is the manager program started +/// directly. +/// +public static class ManagerStarter +{ + public const int DefaultPort = 7000; + public const string ServiceUnit = "clawddotnet-manager.service"; + private static readonly TimeSpan StartWait = TimeSpan.FromSeconds(30); + + /// Where manager.json lives — and next to it the manager's local socket. + public static string ConfigDirectory => AppPaths.ConfigDirectory; + + /// + /// Makes sure the manager answers. Returns the port, or the reason it does not answer. + /// + /// Folder of the calling program; the manager is looked for there and one level up. + public static async Task<(int Port, string? Problem)> EnsureRunningAsync(string searchFrom) + { + var port = Port(ConfigDirectory); + if (Answers(port)) + return (port, null); + + if (Start(searchFrom) is { } problem) + return (port, problem); + + var until = DateTime.UtcNow + StartWait; + while (!Answers(port) && DateTime.UtcNow < until) + await Task.Delay(500).ConfigureAwait(false); + + return Answers(port) + ? (port, null) + : (port, $"The instance manager did not answer on port {port} within {StartWait.TotalSeconds:0} s. " + + "Its log is under Logs/Manager in the data directory."); + } + + public static bool Answers(int port) + { + try + { + using var client = new TcpClient(); + return client.ConnectAsync("127.0.0.1", port).Wait(TimeSpan.FromSeconds(1)) && client.Connected; + } + catch (Exception) + { + return false; + } + } + + /// + /// The port from manager.json; the default on any doubt. Read as one field, not as the + /// manager's settings type, which the callers do not know. + /// + public static int Port(string configDirectory) + { + try + { + var file = Path.Combine(configDirectory, "manager.json"); + if (!File.Exists(file)) return DefaultPort; + + using var doc = JsonDocument.Parse(File.ReadAllText(file)); + return doc.RootElement.TryGetProperty("webUiPort", out var port) + && port.ValueKind == JsonValueKind.Number + && port.TryGetInt32(out var value) + && value is > 0 and < 65536 + ? value + : DefaultPort; + } + catch (Exception) + { + return DefaultPort; + } + } + + /// Why the manager could not be started, or null. + private static string? Start(string searchFrom) + { + if (OperatingSystem.IsLinux() && Systemctl("cat", ServiceUnit) == 0) + { + return Systemctl("start", ServiceUnit) == 0 + ? null + : $"systemctl --user start {ServiceUnit} failed; see journalctl --user -u {ServiceUnit}."; + } + + if (FindExecutable(searchFrom) is not { } manager) + return "The instance manager (ClawdDotNet.Instanzmanager) was not found next to this program."; + + try + { + // On Windows with its own console window: that is where the manager shows the access + // token on first start. Elsewhere a plain child that outlives the caller. + Process.Start(new ProcessStartInfo(manager) + { + UseShellExecute = OperatingSystem.IsWindows(), + WorkingDirectory = Path.GetDirectoryName(manager)! + }); + return null; + } + catch (Exception ex) + { + return $"The instance manager could not be started: {ex.Message}"; + } + } + + private static int Systemctl(string verb, string unit) + { + try + { + var start = new ProcessStartInfo("systemctl") { RedirectStandardOutput = true, RedirectStandardError = true }; + start.ArgumentList.Add("--user"); + start.ArgumentList.Add(verb); + start.ArgumentList.Add(unit); + using var p = Process.Start(start)!; + p.StandardOutput.ReadToEnd(); + p.StandardError.ReadToEnd(); + p.WaitForExit(30_000); + return p.HasExited ? p.ExitCode : -1; + } + catch (Exception) + { + return -1; // no systemd here + } + } + + /// + /// Next to the caller, one level up (the shell lives in shell/), or in the build folders + /// of a development checkout. + /// + /// Built from segments, not as a string: a path with backslashes is one file name on + /// Linux, and the search never found anything there (moved here from the old + /// App.axaml.cs). + /// + public static string? FindExecutable(string searchFrom) + { + var name = OperatingSystem.IsWindows() ? "ClawdDotNet.Instanzmanager.exe" : "ClawdDotNet.Instanzmanager"; + + string[][] candidates = + [ + [], + [".."], + ["..", "..", "..", "..", "ClawdDotNet.ManagerApp", "bin", "Debug", "net10.0"], + ["..", "..", "..", "..", "..", "src", "ClawdDotNet.ManagerApp", "bin", "Debug", "net10.0"] + ]; + + foreach (var segments in candidates) + { + var path = Path.GetFullPath(Path.Combine([searchFrom, .. segments, name])); + if (File.Exists(path)) return path; + } + + return null; + } +} diff --git a/src/ClawdDotNet.Desktop/LauncherRun.cs b/src/ClawdDotNet.Desktop/LauncherRun.cs index 3b35a4b..853fc38 100644 --- a/src/ClawdDotNet.Desktop/LauncherRun.cs +++ b/src/ClawdDotNet.Desktop/LauncherRun.cs @@ -1,6 +1,5 @@ using System.Diagnostics; -using System.Net.Sockets; -using System.Text.Json; +using ClawdDotNet.Core.Instances; using ClawdDotNet.Core.Storage; namespace ClawdDotNet.Desktop; @@ -11,47 +10,29 @@ namespace ClawdDotNet.Desktop; /// /// Until #764 that opened the Avalonia window. The windows are gone; the web interface is /// the only one. What a person who starts the program wants is therefore: the instance manager -/// running, and its web interface in front of them. Until the shell with its own window exists -/// (#764, stage 2) the system browser shows it. +/// running, and its web interface in front of them — in the shell's window +/// (shell/ClawdDotNet.Shell) where the package brings it, otherwise in the system +/// browser. /// -/// Never a second manager. If the manager answers, it is left alone. If it runs as -/// a systemd service on this machine, the service is started — starting the program next to it -/// would put two managers on the same instances. Only otherwise is the manager program started -/// directly. +/// The shell is a program of its own, in its own folder: it carries Avalonia 11.3 and +/// Chromium, and the instance process — started once per instance by the manager — should load +/// neither. /// internal static class LauncherRun { private const int ExitOk = 0; private const int ExitNoManager = 6; - private const int DefaultPort = 7000; - private const string ServiceUnit = "clawddotnet-manager.service"; - private static readonly TimeSpan StartWait = TimeSpan.FromSeconds(30); - public static async Task RunAsync() { - var port = ManagerPort(); + if (StartShell()) + return ExitOk; - if (!Answers(port)) + var (port, problem) = await ManagerStarter.EnsureRunningAsync(AppContext.BaseDirectory); + if (problem is not null) { - var problem = StartManager(); - if (problem is not null) - { - await Console.Error.WriteLineAsync(problem); - return ExitNoManager; - } - - var until = DateTime.UtcNow + StartWait; - while (!Answers(port) && DateTime.UtcNow < until) - await Task.Delay(500); - - if (!Answers(port)) - { - await Console.Error.WriteLineAsync( - $"The instance manager did not answer on port {port} within {StartWait.TotalSeconds:0} s. " - + "Its log is under Logs/Manager in the data directory."); - return ExitNoManager; - } + await Console.Error.WriteLineAsync(problem); + return ExitNoManager; } var url = $"http://localhost:{port}/"; @@ -60,121 +41,23 @@ internal static class LauncherRun return ExitOk; } - /// Why the manager could not be started, or null. - private static string? StartManager() + /// The shell starts the manager itself and signs its window in. + private static bool StartShell() { - if (OperatingSystem.IsLinux() && Systemctl("cat", ServiceUnit) == 0) - { - return Systemctl("start", ServiceUnit) == 0 - ? null - : $"systemctl --user start {ServiceUnit} failed; see journalctl --user -u {ServiceUnit}."; - } - - if (FindManagerExecutable() is not { } manager) - { - return "The instance manager (ClawdDotNet.Instanzmanager) was not found next to this program."; - } + var name = OperatingSystem.IsWindows() ? "ClawdDotNet.Shell.exe" : "ClawdDotNet.Shell"; + var shell = Path.Combine(AppContext.BaseDirectory, "shell", name); + if (!File.Exists(shell)) + return false; try { - // On Windows with its own console window: that is where the manager shows the access - // token on first start. Elsewhere a plain child that outlives us. - Process.Start(new ProcessStartInfo(manager) - { - UseShellExecute = OperatingSystem.IsWindows(), - WorkingDirectory = Path.GetDirectoryName(manager)! - }); - return null; + Process.Start(new ProcessStartInfo(shell) { UseShellExecute = false, WorkingDirectory = Path.GetDirectoryName(shell)! }); + return true; } catch (Exception ex) { - return $"The instance manager could not be started: {ex.Message}"; - } - } - - private static bool Answers(int port) - { - try - { - using var client = new TcpClient(); - return client.ConnectAsync("127.0.0.1", port).Wait(TimeSpan.FromSeconds(1)) && client.Connected; - } - catch (Exception) - { + Console.Error.WriteLine($"The shell could not be started ({ex.Message}); opening the browser instead."); return false; } } - - private static int Systemctl(string verb, string unit) - { - try - { - var start = new ProcessStartInfo("systemctl") { RedirectStandardOutput = true, RedirectStandardError = true }; - start.ArgumentList.Add("--user"); - start.ArgumentList.Add(verb); - start.ArgumentList.Add(unit); - using var p = Process.Start(start)!; - p.StandardOutput.ReadToEnd(); - p.StandardError.ReadToEnd(); - p.WaitForExit(30_000); - return p.HasExited ? p.ExitCode : -1; - } - catch (Exception) - { - return -1; // no systemd here - } - } - - /// - /// Next to this program in an installation; the build folders in a development checkout. - /// - /// Built from segments, not as a string: a path with backslashes is one file name on - /// Linux, and the search never found anything there (moved here from App.axaml.cs). - /// - private static string? FindManagerExecutable() - { - var name = OperatingSystem.IsWindows() ? "ClawdDotNet.Instanzmanager.exe" : "ClawdDotNet.Instanzmanager"; - - var nextTo = Path.Combine(AppContext.BaseDirectory, name); - if (File.Exists(nextTo)) return nextTo; - - string[][] developmentPaths = - [ - ["..", "..", "..", "..", "ClawdDotNet.ManagerApp", "bin", "Debug", "net10.0"], - ["..", "..", "..", "..", "..", "src", "ClawdDotNet.ManagerApp", "bin", "Debug", "net10.0"] - ]; - - foreach (var segments in developmentPaths) - { - var path = Path.GetFullPath(Path.Combine([AppContext.BaseDirectory, .. segments, name])); - if (File.Exists(path)) return path; - } - - return null; - } - - /// - /// The port from manager.json; the default on any doubt. Read as one field, not as - /// the manager's settings type, which this program does not know. - /// - private static int ManagerPort() - { - try - { - var file = Path.Combine(AppPaths.ConfigDirectory, "manager.json"); - if (!File.Exists(file)) return DefaultPort; - - using var doc = JsonDocument.Parse(File.ReadAllText(file)); - return doc.RootElement.TryGetProperty("webUiPort", out var port) - && port.ValueKind == JsonValueKind.Number - && port.TryGetInt32(out var value) - && value is > 0 and < 65536 - ? value - : DefaultPort; - } - catch (Exception) - { - return DefaultPort; - } - } } diff --git a/src/ClawdDotNet.Shell/Assets/app.ico b/src/ClawdDotNet.Shell/Assets/app.ico new file mode 100644 index 0000000000000000000000000000000000000000..0dd160569504cb3323c47ce5fa8cc7777a0d7434 GIT binary patch literal 177470 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≦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≶{s2a`4(S4{n1q%3u;eXebH{HN^A zlk=r~7nyc&huvPt;oeP44+(GAVYe6CGbSA$!lT!5O!(q+Hu6AV%U_H=s~9zG#| zVNT!g_OPd$;r2V<*S>v^$6ePZI6s=-ewTaOn&s2sGgM@)+L)EKWJy+5CB0W%88cq- XtHC5r!o!Az++A5&lNJQaOWpqm*AGRk literal 0 HcmV?d00001 diff --git a/src/ClawdDotNet.Shell/ClawdDotNet.Shell.csproj b/src/ClawdDotNet.Shell/ClawdDotNet.Shell.csproj new file mode 100644 index 0000000..9a54f41 --- /dev/null +++ b/src/ClawdDotNet.Shell/ClawdDotNet.Shell.csproj @@ -0,0 +1,50 @@ + + + + + WinExe + + Exe + net10.0 + enable + enable + ClawdDotNet.Shell + ClawdDotNet.Shell + Assets\app.ico + + app.manifest + + false + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ClawdDotNet.Shell/Program.cs b/src/ClawdDotNet.Shell/Program.cs new file mode 100644 index 0000000..24458e7 --- /dev/null +++ b/src/ClawdDotNet.Shell/Program.cs @@ -0,0 +1,124 @@ +using System.Net.Http.Json; +using Avalonia; +using ClawdDotNet.Core.Instances; +using ClawdDotNet.Core.Storage; +using Xilium.CefGlue; +using Xilium.CefGlue.BrowserProcess; +using Xilium.CefGlue.Common; + +namespace ClawdDotNet.Shell; + +/// +/// #764, stage 2: the shell. Starts the instance manager if it is not running, asks it for a +/// one-time sign-in link over its local socket and shows the web interface in its own window, +/// signed in. The manager keeps running when the window closes — it supervises the instances, +/// the window only shows them. +/// +internal static class Program +{ + private const int ExitOk = 0; + private const int ExitNoManager = 6; + + /// Where the window points; set before Avalonia starts. + internal static string StartUrl { get; private set; } = ""; + + internal static int ManagerPort { get; private set; } + + /// + /// --selftest: load the page, report whether the sign-in worked and the code left the + /// address, then end. For checking a package on a machine where nobody looks at the screen. + /// + internal static bool SelfTest { get; private set; } + + public static int Main(string[] args) + { + // Chromium starts its helper processes from this same executable; they end here. + CefSubProcess.Run(args, true); + + SelfTest = args.Contains("--selftest"); + + // One shell per user is enough: a second one would only open a second window. + using var single = new Mutex(true, "ClawdDotNet.Shell." + Environment.UserName, out var first); + if (!first) + return ExitOk; + + var (port, problem) = ManagerStarter.EnsureRunningAsync(AppContext.BaseDirectory).GetAwaiter().GetResult(); + if (problem is not null) + { + Console.Error.WriteLine(problem); + return ExitNoManager; + } + + ManagerPort = port; + StartUrl = SignInUrl(port); + + // Without a display (a server, an SSH session) there is no window to show — the system + // browser is the next best thing, and on a server usually not there either. Say where to go. + if (OperatingSystem.IsLinux() + && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DISPLAY")) + && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WAYLAND_DISPLAY"))) + { + Console.WriteLine($"No display. The web interface: http://localhost:{port}/"); + return ExitOk; + } + + var cache = Path.Combine(AppPaths.DataDirectory, "shell-cache"); + var resources = ResourcesDirectory(); + + AppDomain.CurrentDomain.ProcessExit += (_, _) => CefRuntime.Shutdown(); + + return AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .AfterSetup(_ => CefRuntimeLoader.Initialize( + new CefSettings + { + RootCachePath = cache, + ResourcesDirPath = resources, + LocalesDirPath = Path.Combine(resources, "locales"), + LogSeverity = CefLogSeverity.Warning, + LogFile = Path.Combine(AppPaths.DataDirectory, "Logs", "shell-cef.log") + }, + // Without no-first-run Chromium waits behind a terms-of-service dialog that + // nobody can see inside this window (found on Linux, 2026-10-02). + [new KeyValuePair("no-first-run", "1"), + new KeyValuePair("no-default-browser-check", "1")])) + .StartWithClassicDesktopLifetime(args); + } + + /// + /// A one-time link from the manager's local socket; without it the plain address, where the + /// sign-in page asks for the token as before. + /// + private static string SignInUrl(int port) + { + try + { + using var client = ManagerEndpoint.CreateClient(ManagerStarter.ConfigDirectory); + client.Timeout = TimeSpan.FromSeconds(5); + var answer = client.PostAsync("/api/shell/login-code", null).GetAwaiter().GetResult(); + if (answer.IsSuccessStatusCode + && answer.Content.ReadFromJsonAsync().GetAwaiter().GetResult() is { Url: { Length: > 0 } url }) + return url; + } + catch (Exception ex) when (ex is HttpRequestException or IOException or TaskCanceledException or TimeoutException) + { + Console.Error.WriteLine($"No one-time sign-in from the manager ({ex.Message}); the page will ask for the token."); + } + + return $"http://localhost:{port}/"; + } + + /// + /// Chromium's resources (icudtl.dat, *.pak, locales). The Linux runtime package puts them into + /// Resources/, Chromium looks next to libcef — the package step moves them; this covers a + /// development build, where they are still in Resources/. + /// + private static string ResourcesDirectory() + { + var nested = Path.Combine(AppContext.BaseDirectory, "Resources"); + return File.Exists(Path.Combine(nested, "icudtl.dat")) ? nested : AppContext.BaseDirectory; + } + + private sealed record LoginCode(string Code, string Url); +} diff --git a/src/ClawdDotNet.Shell/ShellApp.cs b/src/ClawdDotNet.Shell/ShellApp.cs new file mode 100644 index 0000000..e5098bc --- /dev/null +++ b/src/ClawdDotNet.Shell/ShellApp.cs @@ -0,0 +1,135 @@ +using System.Net.Http.Json; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform; +using Avalonia.Themes.Fluent; +using Avalonia.Threading; +using ClawdDotNet.Core.Instances; +using Xilium.CefGlue.Avalonia; + +namespace ClawdDotNet.Shell; + +/// +/// #764: The window and the tray icon. Everything shown inside the window is the web interface; +/// this class only hosts it and says, next to the clock, whether the instances run. +/// +internal sealed class ShellApp : Application +{ + private static readonly TimeSpan StatusInterval = TimeSpan.FromSeconds(15); + + private Window? _window; + private TrayIcon? _tray; + private DispatcherTimer? _timer; + + public override void Initialize() => Styles.Add(new FluentTheme()); + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + var icon = new WindowIcon(AssetLoader.Open(new Uri("avares://ClawdDotNet.Shell/Assets/app.ico"))); + + _window = new Window + { + Title = "ClawdDotNet", + Icon = icon, + Width = 1360, + Height = 880, + MinWidth = 420, + MinHeight = 480, + WindowStartupLocation = WindowStartupLocation.CenterScreen, + Content = Browser(desktop) + }; + desktop.MainWindow = _window; + + _tray = new TrayIcon + { + Icon = icon, + ToolTipText = "ClawdDotNet", + Menu = new NativeMenu + { + Items = + { + Item(Texts.Open, ShowWindow), + new NativeMenuItemSeparator(), + Item(Texts.Quit, () => desktop.Shutdown()) + } + } + }; + _tray.Clicked += (_, _) => ShowWindow(); + TrayIcon.SetIcons(this, [_tray]); + + _timer = new DispatcherTimer { Interval = StatusInterval }; + _timer.Tick += async (_, _) => await RefreshStatusAsync(); + _timer.Start(); + _ = RefreshStatusAsync(); + } + + base.OnFrameworkInitializationCompleted(); + } + + private static AvaloniaCefBrowser Browser(IClassicDesktopStyleApplicationLifetime desktop) + { + var browser = new AvaloniaCefBrowser { Address = Program.StartUrl }; + if (!Program.SelfTest) + return browser; + + browser.LoadEnd += (_, e) => + { + if (!e.Frame.IsMain) return; + _ = Task.Run(async () => + { + // The exchange runs after load; give it a moment. + await Task.Delay(3000); + var signedIn = await browser.EvaluateJavaScript("return !!sessionStorage.getItem('clawd.token')"); + var hash = await browser.EvaluateJavaScript("return location.hash"); + Console.WriteLine($"SELFTEST status={e.HttpStatusCode} signedIn={signedIn} codeInAddress={hash.Contains("code=")}"); + Dispatcher.UIThread.Post(() => desktop.Shutdown(signedIn && !hash.Contains("code=") ? 0 : 1)); + }); + }; + return browser; + } + + private static NativeMenuItem Item(string header, Action action) + { + var item = new NativeMenuItem(header); + item.Click += (_, _) => action(); + return item; + } + + private void ShowWindow() + { + if (_window is null) return; + _window.Show(); + if (_window.WindowState == WindowState.Minimized) + _window.WindowState = WindowState.Normal; + _window.Activate(); + } + + /// The manager's own view of its instances, over the local socket — no token needed there. + private async Task RefreshStatusAsync() + { + string text; + try + { + using var client = ManagerEndpoint.CreateClient(ManagerStarter.ConfigDirectory); + client.Timeout = TimeSpan.FromSeconds(3); + var status = await client.GetFromJsonAsync("/api/shell/status"); + text = status is null + ? Texts.ManagerUnknown + : !status.LicenseHolds ? Texts.NoLicense + : status.Troubled > 0 ? Texts.Troubled(status.Troubled, status.Instances) + : Texts.Running(status.Running, status.Instances); + } + catch (Exception) + { + text = Texts.ManagerDown; + } + + if (_tray is not null) + _tray.ToolTipText = $"ClawdDotNet — {text}"; + } + + private sealed record Status(int Instances, int Running, int Troubled, bool LicenseHolds, int WebUiPort); +} diff --git a/src/ClawdDotNet.Shell/Texts.cs b/src/ClawdDotNet.Shell/Texts.cs new file mode 100644 index 0000000..1b7790c --- /dev/null +++ b/src/ClawdDotNet.Shell/Texts.cs @@ -0,0 +1,26 @@ +using System.Globalization; + +namespace ClawdDotNet.Shell; + +/// +/// #764 / #661: The few texts of the shell itself — tray menu and tooltip. Everything else is the +/// web interface with its language packs. English first, German as the first additional language, +/// chosen by the system's UI language. +/// +internal static class Texts +{ + private static readonly bool German = + CultureInfo.CurrentUICulture.TwoLetterISOLanguageName.Equals("de", StringComparison.OrdinalIgnoreCase); + + public static string Open => German ? "ClawdDotNet öffnen" : "Open ClawdDotNet"; + public static string Quit => German ? "Fenster beenden (Instanzen laufen weiter)" : "Quit window (instances keep running)"; + public static string ManagerUnknown => German ? "Zustand unbekannt" : "state unknown"; + public static string ManagerDown => German ? "Instanzmanager antwortet nicht" : "instance manager not answering"; + public static string NoLicense => German ? "keine gültige Lizenz" : "no valid license"; + + public static string Running(int running, int total) + => German ? $"{running} von {total} Instanzen laufen" : $"{running} of {total} instances running"; + + public static string Troubled(int troubled, int total) + => German ? $"{troubled} von {total} Instanzen gestört" : $"{troubled} of {total} instances in trouble"; +} diff --git a/src/ClawdDotNet.Shell/app.manifest b/src/ClawdDotNet.Shell/app.manifest new file mode 100644 index 0000000..46acd94 --- /dev/null +++ b/src/ClawdDotNet.Shell/app.manifest @@ -0,0 +1,10 @@ + + + + + + + + + +