From b2310823c9f0d25fb0ce2dd6ee41e0e4e8af7c4a Mon Sep 17 00:00:00 2001 From: Babib3l Date: Mon, 25 May 2026 12:09:00 +0000 Subject: [PATCH] Windows Fullscreen Fixes (#87) This PR fixes two bugs introduced in #80 : - Exiting fullscreen could exit the Ryujinx window (https://github.com/Ryubing/Issues/issues/415) - Toggling fullscreen on would move the window to the primary monitor And two other bugs related to input view : - Exiting fullscreen when window was previously in a non maximized state now conserves the window coordinates and size properly (https://github.com/Ryubing/Issues/issues/425) - Opening the ryujinx app no makes the app grow slightly larger vertically on each launch (+31px on my 1080p monitor) (https://github.com/Ryubing/Issues/issues/425) Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/87 --- src/Ryujinx/UI/Helpers/Win32NativeInterop.cs | 16 +++++ .../UI/ViewModels/MainWindowViewModel.cs | 65 ++++++++++++++++--- src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 9 ++- 3 files changed, 79 insertions(+), 11 deletions(-) diff --git a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs index adfa899ed..65fa74297 100644 --- a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs +++ b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs @@ -63,6 +63,18 @@ namespace Ryujinx.Ava.UI.Helpers } } + [StructLayout(LayoutKind.Sequential)] + public struct NativeRect + { + public int Left; + public int Top; + public int Right; + public int Bottom; + + public int Width => Right - Left; + public int Height => Bottom - Top; + } + public static nint CreateEmptyCursor() { return CreateCursor(nint.Zero, 0, 0, 1, 1, [0xFF], [0x00]); @@ -119,6 +131,10 @@ namespace Ryujinx.Ava.UI.Helpers [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 GetWindowRect(nint hWnd, out NativeRect lpRect); + [LibraryImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static partial bool SetWindowPos( diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 0531fab9c..88ca1de17 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -2060,6 +2060,12 @@ namespace Ryujinx.Ava.UI.ViewModels } private nint _savedWindowStyle; + private WindowState _savedWindowState; + private PixelPoint _savedWindowPosition; + private double _savedWindowWidth; + private double _savedWindowHeight; + private Win32NativeInterop.NativeRect _savedWindowRect; + private bool _savedWindowRectValid; [SupportedOSPlatform("windows")] private void MakeWindowFullscreen() @@ -2067,19 +2073,36 @@ namespace Ryujinx.Ava.UI.ViewModels nint hwnd = Window.TryGetPlatformHandle()?.Handle ?? nint.Zero; if (hwnd == nint.Zero) return; + PixelPoint windowCenter = new( + Window.Position.X + (int)(Window.Bounds.Width / 2), + Window.Position.Y + (int)(Window.Bounds.Height / 2)); + + Avalonia.Platform.Screen? screen = + Window.Screens.ScreenFromVisual(Window) ?? + Window.Screens.ScreenFromPoint(windowCenter) ?? + Window.Screens.Primary; + + if (screen == null) + { + return; // Can't determine screen size, don't attempt fullscreen + } + // Save current style and placement _savedWindowStyle = Win32NativeInterop.GetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE); + _savedWindowState = WindowState; + _savedWindowPosition = Window.Position; + _savedWindowWidth = Window.Width; + _savedWindowHeight = Window.Height; + _savedWindowRectValid = Win32NativeInterop.GetWindowRect(hwnd, out _savedWindowRect); // Remove window chrome: WS_OVERLAPPEDWINDOW -> WS_POPUP | WS_VISIBLE Win32NativeInterop.SetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE, unchecked((nint)(Win32NativeInterop.WS_POPUP | Win32NativeInterop.WS_VISIBLE))); - - // TODO: why is this nullable - 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, + int w = screen.Bounds.Width; + int h = screen.Bounds.Height; + + Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, screen.Bounds.X, screen.Bounds.Y, w, h, Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE | Win32NativeInterop.SWP_FRAMECHANGED); WindowState = WindowState.FullScreen; @@ -2094,10 +2117,34 @@ namespace Ryujinx.Ava.UI.ViewModels // 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); + if (_savedWindowState is WindowState.Maximized) + { + Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, 0, 0, 0, 0, + Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE | + Win32NativeInterop.SWP_FRAMECHANGED | Win32NativeInterop.SWP_NOMOVE | Win32NativeInterop.SWP_NOSIZE); + } + else if (_savedWindowRectValid) + { + Dispatcher.UIThread.Post(() => RestoreSavedWindowRect(hwnd), DispatcherPriority.Background); + } + else + { + Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, 0, 0, 0, 0, + Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE | + Win32NativeInterop.SWP_FRAMECHANGED | Win32NativeInterop.SWP_NOMOVE | Win32NativeInterop.SWP_NOSIZE); + } + } + [SupportedOSPlatform("windows")] + private void RestoreSavedWindowRect(nint hwnd) + { + Window.Position = _savedWindowPosition; + Window.Width = _savedWindowWidth; + Window.Height = _savedWindowHeight; + + Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, _savedWindowRect.Left, _savedWindowRect.Top, + _savedWindowRect.Width, _savedWindowRect.Height, + Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE | Win32NativeInterop.SWP_FRAMECHANGED); } public static void SaveConfig() diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index e7934f38a..1f20604f6 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -48,6 +48,8 @@ namespace Ryujinx.Ava.UI.Windows private bool _isLoading; private bool _applicationsLoadedOnce; + private double _windowStartupWidthDelta; + private double _windowStartupHeightDelta; private UserChannelPersistence _userChannelPersistence; private static bool _deferLoad; @@ -477,8 +479,8 @@ namespace Ryujinx.Ava.UI.Windows { // Since scaling is being applied to the loaded settings from disk (see SetWindowSizePosition() above), scaling should be removed from width/height before saving out to disk // as well - otherwise anyone not using a 1.0 scale factor their window will increase in size with every subsequent launch of the program when scaling is applied (Nov. 14, 2024) - ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)(Height / Program.WindowScaleFactor); - ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)(Width / Program.WindowScaleFactor); + ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)((Height - _windowStartupHeightDelta) / Program.WindowScaleFactor); + ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)((Width - _windowStartupWidthDelta) / Program.WindowScaleFactor); ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X; ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y; @@ -493,6 +495,9 @@ namespace Ryujinx.Ava.UI.Windows Initialize(); + _windowStartupWidthDelta = Math.Max(0, Width - ViewModel.WindowWidth); + _windowStartupHeightDelta = Math.Max(0, Height - ViewModel.WindowHeight); + PlatformSettings!.ColorValuesChanged += OnPlatformColorValuesChanged; ViewModel.Initialize(