From ad1d9dee6fdce632471208ffd75752709e2d0e53 Mon Sep 17 00:00:00 2001 From: Zephyron Date: Sat, 14 Feb 2026 21:15:07 +1000 Subject: [PATCH] GUI: Add embedded WebView2 renderer for offline web applets Replace the external system browser with an embedded WebView2 instance using Avalonia's NativeControlHost and CoreWebView2 COM API directly. This renders offline web applet content (e.g., AC3 Remastered menus) inside the Ryujinx window without leaving the application. Key changes: - WebView2Host: custom NativeControlHost with Win32 child HWND - WebAppletWindow: overlay dialog hosting the WebView2 control - STAThread on Main for COM STA apartment (required by WebView2) - WM_SIZE handler for proper WebView2 resize tracking - Fallback dialog for Linux where WebView2 is unavailable Signed-off-by: Zephyron --- Directory.Packages.props | 3 + Ryujinx.sln | 96 ++++--- .../HOS/Applets/Browser/BrowserApplet.cs | 7 +- src/Ryujinx/Program.cs | 5 +- src/Ryujinx/Ryujinx.csproj | 3 + src/Ryujinx/UI/Applet/AvaHostUIHandler.cs | 130 ++++++--- src/Ryujinx/UI/Applet/WebAppletWindow.axaml | 45 +++ .../UI/Applet/WebAppletWindow.axaml.cs | 122 ++++++++ src/Ryujinx/UI/Applet/WebView2Host.cs | 266 ++++++++++++++++++ src/Ryujinx/UI/RyujinxApp.axaml.cs | 9 + 10 files changed, 595 insertions(+), 91 deletions(-) create mode 100644 src/Ryujinx/UI/Applet/WebAppletWindow.axaml create mode 100644 src/Ryujinx/UI/Applet/WebAppletWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Applet/WebView2Host.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index fd61602a8..3fe05425d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,6 +8,7 @@ + @@ -58,5 +59,7 @@ + + \ No newline at end of file diff --git a/Ryujinx.sln b/Ryujinx.sln index deddb97a0..07b996f4a 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.1.32228.430 +# Visual Studio Version 18 +VisualStudioVersion = 18.2.11415.280 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}" EndProject @@ -21,7 +21,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.GAL", "src EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.OpenGL", "src\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj", "{9558FB96-075D-4219-8FFF-401979DC0B69}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.RenderDoc", "src\Ryujinx.Graphics.RenderDocApi\Ryujinx.Graphics.RenderDocApi.csproj", "{D58FA894-27D5-4EAA-9042-AD422AD82931}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.RenderDocApi", "src\Ryujinx.Graphics.RenderDocApi\Ryujinx.Graphics.RenderDocApi.csproj", "{D58FA894-27D5-4EAA-9042-AD422AD82931}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Texture", "src\Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj", "{E1B1AD28-289D-47B7-A106-326972240207}" EndProject @@ -88,10 +88,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig .github\workflows\build.yml = .github\workflows\build.yml .github\workflows\canary.yml = .github\workflows\canary.yml - Directory.Packages.props = Directory.Packages.props Directory.Build.props = Directory.Build.props - .github\workflows\release.yml = .github\workflows\release.yml + Directory.Packages.props = Directory.Packages.props nuget.config = nuget.config + .github\workflows\release.yml = .github\workflows\release.yml EndProjectSection EndProject Global @@ -212,6 +212,18 @@ Global {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|x64.Build.0 = Release|Any CPU {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|x86.ActiveCfg = Release|Any CPU {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|x86.Build.0 = Release|Any CPU + {D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x64.ActiveCfg = Debug|Any CPU + {D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x64.Build.0 = Debug|Any CPU + {D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x86.ActiveCfg = Debug|Any CPU + {D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x86.Build.0 = Debug|Any CPU + {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|Any CPU.Build.0 = Release|Any CPU + {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x64.ActiveCfg = Release|Any CPU + {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x64.Build.0 = Release|Any CPU + {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.ActiveCfg = Release|Any CPU + {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.Build.0 = Release|Any CPU {E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.Build.0 = Debug|Any CPU {E1B1AD28-289D-47B7-A106-326972240207}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -356,6 +368,30 @@ Global {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|x64.Build.0 = Release|Any CPU {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|x86.ActiveCfg = Release|Any CPU {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|x86.Build.0 = Release|Any CPU + {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x64.ActiveCfg = Debug|Any CPU + {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x64.Build.0 = Debug|Any CPU + {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x86.ActiveCfg = Debug|Any CPU + {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x86.Build.0 = Debug|Any CPU + {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|Any CPU.Build.0 = Release|Any CPU + {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x64.ActiveCfg = Release|Any CPU + {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x64.Build.0 = Release|Any CPU + {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x86.ActiveCfg = Release|Any CPU + {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x86.Build.0 = Release|Any CPU + {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|x64.ActiveCfg = Debug|Any CPU + {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|x64.Build.0 = Debug|Any CPU + {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|x86.ActiveCfg = Debug|Any CPU + {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|x86.Build.0 = Debug|Any CPU + {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|Any CPU.ActiveCfg = Release|Any CPU + {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|Any CPU.Build.0 = Release|Any CPU + {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x64.ActiveCfg = Release|Any CPU + {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x64.Build.0 = Release|Any CPU + {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x86.ActiveCfg = Release|Any CPU + {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x86.Build.0 = Release|Any CPU {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -392,6 +428,18 @@ Global {C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|x64.Build.0 = Release|Any CPU {C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|x86.ActiveCfg = Release|Any CPU {C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|x86.Build.0 = Release|Any CPU + {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|x64.ActiveCfg = Debug|Any CPU + {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|x64.Build.0 = Debug|Any CPU + {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|x86.ActiveCfg = Debug|Any CPU + {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|x86.Build.0 = Debug|Any CPU + {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|Any CPU.Build.0 = Release|Any CPU + {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|x64.ActiveCfg = Release|Any CPU + {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|x64.Build.0 = Release|Any CPU + {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|x86.ActiveCfg = Release|Any CPU + {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|x86.Build.0 = Release|Any CPU {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -535,44 +583,6 @@ Global {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Release|x64.Build.0 = Release|Any CPU {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Release|x86.ActiveCfg = Release|Any CPU {F6F9826A-BC58-4D78-A700-F358A66B2B06}.Release|x86.Build.0 = Release|Any CPU - {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|x64.ActiveCfg = Debug|Any CPU - {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|x64.Build.0 = Debug|Any CPU - {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|x86.ActiveCfg = Debug|Any CPU - {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Debug|x86.Build.0 = Debug|Any CPU - {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|Any CPU.Build.0 = Release|Any CPU - {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|x64.ActiveCfg = Release|Any CPU - {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|x64.Build.0 = Release|Any CPU - {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|x86.ActiveCfg = Release|Any CPU - {D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}.Release|x86.Build.0 = Release|Any CPU - {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|Any CPU.Build.0 = Debug|Any CPU - {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|x64.ActiveCfg = Debug|Any CPU - {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|x64.Build.0 = Debug|Any CPU - {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|x86.ActiveCfg = Debug|Any CPU - {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Debug|x86.Build.0 = Debug|Any CPU - {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|Any CPU.ActiveCfg = Release|Any CPU - {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|Any CPU.Build.0 = Release|Any CPU - {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x64.ActiveCfg = Release|Any CPU - {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x64.Build.0 = Release|Any CPU - {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x86.ActiveCfg = Release|Any CPU - {988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x86.Build.0 = Release|Any CPU - {D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x64.ActiveCfg = Debug|Any CPU - {D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x64.Build.0 = Debug|Any CPU - {D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x86.ActiveCfg = Debug|Any CPU - {D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x86.Build.0 = Debug|Any CPU - {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|Any CPU.Build.0 = Release|Any CPU - {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x64.ActiveCfg = Release|Any CPU - {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x64.Build.0 = Release|Any CPU - {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.ActiveCfg = Release|Any CPU - {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.Build.0 = Release|Any CPU - {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs index ff17b4910..e682656a7 100644 --- a/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs @@ -151,14 +151,11 @@ namespace Ryujinx.HLE.HOS.Applets.Browser using CancellationTokenSource serverCts = new(); Task<(WebExitReason Reason, string LastUrl)> callbackTask = server.WaitForCallbackAsync(serverCts.Token); - // Open the URL in the system browser and show a waiting dialog + // Show the web content in an embedded WebView and wait for callback bool userWaited = true; try { - // Open system browser - OpenSystemBrowser(url); - - // Use the UI handler to show a blocking dialog + // Use the UI handler to display embedded web content if (_system.Device.UIHandler != null) { using CancellationTokenSource uiCts = new(); diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 8d03f81da..0ca8488dc 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -1,5 +1,6 @@ using Avalonia; using Avalonia.Threading; +using Avalonia.WebView.Desktop; using DiscordRPC; using Gommon; using Projektanker.Icons.Avalonia; @@ -45,6 +46,7 @@ namespace Ryujinx.Ava private const uint MbIconwarning = 0x30; + [STAThread] public static int Main(string[] args) { Version = ReleaseInformation.Version; @@ -127,7 +129,8 @@ namespace Ryujinx.Ava RenderingMode = UseHardwareAcceleration ? [Win32RenderingMode.AngleEgl, Win32RenderingMode.Software] : [Win32RenderingMode.Software] - }); + }) + .UseDesktopWebView(); private static bool ConsumeCommandLineArgument(ref string[] args, string targetArgument) { diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 5da152501..bb6003a53 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -51,6 +51,7 @@ + @@ -74,6 +75,8 @@ + + diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs index 90a7be4ad..1eb1ac794 100644 --- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs @@ -1,3 +1,4 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Threading; using FluentAvalonia.UI.Controls; @@ -9,6 +10,7 @@ using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; +using Ryujinx.Common.Logging; using Ryujinx.HLE; using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; @@ -19,6 +21,7 @@ using System; using System.Collections.ObjectModel; using System.Linq; using System.Threading; +using System.Threading.Tasks; namespace Ryujinx.Ava.UI.Applet { @@ -336,58 +339,30 @@ namespace Ryujinx.Ava.UI.Applet public bool DisplayWebPage(string url, string title, CancellationToken cancellationToken) { ManualResetEvent dialogCloseEvent = new(false); - bool userCancelled = false; + bool closedByCallback = false; Dispatcher.UIThread.InvokeAsync(async () => { try { - ContentDialog dialog = new() + if (OperatingSystem.IsLinux()) { - Title = title, - CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose], - Content = new TextBlock - { - Text = "A web page has been opened in your system browser.\n\n" + - "Please interact with it to continue.\n\n" + - "This dialog will close automatically when the page sends a response.\n\n" + - "Click Close to skip the web applet.", - TextWrapping = Avalonia.Media.TextWrapping.Wrap, - MaxWidth = 400, - }, - }; - - dialog.CloseButtonCommand = Commands.Create(() => + // Linux: NativeWebView is not supported, use a fallback dialog + closedByCallback = await ShowFallbackDialog(title, cancellationToken); + } + else { - if (!cancellationToken.IsCancellationRequested) - { - userCancelled = true; - } - }); - - // Register cancellation to close the dialog when the callback arrives - using CancellationTokenRegistration registration = cancellationToken.Register(() => - { - Dispatcher.UIThread.Post(() => - { - try - { - dialog.Hide(); - } - catch - { - // Dialog may already be closed - } - }); - }); - - await ContentDialogHelper.ShowAsync(dialog); + // Windows / macOS: use embedded NativeWebView in an overlay window + closedByCallback = await ShowWebViewOverlay(url, title, cancellationToken); + } } catch (Exception ex) { - Ryujinx.Common.Logging.Logger.Warning?.Print( - Ryujinx.Common.Logging.LogClass.ServiceAm, - $"Error displaying web applet dialog: {ex.Message}"); + Logger.Warning?.Print(LogClass.ServiceAm, + $"Error displaying embedded web applet: {ex.Message}"); + + // Fall back to a simple waiting dialog if WebView fails + closedByCallback = await ShowFallbackDialog(title, cancellationToken); } finally { @@ -397,6 +372,77 @@ namespace Ryujinx.Ava.UI.Applet dialogCloseEvent.WaitOne(); + return closedByCallback; + } + + /// + /// Shows an embedded NativeWebView in a full-screen overlay window (Windows/macOS). + /// + private async Task ShowWebViewOverlay(string url, string title, CancellationToken cancellationToken) + { + MainWindow mainWindow = RyujinxApp.MainWindow; + + WebAppletWindow webWindow = new() + { + Title = title, + Width = mainWindow.Bounds.Width, + Height = mainWindow.Bounds.Height, + Position = mainWindow.PointToScreen(new Point()), + }; + + webWindow.Navigate(url, cancellationToken); + + await webWindow.ShowDialog(mainWindow); + + return webWindow.ClosedByCallback; + } + + /// + /// Fallback dialog shown when WebView is unavailable (Linux) or initialization fails. + /// + private async Task ShowFallbackDialog(string title, CancellationToken cancellationToken) + { + bool userCancelled = false; + + ContentDialog dialog = new() + { + Title = title, + CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose], + Content = new TextBlock + { + Text = "The web applet is running.\n\n" + + "This dialog will close automatically when the applet completes.\n\n" + + "Click Close to skip.", + TextWrapping = Avalonia.Media.TextWrapping.Wrap, + MaxWidth = 400, + }, + }; + + dialog.CloseButtonCommand = Commands.Create(() => + { + if (!cancellationToken.IsCancellationRequested) + { + userCancelled = true; + } + }); + + using CancellationTokenRegistration registration = cancellationToken.Register(() => + { + Dispatcher.UIThread.Post(() => + { + try + { + dialog.Hide(); + } + catch + { + // Dialog may already be closed + } + }); + }); + + await ContentDialogHelper.ShowAsync(dialog); + return !userCancelled; } } diff --git a/src/Ryujinx/UI/Applet/WebAppletWindow.axaml b/src/Ryujinx/UI/Applet/WebAppletWindow.axaml new file mode 100644 index 000000000..7076d37f9 --- /dev/null +++ b/src/Ryujinx/UI/Applet/WebAppletWindow.axaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + +