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 <zephyron@citron-emu.org>
This commit is contained in:
parent
b3ec6628ef
commit
ad1d9dee6f
@ -8,6 +8,7 @@
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.6" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3719.77" />
|
||||
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.6.2" />
|
||||
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.6.2" />
|
||||
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
|
||||
@ -58,5 +59,7 @@
|
||||
<PackageVersion Include="System.IO.Hashing" Version="9.0.2" />
|
||||
<PackageVersion Include="System.Management" Version="9.0.2" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
<PackageVersion Include="WebView.Avalonia.AGPL" Version="11.0.0.2026011404" />
|
||||
<PackageVersion Include="WebView.Avalonia.Desktop" Version="11.0.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
96
Ryujinx.sln
96
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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -51,6 +51,7 @@
|
||||
<PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)'=='Debug'" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" />
|
||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" />
|
||||
<PackageReference Include="Svg.Controls.Avalonia" />
|
||||
<PackageReference Include="Svg.Controls.Skia.Avalonia" />
|
||||
<PackageReference Include="DynamicData" />
|
||||
@ -74,6 +75,8 @@
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||
<PackageReference Include="SPB" />
|
||||
<PackageReference Include="SharpZipLib" />
|
||||
<PackageReference Include="WebView.Avalonia.AGPL" />
|
||||
<PackageReference Include="WebView.Avalonia.Desktop" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows an embedded NativeWebView in a full-screen overlay window (Windows/macOS).
|
||||
/// </summary>
|
||||
private async Task<bool> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fallback dialog shown when WebView is unavailable (Linux) or initialization fails.
|
||||
/// </summary>
|
||||
private async Task<bool> 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;
|
||||
}
|
||||
}
|
||||
|
||||
45
src/Ryujinx/UI/Applet/WebAppletWindow.axaml
Normal file
45
src/Ryujinx/UI/Applet/WebAppletWindow.axaml
Normal file
@ -0,0 +1,45 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Ryujinx.Ava.UI.Applet"
|
||||
x:Class="Ryujinx.Ava.UI.Applet.WebAppletWindow"
|
||||
Title="Web Applet"
|
||||
Background="Black"
|
||||
Width="1280"
|
||||
Height="720"
|
||||
CanResize="False"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="Manual"
|
||||
SystemDecorations="Full"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
ExtendClientAreaChromeHints="NoChrome"
|
||||
ExtendClientAreaTitleBarHeightHint="-1">
|
||||
|
||||
<Grid Name="RootGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- WebView2 native host fills the main area -->
|
||||
<local:WebView2Host x:Name="WebViewHost" Grid.Row="0" />
|
||||
|
||||
<!-- Loading overlay (shown until WebView2 is ready) -->
|
||||
<TextBlock Name="LoadingText"
|
||||
Grid.Row="0"
|
||||
Text="Initializing web applet..."
|
||||
Foreground="White"
|
||||
FontSize="18"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<!-- Bottom bar with Skip button -->
|
||||
<Border Grid.Row="1" Background="#1A1A1A" Padding="8">
|
||||
<Button Name="CloseButton"
|
||||
Content="Skip"
|
||||
HorizontalAlignment="Right"
|
||||
Padding="24,6"
|
||||
FontSize="13"
|
||||
Click="CloseButton_Click" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
122
src/Ryujinx/UI/Applet/WebAppletWindow.axaml.cs
Normal file
122
src/Ryujinx/UI/Applet/WebAppletWindow.axaml.cs
Normal file
@ -0,0 +1,122 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Applet
|
||||
{
|
||||
public partial class WebAppletWindow : Window
|
||||
{
|
||||
private CancellationTokenRegistration _cancellationRegistration;
|
||||
private string _pendingUrl;
|
||||
private bool _navigated;
|
||||
|
||||
/// <summary>
|
||||
/// True if the window was closed by the callback (not by user).
|
||||
/// </summary>
|
||||
public bool ClosedByCallback { get; private set; }
|
||||
|
||||
public WebAppletWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Subscribe to WebView2Host events
|
||||
WebViewHost.WebView2Ready += OnWebView2Ready;
|
||||
WebViewHost.NavigationCompleted += OnNavigationCompleted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the URL and cancellation token. Navigation happens once WebView2 is ready.
|
||||
/// </summary>
|
||||
public void Navigate(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceAm, $"WebAppletWindow: URL queued: {url}");
|
||||
|
||||
_pendingUrl = url;
|
||||
|
||||
_cancellationRegistration = cancellationToken.Register(() =>
|
||||
{
|
||||
ClosedByCallback = true;
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Window may already be closed
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnOpened(EventArgs e)
|
||||
{
|
||||
base.OnOpened(e);
|
||||
Logger.Info?.Print(LogClass.ServiceAm, "WebAppletWindow: Window opened");
|
||||
}
|
||||
|
||||
private void OnWebView2Ready(bool success, string errorMessage)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceAm, "WebAppletWindow: WebView2 ready, navigating...");
|
||||
|
||||
if (!_navigated && !string.IsNullOrEmpty(_pendingUrl))
|
||||
{
|
||||
_navigated = true;
|
||||
WebViewHost.NavigateToUrl(_pendingUrl);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceAm,
|
||||
$"WebAppletWindow: WebView2 failed: {errorMessage}");
|
||||
|
||||
TextBlock loadingText = this.FindControl<TextBlock>("LoadingText");
|
||||
if (loadingText != null)
|
||||
{
|
||||
loadingText.Text = $"WebView2 initialization failed.\n{errorMessage}\n\nClick 'Skip' to continue.";
|
||||
loadingText.TextAlignment = Avalonia.Media.TextAlignment.Center;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnNavigationCompleted(bool success)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
// Hide loading text once page loads
|
||||
TextBlock loadingText = this.FindControl<TextBlock>("LoadingText");
|
||||
if (loadingText != null)
|
||||
{
|
||||
loadingText.IsVisible = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ClosedByCallback = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
WebViewHost.WebView2Ready -= OnWebView2Ready;
|
||||
WebViewHost.NavigationCompleted -= OnNavigationCompleted;
|
||||
_cancellationRegistration.Dispose();
|
||||
base.OnClosed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
266
src/Ryujinx/UI/Applet/WebView2Host.cs
Normal file
266
src/Ryujinx/UI/Applet/WebView2Host.cs
Normal file
@ -0,0 +1,266 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Applet
|
||||
{
|
||||
/// <summary>
|
||||
/// An Avalonia NativeControlHost that embeds a WebView2 browser instance directly
|
||||
/// using the CoreWebView2 COM API. Handles WM_SIZE to keep the WebView2 controller
|
||||
/// properly sized when Avalonia resizes the host window.
|
||||
/// </summary>
|
||||
public class WebView2Host : NativeControlHost
|
||||
{
|
||||
private const uint WM_SIZE = 0x0005;
|
||||
|
||||
private nint _hwnd;
|
||||
private CoreWebView2Controller _controller;
|
||||
private CoreWebView2Environment _environment;
|
||||
private string _pendingUrl;
|
||||
private bool _isInitialized;
|
||||
private WindowProc _wndProcDelegate;
|
||||
private string _className;
|
||||
|
||||
/// <summary>Fires when the WebView2 engine is ready (or failed). Args: (success, errorMessage).</summary>
|
||||
public event Action<bool, string> WebView2Ready;
|
||||
|
||||
/// <summary>Fires when navigation starts. Arg: URL.</summary>
|
||||
public event Action<string> NavigationStarted;
|
||||
|
||||
/// <summary>Fires when navigation completes. Arg: success.</summary>
|
||||
public event Action<bool> NavigationCompleted;
|
||||
|
||||
/// <summary>True once the WebView2 engine is fully initialized.</summary>
|
||||
public bool IsWebView2Ready => _isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// Queues a URL for navigation. If WebView2 is already initialized, navigates immediately.
|
||||
/// </summary>
|
||||
public void NavigateToUrl(string url)
|
||||
{
|
||||
_pendingUrl = url;
|
||||
|
||||
if (_controller?.CoreWebView2 != null)
|
||||
{
|
||||
_controller.CoreWebView2.Navigate(url);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return CreateWin32(parent);
|
||||
}
|
||||
|
||||
Logger.Warning?.Print(LogClass.ServiceAm, "WebView2Host: Only supported on Windows");
|
||||
return base.CreateNativeControlCore(parent);
|
||||
}
|
||||
|
||||
protected override void DestroyNativeControlCore(IPlatformHandle control)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
DestroyWin32();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DestroyNativeControlCore(control);
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private PlatformHandle CreateWin32(IPlatformHandle parent)
|
||||
{
|
||||
_className = "RyujinxWebView2-" + Guid.NewGuid();
|
||||
|
||||
// Window proc that handles WM_SIZE to keep WebView2 controller bounds in sync
|
||||
_wndProcDelegate = (hWnd, msg, wParam, lParam) =>
|
||||
{
|
||||
if ((uint)msg == WM_SIZE && _controller != null)
|
||||
{
|
||||
int width = (int)(lParam & 0xFFFF);
|
||||
int height = (int)((lParam >> 16) & 0xFFFF);
|
||||
|
||||
if (width > 0 && height > 0)
|
||||
{
|
||||
_controller.Bounds = new System.Drawing.Rectangle(0, 0, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||||
};
|
||||
|
||||
WndClassEx wndClassEx = new()
|
||||
{
|
||||
cbSize = Marshal.SizeOf<WndClassEx>(),
|
||||
hInstance = GetModuleHandle(null),
|
||||
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
||||
style = ClassStyles.CsOwndc,
|
||||
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
||||
hCursor = CreateArrowCursor(),
|
||||
};
|
||||
|
||||
RegisterClassEx(ref wndClassEx);
|
||||
|
||||
_hwnd = CreateWindowEx(
|
||||
0, _className, "WebView2Host",
|
||||
WindowStyles.WsChild,
|
||||
0, 0, 1, 1, // Initial size doesn't matter - NativeControlHost will resize via WM_SIZE
|
||||
parent.Handle, nint.Zero, nint.Zero, nint.Zero);
|
||||
|
||||
Marshal.FreeHGlobal(wndClassEx.lpszClassName);
|
||||
|
||||
if (_hwnd == nint.Zero)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceAm,
|
||||
$"WebView2Host: Failed to create child window. Error: {Marshal.GetLastWin32Error()}");
|
||||
return new PlatformHandle(nint.Zero, "HWND");
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.ServiceAm,
|
||||
$"WebView2Host: Child window created (HWND=0x{_hwnd:X})");
|
||||
|
||||
// Start async WebView2 initialization
|
||||
_ = InitializeWebView2Async();
|
||||
|
||||
return new PlatformHandle(_hwnd, "HWND");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private async Task InitializeWebView2Async()
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceAm, "WebView2Host: Creating CoreWebView2Environment...");
|
||||
|
||||
string userDataFolder = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Ryujinx", "WebView2");
|
||||
|
||||
_environment = await CoreWebView2Environment.CreateAsync(
|
||||
browserExecutableFolder: null,
|
||||
userDataFolder: userDataFolder);
|
||||
|
||||
Logger.Info?.Print(LogClass.ServiceAm,
|
||||
$"WebView2Host: Environment created (version={_environment.BrowserVersionString}). Creating controller...");
|
||||
|
||||
_controller = await _environment.CreateCoreWebView2ControllerAsync(_hwnd);
|
||||
|
||||
Logger.Info?.Print(LogClass.ServiceAm, "WebView2Host: Controller created. Configuring...");
|
||||
|
||||
// Query the actual HWND size (in physical pixels) and set controller bounds
|
||||
if (GetClientRect(_hwnd, out RECT rect))
|
||||
{
|
||||
int width = rect.Right - rect.Left;
|
||||
int height = rect.Bottom - rect.Top;
|
||||
_controller.Bounds = new System.Drawing.Rectangle(0, 0,
|
||||
Math.Max(1, width), Math.Max(1, height));
|
||||
|
||||
Logger.Info?.Print(LogClass.ServiceAm,
|
||||
$"WebView2Host: Initial bounds set to {width}x{height} (physical pixels)");
|
||||
}
|
||||
|
||||
_controller.IsVisible = true;
|
||||
|
||||
// Configure settings
|
||||
CoreWebView2Settings settings = _controller.CoreWebView2.Settings;
|
||||
settings.AreDevToolsEnabled = false;
|
||||
settings.AreDefaultContextMenusEnabled = false;
|
||||
settings.IsStatusBarEnabled = false;
|
||||
settings.IsZoomControlEnabled = false;
|
||||
|
||||
// Subscribe to events
|
||||
_controller.CoreWebView2.NavigationStarting += (s, e) =>
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceAm, $"WebView2: Navigation starting: {e.Uri}");
|
||||
NavigationStarted?.Invoke(e.Uri);
|
||||
};
|
||||
|
||||
_controller.CoreWebView2.NavigationCompleted += (s, e) =>
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceAm,
|
||||
$"WebView2: Navigation completed (Success={e.IsSuccess}, Status={e.WebErrorStatus})");
|
||||
NavigationCompleted?.Invoke(e.IsSuccess);
|
||||
};
|
||||
|
||||
_controller.CoreWebView2.NewWindowRequested += (s, e) =>
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceAm, $"WebView2: New window requested: {e.Uri}");
|
||||
e.Handled = true;
|
||||
_controller.CoreWebView2.Navigate(e.Uri);
|
||||
};
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
Logger.Info?.Print(LogClass.ServiceAm,
|
||||
$"WebView2Host: Initialization complete! Browser version: {_environment.BrowserVersionString}");
|
||||
|
||||
// Navigate to pending URL
|
||||
if (!string.IsNullOrEmpty(_pendingUrl))
|
||||
{
|
||||
Logger.Info?.Print(LogClass.ServiceAm, $"WebView2Host: Navigating to pending URL: {_pendingUrl}");
|
||||
_controller.CoreWebView2.Navigate(_pendingUrl);
|
||||
}
|
||||
|
||||
WebView2Ready?.Invoke(true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceAm,
|
||||
$"WebView2Host: Initialization FAILED: {ex}");
|
||||
WebView2Ready?.Invoke(false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private void DestroyWin32()
|
||||
{
|
||||
try
|
||||
{
|
||||
_controller?.Close();
|
||||
_controller = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceAm,
|
||||
$"WebView2Host: Error closing controller: {ex.Message}");
|
||||
}
|
||||
|
||||
if (_hwnd != nint.Zero)
|
||||
{
|
||||
DestroyWindow(_hwnd);
|
||||
_hwnd = nint.Zero;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_className))
|
||||
{
|
||||
UnregisterClass(_className, GetModuleHandle(null));
|
||||
}
|
||||
}
|
||||
|
||||
#region Win32 Interop
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct RECT
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GetClientRect(nint hWnd, out RECT lpRect);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
using AvaloniaWebView;
|
||||
using FluentAvalonia.UI.Windowing;
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
@ -49,6 +50,14 @@ namespace Ryujinx.Ava
|
||||
public static void SetTaskbarProgressValue(ulong current, ulong total) => MainWindow.PlatformFeatures.SetTaskBarProgressBarValue(current, total);
|
||||
public static void SetTaskbarProgressValue(long current, long total) => SetTaskbarProgressValue(Convert.ToUInt64(current), Convert.ToUInt64(total));
|
||||
|
||||
public override void RegisterServices()
|
||||
{
|
||||
base.RegisterServices();
|
||||
|
||||
// Initialize the WebView builder so that embedded WebView controls work.
|
||||
AvaloniaWebViewBuilder.Initialize(default);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
Name = FormatTitle();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user