using Avalonia;
using Avalonia.Media;
using DodoSSH.Client.Session;
using Velopack;
namespace DodoSSH.Client.App;
internal static class Program
{
///
/// Entry point.
///
///
/// STAThread is required, not decorative: WebView2 checks the apartment state and refuses
/// to initialise on an MTA thread. Without it the terminal is simply blank on Windows. It applies to
/// everything below, which is why the Velopack call lives inside this method rather than in an entry
/// point of its own.
///
[STAThread]
public static void Main(string[] args)
{
// First, before Avalonia is even configured.
//
// The installer, the updater and the uninstaller all re-run this executable with arguments that
// mean "do the install bookkeeping and stop". Run() is what notices, does it, and exits — so on
// those runs nothing below happens at all, and that is the point rather than a side effect:
// DodoSshApp.Compose opens the SQLite cache and starts the terminal workspace's listening socket,
// and a silent installer run that reached either would be a background process holding the cache
// file open during the very file operations the installer is performing.
//
// There are deliberately no OnFirstRun or OnAfterUpdate hooks. A hook process has no passphrase,
// so the cache is bytes it cannot read, and the one thing that would want doing after an update —
// a schema migration — already runs on every ordinary launch from MainWindowViewModel.StartAsync,
// before unlock and touching no encrypted content.
VelopackApp.Build().Run();
KeepTheWebViewProfileOutOfTheInstallDirectory();
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
///
/// Puts WebView2's user data folder beside the vault cache instead of beside the executable.
///
///
///
/// WebView2 defaults this to a directory next to the host executable. Under a Velopack install that is
/// %LOCALAPPDATA%\DodoSSH.Desktop\current\, and current\ is replaced by every
/// update — so the browser profile would be destroyed on each one, and the first connect afterwards
/// would pay a cold WebView2 start: a new user data directory and a fresh process tree, which is the
/// slow path TerminalWorkspaceOptions.RendererTimeout's fifteen seconds was sized for. It would
/// land at the exact moment somebody is most ready to believe the update broke the terminal.
///
///
/// The profile directory is the right home because Velopack never touches it — the pack id is
/// deliberately not DodoSSH, so the install root and ClientPaths.DataDirectory are
/// siblings rather than the same folder. See docs/adr/0013-desktop-distribution-and-updates.md.
///
///
/// An environment variable rather than the control's own options, because it is read by the WebView2
/// loader before any of this application's UI exists, and because it needs no reference to whichever
/// WebView package the terminal happens to be hosted by.
///
///
private static void KeepTheWebViewProfileOutOfTheInstallDirectory()
{
if (!OperatingSystem.IsWindows())
{
return;
}
var folder = Path.Combine(ClientPaths.Default.DataDirectory, "WebView2");
try
{
Directory.CreateDirectory(folder);
Environment.SetEnvironmentVariable("WEBVIEW2_USER_DATA_FOLDER", folder);
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException)
{
// Left unset, which puts the profile back beside the executable. That is a slow first connect
// after each update, not a broken terminal, and refusing to start an SSH client over it would
// be the wrong trade.
}
}
/// Used by the designer as well as by .
///
/// WithInterFont registers Inter; it does not make it the default, and until this line the
/// application shipped a font it then declined to use — falling back to Segoe UI on Windows and to
/// whatever fontconfig offered on Linux. Almost nothing visible moves, because App.axaml sets
/// MonoFont on essentially every control that draws text, but the fallback behind those is now
/// a font that travels with the build rather than one the machine is assumed to have. The layout
/// suite pins the same family, and has to: see HeadlessApp.
///
public static AppBuilder BuildAvaloniaApp() =>
AppBuilder.Configure()
.UsePlatformDetect()
.WithInterFont()
.With(new FontManagerOptions { DefaultFamilyName = "avares://Avalonia.Fonts.Inter/Assets#Inter" })
.LogToTrace();
}