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
This commit is contained in:
Babib3l
2026-05-25 12:09:00 +00:00
committed by sh0inx
parent b62c58c2fe
commit 609b956edd
3 changed files with 79 additions and 11 deletions

View File

@@ -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() public static nint CreateEmptyCursor()
{ {
return CreateCursor(nint.Zero, 0, 0, 1, 1, [0xFF], [0x00]); return CreateCursor(nint.Zero, 0, 0, 1, 1, [0xFF], [0x00]);
@@ -119,6 +131,10 @@ namespace Ryujinx.Ava.UI.Helpers
[LibraryImport("user32.dll", SetLastError = true)] [LibraryImport("user32.dll", SetLastError = true)]
public static partial nint SetWindowLongPtrW(nint hWnd, int nIndex, nint value); 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)] [LibraryImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
public static partial bool SetWindowPos( public static partial bool SetWindowPos(

View File

@@ -2060,6 +2060,12 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
private nint _savedWindowStyle; private nint _savedWindowStyle;
private WindowState _savedWindowState;
private PixelPoint _savedWindowPosition;
private double _savedWindowWidth;
private double _savedWindowHeight;
private Win32NativeInterop.NativeRect _savedWindowRect;
private bool _savedWindowRectValid;
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
private void MakeWindowFullscreen() private void MakeWindowFullscreen()
@@ -2067,19 +2073,36 @@ namespace Ryujinx.Ava.UI.ViewModels
nint hwnd = Window.TryGetPlatformHandle()?.Handle ?? nint.Zero; nint hwnd = Window.TryGetPlatformHandle()?.Handle ?? nint.Zero;
if (hwnd == nint.Zero) return; 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 // Save current style and placement
_savedWindowStyle = Win32NativeInterop.GetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE); _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 // Remove window chrome: WS_OVERLAPPEDWINDOW -> WS_POPUP | WS_VISIBLE
Win32NativeInterop.SetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE, Win32NativeInterop.SetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE,
unchecked((nint)(Win32NativeInterop.WS_POPUP | Win32NativeInterop.WS_VISIBLE))); 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); Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE | Win32NativeInterop.SWP_FRAMECHANGED);
WindowState = WindowState.FullScreen; WindowState = WindowState.FullScreen;
@@ -2094,10 +2117,34 @@ namespace Ryujinx.Ava.UI.ViewModels
// Restore original window style // Restore original window style
Win32NativeInterop.SetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE, _savedWindowStyle); Win32NativeInterop.SetWindowLongPtrW(hwnd, Win32NativeInterop.GWL_STYLE, _savedWindowStyle);
Win32NativeInterop.SetWindowPos(hwnd, nint.Zero, 0, 0, 0, 0, if (_savedWindowState is WindowState.Maximized)
Win32NativeInterop.SWP_NOZORDER | Win32NativeInterop.SWP_NOACTIVATE | {
Win32NativeInterop.SWP_FRAMECHANGED | Win32NativeInterop.SWP_NOMOVE | Win32NativeInterop.SWP_NOSIZE); 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() public static void SaveConfig()

View File

@@ -48,6 +48,8 @@ namespace Ryujinx.Ava.UI.Windows
private bool _isLoading; private bool _isLoading;
private bool _applicationsLoadedOnce; private bool _applicationsLoadedOnce;
private double _windowStartupWidthDelta;
private double _windowStartupHeightDelta;
private UserChannelPersistence _userChannelPersistence; private UserChannelPersistence _userChannelPersistence;
private static bool _deferLoad; 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 // 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) // 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.WindowSizeHeight.Value = (int)((Height - _windowStartupHeightDelta) / Program.WindowScaleFactor);
ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)(Width / 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.WindowPositionX.Value = Position.X;
ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y; ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y;
@@ -493,6 +495,9 @@ namespace Ryujinx.Ava.UI.Windows
Initialize(); Initialize();
_windowStartupWidthDelta = Math.Max(0, Width - ViewModel.WindowWidth);
_windowStartupHeightDelta = Math.Max(0, Height - ViewModel.WindowHeight);
PlatformSettings!.ColorValuesChanged += OnPlatformColorValuesChanged; PlatformSettings!.ColorValuesChanged += OnPlatformColorValuesChanged;
ViewModel.Initialize( ViewModel.Initialize(