4 - A Desktop App
A Desktop App
A desktop app is the CMD app with a window on top. The broker client is UI-framework-agnostic, so the rule is simple: the connection lives in a small service, the UI binds to it, and no XAML/AXAML ever calls a worker. WPF, Avalonia, and OpenSilver differ only in the view layer.
The .csproj
Start from the CMD app's project and add the UI framework. WPF (Windows-only):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\XRUIOS.SecureLayer\XRUIOS.SecureLayer.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="EclipseProject"><HintPath>..\..\libs\EclipseProject.dll</HintPath></Reference>
</ItemGroup>
</Project>
The XRUIOS references are identical to the CMD app - XRUIOS.SecureLayer + EclipseProject.dll. Only the SDK properties change:
| Framework | Change from the CMD app |
|---|---|
| WPF | OutputType=WinExe, TargetFramework=net10.0-windows, <UseWPF>true</UseWPF> |
| Avalonia | keep net10.0 (cross-platform); add the Avalonia + Avalonia.Desktop packages |
| OpenSilver | the OpenSilver SDK/packages; the service below is unchanged |
The client stack does not care which you pick.
The service (this is the whole app)
Wrap the four-line client in a class the UI can hold. Nothing here is WPF- or Avalonia-specific.
public sealed class XruiosClient
{
private EclipseSecureClient? _mgr;
public async Task ConnectAsync()
{
var (addr, id, psk) = AppCreds.LoadFor("mydesktopapp"); // env / creds file, as in the CMD app
_mgr = await EclipseSecureClient.ConnectAsync(addr, id + "-ui", Convert.FromBase64String(psk), identity: id);
}
public Task<string> GetDayAsync(string day) =>
_mgr!.InvokeAsync<string>("GetEvents", new() { ["day"] = day });
public Task<string> AddAsync(string day, string summary) =>
_mgr!.InvokeAsync<string>("AddEvent", new() { ["day"] = day, ["summary"] = summary });
}
The view model binds to it
await marshals back to the UI thread, so the only discipline is: don't block on the connection, and do the work off the constructor.
public sealed class DayViewModel : ObservableObject
{
private readonly XruiosClient _client;
public ObservableCollection<string> Events { get; } = new();
public async Task LoadAsync(string day)
{
string raw = await _client.GetDayAsync(day); // off the UI thread
Events.Clear();
foreach (var line in raw.Split(';')) Events.Add(line.Trim()); // back on it
}
}
The window is then just a list bound to Events and a button that calls AddAsync then LoadAsync. The XAML never sees the broker, never holds the PSK, never names a worker - it only knows the view model.
Three things a window must respect
- Connect once, async, off the constructor. Do the handshake in
OnStartup(WPF) /OnFrameworkInitializationCompleted(Avalonia), show a "connecting" state, and enable the UI only when it's up. A failed handshake is a normal state - "XRUIOS is locked, log in first" - not a crash. - A denied capability is a UI affordance, not a swallowed exception. If you weren't granted
DeleteEvent, don't render a delete button. The broker refuses it regardless, but a good app doesn't offer what it can't do. Catch the denial and disable. - The PSK still comes from the Manager. A desktop app is more tempting to ship with a baked-in secret. Don't - load it from the creds the Manager wrote, exactly like the console app. Ship the exe with no secret in it.
That is the entire difference between a desktop app and a console app: the connection is async, the denials are visible, and the four-line client sits behind a view model instead of Main.
Next: untrusted code, same gate - 5 - An Aether Mod.