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 partial class App : Application
|
||||||
{
|
{
|
||||||
public App()
|
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();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Window CreateWindow(IActivationState? activationState)
|
protected override Window CreateWindow(IActivationState? activationState)
|
||||||
{
|
{
|
||||||
|
Diag.Log("CreateWindow");
|
||||||
var window = new Window(new MainPage()) { Title = "SilverMetal Windows" };
|
var window = new Window(new MainPage()) { Title = "SilverMetal Windows" };
|
||||||
#if 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) =>
|
window.HandlerChanged += (s, e) =>
|
||||||
{
|
{
|
||||||
if (window.Handler?.PlatformView is Microsoft.UI.Xaml.Window native)
|
try
|
||||||
native.ApplyKioskChrome();
|
{
|
||||||
|
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
|
#endif
|
||||||
return window;
|
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"
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
xmlns:local="clr-namespace:SilverOS.Welcome.App"
|
xmlns:local="clr-namespace:SilverOS.Welcome.App"
|
||||||
xmlns:components="clr-namespace:SilverOS.Welcome.App.Components;assembly=SilverOS.Welcome.UI"
|
xmlns:components="clr-namespace:SilverOS.Welcome.App.Components;assembly=SilverOS.Welcome.UI"
|
||||||
x:Class="SilverOS.Welcome.App.MainPage">
|
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>
|
<BlazorWebView.RootComponents>
|
||||||
<RootComponent Selector="#app" ComponentType="{x:Type components:Routes}" />
|
<RootComponent Selector="#app" ComponentType="{x:Type components:Routes}" />
|
||||||
</BlazorWebView.RootComponents>
|
</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 partial class MainPage : ContentPage
|
||||||
{
|
{
|
||||||
public MainPage()
|
public MainPage()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
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
|
public static class WindowExtensions
|
||||||
{
|
{
|
||||||
// Borderless, fullscreen, non-closable kiosk window.
|
// Borderless, fullscreen, non-closable kiosk window. Returns true when applied.
|
||||||
public static void ApplyKioskChrome(this Microsoft.UI.Xaml.Window winuiWindow)
|
// 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);
|
var hwnd = WindowNative.GetWindowHandle(winuiWindow);
|
||||||
|
if (hwnd == IntPtr.Zero) return false;
|
||||||
var id = Win32Interop.GetWindowIdFromWindow(hwnd);
|
var id = Win32Interop.GetWindowIdFromWindow(hwnd);
|
||||||
var appWindow = AppWindow.GetFromWindowId(id);
|
var appWindow = AppWindow.GetFromWindowId(id);
|
||||||
if (appWindow.Presenter is OverlappedPresenter p)
|
if (appWindow is null) return false;
|
||||||
{
|
// FullScreen presenter is borderless (no title bar) by nature — simpler and
|
||||||
p.SetBorderAndTitleBar(false, false);
|
// more reliable than toggling OverlappedPresenter border/title-bar flags.
|
||||||
p.IsResizable = false; p.IsMaximizable = false; p.IsMinimizable = false;
|
|
||||||
}
|
|
||||||
appWindow.SetPresenter(AppWindowPresenterKind.FullScreen);
|
appWindow.SetPresenter(AppWindowPresenterKind.FullScreen);
|
||||||
// Block the close box; the wizard exits by rebooting, not by closing.
|
|
||||||
appWindow.Closing += (s, e) => e.Cancel = true;
|
appWindow.Closing += (s, e) => e.Cancel = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user