diff --git a/windows/welcome/src/SilverOS.Welcome.App/App.xaml.cs b/windows/welcome/src/SilverOS.Welcome.App/App.xaml.cs index 1991936..0b313e2 100644 --- a/windows/welcome/src/SilverOS.Welcome.App/App.xaml.cs +++ b/windows/welcome/src/SilverOS.Welcome.App/App.xaml.cs @@ -1,20 +1,40 @@ -namespace SilverOS.Welcome.App; +namespace SilverOS.Welcome.App; public partial class App : Application { public App() { + Diag.Log("App ctor"); + AppDomain.CurrentDomain.UnhandledException += (s, e) => + Diag.Log("UNHANDLED: " + (e.ExceptionObject as Exception)?.ToString()); + TaskScheduler.UnobservedTaskException += (s, e) => + { + Diag.Log("UNOBSERVED TASK: " + e.Exception); + e.SetObserved(); + }; InitializeComponent(); } protected override Window CreateWindow(IActivationState? activationState) { + Diag.Log("CreateWindow"); var window = new Window(new MainPage()) { Title = "SilverMetal Windows" }; #if WINDOWS + // Apply kiosk chrome once the native window handler exists. Wrapped so a + // chrome failure can NEVER break app/WebView startup (a previous version + // threw here and left the wizard blank). HandlerChanged can fire before the + // HWND is ready, so ApplyKioskChrome null-guards and re-applies safely. window.HandlerChanged += (s, e) => { - if (window.Handler?.PlatformView is Microsoft.UI.Xaml.Window native) - native.ApplyKioskChrome(); + try + { + if (window.Handler?.PlatformView is Microsoft.UI.Xaml.Window native) + { + var ok = native.ApplyKioskChrome(); + Diag.Log($"ApplyKioskChrome applied={ok}"); + } + } + catch (Exception ex) { Diag.Log("ApplyKioskChrome FAILED: " + ex); } }; #endif return window; diff --git a/windows/welcome/src/SilverOS.Welcome.App/Diagnostics.cs b/windows/welcome/src/SilverOS.Welcome.App/Diagnostics.cs new file mode 100644 index 0000000..4fbfc65 --- /dev/null +++ b/windows/welcome/src/SilverOS.Welcome.App/Diagnostics.cs @@ -0,0 +1,24 @@ +namespace SilverOS.Welcome.App; + +// Lightweight always-on file logger for first-boot diagnosis. The Welcome app +// runs as the kiosk shell with no console and (in Release) no debugger, so when +// something fails silently (blank WebView, chrome not applied) there is nowhere +// to look. This writes to a world-writable ProgramData path that survives the +// session and can be read off the disk image. +public static class Diag +{ + const string Dir = @"C:\ProgramData\SilverMetal"; + const string Path = Dir + @"\welcome.log"; + static readonly object _lock = new(); + + public static void Log(string msg) + { + try + { + System.IO.Directory.CreateDirectory(Dir); + lock (_lock) + System.IO.File.AppendAllText(Path, $"{DateTime.Now:HH:mm:ss.fff} {msg}{Environment.NewLine}"); + } + catch { /* logging must never throw */ } + } +} diff --git a/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml b/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml index 078f543..2343cb6 100644 --- a/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml +++ b/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml @@ -1,11 +1,13 @@ - + - + diff --git a/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml.cs b/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml.cs index 55e4c7c..c0a0877 100644 --- a/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml.cs +++ b/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml.cs @@ -1,9 +1,34 @@ -namespace SilverOS.Welcome.App; +using Microsoft.AspNetCore.Components.WebView; + +namespace SilverOS.Welcome.App; public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); + Diag.Log("MainPage ctor"); } + + // Fires once the platform WebView2 is created. If this never appears in the log, + // WebView2 environment creation failed (the real cause of a blank wizard). + void OnBlazorInitialized(object? sender, BlazorWebViewInitializedEventArgs e) + { + Diag.Log("BlazorWebViewInitialized"); +#if WINDOWS + try + { + var wv = e.WebView; // Microsoft.UI.Xaml.Controls.WebView2 + wv.NavigationCompleted += (a, b) => + Diag.Log($"WV2 NavigationCompleted ok={b.IsSuccess} status={b.WebErrorStatus}"); + if (wv.CoreWebView2 is not null) + wv.CoreWebView2.ProcessFailed += (a, b) => + Diag.Log("WV2 ProcessFailed: " + b.ProcessFailedKind); + } + catch (Exception ex) { Diag.Log("WV2 hook failed: " + ex.Message); } +#endif + } + + void OnUrlLoading(object? sender, UrlLoadingEventArgs e) + => Diag.Log("UrlLoading: " + e.Url); } diff --git a/windows/welcome/src/SilverOS.Welcome.App/Platforms/Windows/WindowExtensions.cs b/windows/welcome/src/SilverOS.Welcome.App/Platforms/Windows/WindowExtensions.cs index 6241ba7..3bbc16a 100644 --- a/windows/welcome/src/SilverOS.Welcome.App/Platforms/Windows/WindowExtensions.cs +++ b/windows/welcome/src/SilverOS.Welcome.App/Platforms/Windows/WindowExtensions.cs @@ -7,20 +7,21 @@ namespace SilverOS.Welcome.App; public static class WindowExtensions { - // Borderless, fullscreen, non-closable kiosk window. - public static void ApplyKioskChrome(this Microsoft.UI.Xaml.Window winuiWindow) + // Borderless, fullscreen, non-closable kiosk window. Returns true when applied. + // Defensive: HandlerChanged can fire before the HWND/AppWindow is ready, so we + // bail out cleanly and rely on a later HandlerChanged to apply it. + public static bool ApplyKioskChrome(this Microsoft.UI.Xaml.Window winuiWindow) { var hwnd = WindowNative.GetWindowHandle(winuiWindow); + if (hwnd == IntPtr.Zero) return false; var id = Win32Interop.GetWindowIdFromWindow(hwnd); var appWindow = AppWindow.GetFromWindowId(id); - if (appWindow.Presenter is OverlappedPresenter p) - { - p.SetBorderAndTitleBar(false, false); - p.IsResizable = false; p.IsMaximizable = false; p.IsMinimizable = false; - } + if (appWindow is null) return false; + // FullScreen presenter is borderless (no title bar) by nature — simpler and + // more reliable than toggling OverlappedPresenter border/title-bar flags. appWindow.SetPresenter(AppWindowPresenterKind.FullScreen); - // Block the close box; the wizard exits by rebooting, not by closing. appWindow.Closing += (s, e) => e.Cancel = true; + return true; } } #endif