fix(welcome): harden kiosk chrome + add startup/WebView2 diagnostics
All checks were successful
Build SilverMetal Enhanced - Windows ISO / build (pull_request) Successful in 5m29s
All checks were successful
Build SilverMetal Enhanced - Windows ISO / build (pull_request) Successful in 5m29s
4th VM e2e: kiosk shell engages + the app launches fullscreen, but the Blazor wizard renders BLANK and the kiosk chrome didn't apply (title bar present) — the app didn't crash, so there's no log to read. Two changes: 1) ApplyKioskChrome made defensive (null-guard HWND/AppWindow, FullScreen presenter only, returns bool) and wrapped in try/catch at the call site, so a chrome failure can never stall app/WebView startup (the likely cause of the blank). 2) Always-on file log at C:\ProgramData\SilverMetal\welcome.log: app ctor, window create, chrome result, unhandled exceptions, and the BlazorWebView/WebView2 lifecycle (Initialized, NavigationCompleted, ProcessFailed). If the wizard is still blank next run, this pinpoints whether WebView2 env creation failed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
24
windows/welcome/src/SilverOS.Welcome.App/Diagnostics.cs
Normal file
24
windows/welcome/src/SilverOS.Welcome.App/Diagnostics.cs
Normal file
@@ -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 */ }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:SilverOS.Welcome.App"
|
||||
xmlns:components="clr-namespace:SilverOS.Welcome.App.Components;assembly=SilverOS.Welcome.UI"
|
||||
x:Class="SilverOS.Welcome.App.MainPage">
|
||||
|
||||
<BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html">
|
||||
<BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html"
|
||||
BlazorWebViewInitialized="OnBlazorInitialized"
|
||||
UrlLoading="OnUrlLoading">
|
||||
<BlazorWebView.RootComponents>
|
||||
<RootComponent Selector="#app" ComponentType="{x:Type components:Routes}" />
|
||||
</BlazorWebView.RootComponents>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user