diff --git a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs index 0ff7f5fde..adfa899ed 100644 --- a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs +++ b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs @@ -8,6 +8,12 @@ namespace Ryujinx.Ava.UI.Helpers internal partial class Win32NativeInterop { internal const int GWLP_WNDPROC = -4; + internal const int GWL_STYLE = -16; + internal const int GWL_EXSTYLE = -20; + + internal const uint WS_OVERLAPPEDWINDOW = 0x00CF0000; + internal const uint WS_POPUP = 0x80000000; + internal const uint WS_VISIBLE = 0x10000000; [Flags] public enum ClassStyles : uint @@ -107,9 +113,29 @@ namespace Ryujinx.Ava.UI.Helpers nint hInstance, nint lpParam); + [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLongPtrW")] + public static partial nint GetWindowLongPtrW(nint hWnd, int nIndex); + [LibraryImport("user32.dll", SetLastError = true)] public static partial nint SetWindowLongPtrW(nint hWnd, int nIndex, nint value); + [LibraryImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool SetWindowPos( + nint hWnd, + nint hWndInsertAfter, + int x, + int y, + int cx, + int cy, + uint uFlags); + + internal const uint SWP_NOZORDER = 0x0004; + internal const uint SWP_NOACTIVATE = 0x0010; + internal const uint SWP_FRAMECHANGED = 0x0020; + internal const uint SWP_NOMOVE = 0x0002; + internal const uint SWP_NOSIZE = 0x0001; + [LibraryImport("user32.dll", SetLastError = true)] public static partial ushort GetAsyncKeyState(int nVirtKey); diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 57fd825b3..e488495d6 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -5,6 +5,7 @@ using Avalonia.Input; using Avalonia.Media; using Avalonia.Platform.Storage; using Avalonia.Threading; +using System.Runtime.Versioning; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using DynamicData; @@ -2014,7 +2015,7 @@ namespace Ryujinx.Ava.UI.ViewModels LastFullscreenToggle = Environment.TickCount64; - if (WindowState is not WindowState.Normal) + if (WindowState is WindowState.FullScreen) { WindowState = WindowState.Normal; Window.TitleBar.ExtendsContentIntoTitleBar = !ConfigurationState.Instance.ShowOldUI; @@ -2023,21 +2024,74 @@ namespace Ryujinx.Ava.UI.ViewModels { ShowMenuAndStatusBar = true; } + + if (OperatingSystem.IsWindows()) + { + RestoreWindowFromFullscreen(); + } } else { - WindowState = WindowState.FullScreen; Window.TitleBar.ExtendsContentIntoTitleBar = true; if (IsGameRunning) { ShowMenuAndStatusBar = false; } + + if (OperatingSystem.IsWindows()) + { + MakeWindowFullscreen(); + } + else + { + WindowState = WindowState.FullScreen; + } } IsFullScreen = WindowState is WindowState.FullScreen; } + private nint _savedWindowStyle; + + [SupportedOSPlatform("windows")] + private void MakeWindowFullscreen() + { + nint hwnd = Window.TryGetPlatformHandle()?.Handle ?? nint.Zero; + if (hwnd == nint.Zero) return; + + // Save current style and placement + _savedWindowStyle = Win32NativeInterop.GetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE); + + // Remove window chrome: WS_OVERLAPPEDWINDOW -> WS_POPUP | WS_VISIBLE + Win32NativeInterop.SetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE, + unchecked((nint)(Win32NativeInterop.WS_POPUP | Win32NativeInterop.WS_VISIBLE))); + + Avalonia.Platform.Screen? screen = Window.Screens.ScreenFromVisual(Window); + int w = screen?.Bounds.Width ?? 0; + int h = screen?.Bounds.Height ?? 0; + + Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, 0, 0, w, h, + Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE | Win32NativeInterop.SWP_FRAMECHANGED); + + WindowState = WindowState.FullScreen; + } + + [SupportedOSPlatform("windows")] + private void RestoreWindowFromFullscreen() + { + nint hwnd = Window.TryGetPlatformHandle()?.Handle ?? nint.Zero; + if (hwnd == nint.Zero) return; + + // Restore original window style + Win32NativeInterop.SetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE, _savedWindowStyle); + + Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, 0, 0, 0, 0, + Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE | + Win32NativeInterop.SWP_FRAMECHANGED | Win32NativeInterop.SWP_NOMOVE | Win32NativeInterop.SWP_NOSIZE); + + } + public static void SaveConfig() { ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);