diff --git a/src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs b/src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs index a89dcd1ad..0bafc8286 100644 --- a/src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs +++ b/src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs @@ -11,25 +11,10 @@ namespace Ryujinx.Common.SystemInterop [return: MarshalAs(UnmanagedType.Bool)] private static partial bool SetProcessDPIAware(); - private const string X11LibraryName = "libX11.so.6"; - - [LibraryImport(X11LibraryName)] - private static partial nint XOpenDisplay([MarshalAs(UnmanagedType.LPStr)] string display); - - [LibraryImport(X11LibraryName)] - private static partial nint XGetDefault(nint display, [MarshalAs(UnmanagedType.LPStr)] string program, [MarshalAs(UnmanagedType.LPStr)] string option); - - [LibraryImport(X11LibraryName)] - private static partial int XDisplayWidth(nint display, int screenNumber); - - [LibraryImport(X11LibraryName)] - private static partial int XDisplayWidthMM(nint display, int screenNumber); - - [LibraryImport(X11LibraryName)] - private static partial int XCloseDisplay(nint display); - private const double StandardDpiScale = 96.0; - private const double MaxScaleFactor = 1.25; + private const double MaxScaleFactor = 3.0; + + private static X11Helper.XSettingsListener xSettingsHelper = null; /// /// Marks the application as DPI-Aware when running on the Windows operating system. @@ -43,7 +28,7 @@ namespace Ryujinx.Common.SystemInterop } } - public static double GetActualScaleFactor() + public static void ConfigureDPIScaling(WindowingSystemType windowingSystem) { double userDpiScale = 96.0; @@ -55,27 +40,50 @@ namespace Ryujinx.Common.SystemInterop } else if (OperatingSystem.IsLinux()) { - string xdgSessionType = Environment.GetEnvironmentVariable("XDG_SESSION_TYPE")?.ToLower(); - - if (xdgSessionType is null or "x11") + if (windowingSystem == WindowingSystemType.X11) { - nint display = XOpenDisplay(null); - string dpiString = Marshal.PtrToStringAnsi(XGetDefault(display, "Xft", "dpi")); - if (dpiString == null || !double.TryParse(dpiString, NumberStyles.Any, CultureInfo.InvariantCulture, out userDpiScale)) + var avaScaleFactor = Environment.GetEnvironmentVariable("AVALONIA_GLOBAL_SCALE_FACTOR"); + if (avaScaleFactor is string avaScaleStr && + double.TryParse(avaScaleStr, NumberStyles.Any, CultureInfo.InvariantCulture, out double avaScale) && + avaScale > 0) { - userDpiScale = XDisplayWidth(display, 0) * 25.4 / XDisplayWidthMM(display, 0); + // userDpiScale = avaScale * 96.0; // TODO: avalonia uses logical size? + return userDpiScale; } - _ = XCloseDisplay(display); - } - else if (xdgSessionType == "wayland") - { - // TODO - Logger.Warning?.Print(LogClass.Application, "Couldn't determine monitor DPI: Wayland not yet supported"); - } - else - { - Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Unrecognised XDG_SESSION_TYPE: {xdgSessionType}"); + if (xSettingsHelper == null) + { + var display = X11Helper.XDisplay.Open(null); + xSettingsHelper = new X11Helper.XSettingsListener(display); + } + + xSettingsHelper.CurrentSettings.TryGetValue("Gdk/UnscaledDPI", out var gdkUnscaledDPI); + xSettingsHelper.CurrentSettings.TryGetValue("Gdk/WindowScalingFactor", out var gdkWindowScalingFactor); + xSettingsHelper.CurrentSettings.TryGetValue("Xft/DPI", out var xftDpiSetting); + + double scaleFactor = 1.0; + + if (gdkUnscaledDPI?.Type == X11Helper.XSettingType.Integer && gdkWindowScalingFactor?.Type == X11Helper.XSettingType.Integer) + { + var unscaledDPI = (int)gdkUnscaledDPI.Value / (96d * 1024); + var windowScalingFactor = (double)(int)gdkWindowScalingFactor.Value; + + scaleFactor = unscaledDPI * windowScalingFactor; + } + else + { + var display = xSettingsHelper.Display; + string dpiString = Marshal.PtrToStringAnsi(display.GetDefault("Xft", "dpi")); + if (dpiString == null || !double.TryParse(dpiString, NumberStyles.Any, CultureInfo.InvariantCulture, out userDpiScale)) + { + userDpiScale = display.GetWidth(0) * 25.4 / display.GetWidthMM(0); + } + } + + scaleFactor = Math.Max(scaleFactor, 1.0); + // userDpiScale = 96.0 * scaleFactor; // TODO: avalonia uses logical size? + + Environment.SetEnvironmentVariable("AVALONIA_GLOBAL_SCALE_FACTOR", scaleFactor.ToString(CultureInfo.InvariantCulture)); } } } @@ -83,15 +91,6 @@ namespace Ryujinx.Common.SystemInterop { Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}"); } - - return userDpiScale; - } - - public static double GetWindowScaleFactor() - { - double userDpiScale = GetActualScaleFactor(); - - return Math.Min(userDpiScale / StandardDpiScale, MaxScaleFactor); } } } diff --git a/src/Ryujinx.Common/SystemInterop/WindowingSystemType.cs b/src/Ryujinx.Common/SystemInterop/WindowingSystemType.cs new file mode 100644 index 000000000..0407809b1 --- /dev/null +++ b/src/Ryujinx.Common/SystemInterop/WindowingSystemType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Common.SystemInterop +{ + public enum WindowingSystemType + { + Unknown, + Win32, + X11, + Cocoa, + } +} diff --git a/src/Ryujinx.Common/SystemInterop/X11Helper.cs b/src/Ryujinx.Common/SystemInterop/X11Helper.cs new file mode 100644 index 000000000..59379eacb --- /dev/null +++ b/src/Ryujinx.Common/SystemInterop/X11Helper.cs @@ -0,0 +1,533 @@ +using Ryujinx.Common.Memory; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; + +namespace Ryujinx.Common.SystemInterop +{ + [SupportedOSPlatform("linux")] + public static partial class X11Helper + { + private const string X11LibraryName = "libX11.so.6"; + + [LibraryImport(X11LibraryName)] + private static partial nint XOpenDisplay([MarshalAs(UnmanagedType.LPStr)] string display); + + [LibraryImport(X11LibraryName)] + private static partial nint XGetDefault(nint display, [MarshalAs(UnmanagedType.LPStr)] string program, [MarshalAs(UnmanagedType.LPStr)] string option); + + [LibraryImport(X11LibraryName)] + private static partial int XDisplayWidth(nint display, int screenNumber); + + [LibraryImport(X11LibraryName)] + private static partial int XDisplayWidthMM(nint display, int screenNumber); + + [LibraryImport(X11LibraryName)] + private static partial int XCloseDisplay(nint display); + + [LibraryImport(X11LibraryName)] + private static partial nint XInternAtom(nint display, [MarshalAs(UnmanagedType.LPStr)] string atom_name, [MarshalAs(UnmanagedType.U4)] bool only_if_exists); + + [LibraryImport(X11LibraryName)] + private static partial nint XGetSelectionOwner(nint display, nint selection); + + [LibraryImport(X11LibraryName)] + private static partial int XGetWindowProperty(nint display, nint window, nint atom, nint long_offset, + nint long_length, [MarshalAs(UnmanagedType.U4)] bool delete, nint req_type, out nint actual_type, out int actual_format, + out nint nitems, out nint bytes_after, out nint prop); + + [LibraryImport(X11LibraryName)] + private static partial int XFree(nint data); + + [LibraryImport(X11LibraryName)] + private static partial int XSelectInput(nint display, nint window, nint event_mask); + + [LibraryImport(X11LibraryName)] + private static partial int XSync(nint display, [MarshalAs(UnmanagedType.U4)] bool discard); + + [LibraryImport(X11LibraryName)] + private static partial int XPending(nint display); + + [LibraryImport(X11LibraryName)] + private static partial int XNextEvent(nint display, out XEvent event_return); + + [LibraryImport(X11LibraryName)] + private static partial nint XSetErrorHandler(nint handler); + + [LibraryImport(X11LibraryName)] + private static partial int XDefaultScreen(nint display); + + // X11 constants + private const int PropertyChangeMask = (1 << 22); + private const int StructureNotifyMask = (1 << 17); + private const int PropertyNotify = 28; + private const int DestroyNotify = 17; + private const int Success = 0; + + [StructLayout(LayoutKind.Sequential)] + private struct XEvent + { + public int type; + public int pad0; + Array23 pads; + } + + [StructLayout(LayoutKind.Sequential)] + private struct XPropertyEvent + { + public int type; + public nuint serial; + public int send_event; + public nint display; + public nint window; + public nint atom; + public nint time; + public int state; + } + + [StructLayout(LayoutKind.Sequential)] + private struct XDestroyWindowEvent + { + public int type; + public nuint serial; + public int send_event; + public nint display; + public nint eventWindow; + public nint window; + } + + public enum XSettingType : byte + { + Integer = 0, + String = 1, + Color = 2 + } + + public enum XSettingsByteOrder : byte + { + LittleEndian = 0, + BigEndian = 1, + } + + public class XSettingValue + { + public XSettingType Type { get; set; } + public object Value { get; set; } + public uint Serial { get; set; } + + public override string ToString() + { + return Type switch + { + XSettingType.Integer => $"XInteger({Value})", + XSettingType.String => $"XString({Value})", + XSettingType.Color => $"XColor{Value}", + _ => "Unknown", + }; + } + } + + public class XSettingsMap + { + private readonly Dictionary _settings = new(); + public uint Serial { get; set; } + + public bool TryGetValue(string name, out XSettingValue value) + { + return _settings.TryGetValue(name, out value); + } + + public void Set(string name, XSettingValue value) + { + _settings[name] = value; + } + + public void Clear() + { + _settings.Clear(); + } + + public IEnumerable> GetAll() + { + return _settings; + } + } + + public class XSettingsListener : IDisposable + { + private readonly XDisplay _display; + private readonly int _screen; + private nint _settingsWindow; + private nint _settingsAtom; + private nint _selectionAtom; + private readonly XSettingsMap _currentSettings = new(); + private readonly List> _listeners = new(); + + public XDisplay Display => _display; + + public XSettingsMap CurrentSettings => _currentSettings; + + public XSettingsListener(XDisplay display, int screen = -1) + { + _display = display; + _screen = screen < 0 ? XDefaultScreen(_display.Handle) : screen; + + _selectionAtom = XInternAtom(_display.Handle, $"_XSETTINGS_S{_screen}", false); + _settingsAtom = XInternAtom(_display.Handle, "_XSETTINGS_SETTINGS", false); + + if (_selectionAtom == nint.Zero || _settingsAtom == nint.Zero) + { + throw new Exception("Failed to intern required atoms"); + } + + InitializeSettings(); + } + + public void RegisterListener(Action listener) + { + _listeners.Add(listener); + listener?.Invoke(_currentSettings); + } + + public void UnregisterListener(Action listener) + { + _listeners.Remove(listener); + } + + public void Poll() + { + while (XPending(_display.Handle) > 0) + { + XNextEvent(_display.Handle, out XEvent xevent); + + if (xevent.type == PropertyNotify) + { + unsafe + { + var propEvent = Marshal.PtrToStructure(new IntPtr(&xevent)); + if (propEvent.window == _settingsWindow && propEvent.atom == _settingsAtom) + { + HandleSettingsChange(); + } + } + } + else if (xevent.type == DestroyNotify) + { + unsafe + { + var destroyEvent = Marshal.PtrToStructure(new IntPtr(&xevent)); + if (destroyEvent.window == _settingsWindow) + { + HandleSettingsManagerDestroyed(); + } + } + } + } + } + + private void InitializeSettings() + { + _settingsWindow = XGetSelectionOwner(_display.Handle, _selectionAtom); + + if (_settingsWindow == nint.Zero) + { + _currentSettings.Clear(); + return; + } + + XSelectInput(_display.Handle, _settingsWindow, PropertyChangeMask | StructureNotifyMask); + XSync(_display.Handle, false); + + ReadSettings(); + } + + private void ReadSettings() + { + if (_settingsWindow == nint.Zero) + { + _currentSettings.Clear(); + return; + } + + bool error = false; + nint oldHandler = XSetErrorHandler(Marshal.GetFunctionPointerForDelegate((display, errorEvent) => + { + error = true; + return 0; + })); + + try + { + int result = XGetWindowProperty( + _display.Handle, + _settingsWindow, + _settingsAtom, + 0, + 0x7FFFFFFF, + false, + _settingsAtom, + out nint actualType, + out int actualFormat, + out nint nitems, + out nint bytesAfter, + out nint prop); + + XSync(_display.Handle, false); + + if (error || result != Success || actualType != _settingsAtom || actualFormat != 8 || prop == nint.Zero) + { + if (prop != nint.Zero) + { + XFree(prop); + } + + _currentSettings.Clear(); + _settingsWindow = nint.Zero; + return; + } + + try + { + ParseSettings(prop, (int)nitems); + } + finally + { + XFree(prop); + } + } + finally + { + XSetErrorHandler(oldHandler); + } + } + + private void ParseSettings(nint data, int length) + { + if (length < 12) + return; + + byte[] bytes = new byte[length]; + Marshal.Copy(data, bytes, 0, length); + + int offset = 0; + + XSettingsByteOrder byteOrder = (XSettingsByteOrder)bytes[offset++]; + offset += 3; + uint serial = ReadUInt32(bytes, ref offset, byteOrder); + uint numSettings = ReadUInt32(bytes, ref offset, byteOrder); + + _currentSettings.Serial = serial; + var newSettings = new Dictionary(); + + for (uint i = 0; i < numSettings && offset < length; i++) + { + try + { + var setting = ReadSetting(bytes, ref offset, byteOrder); + if (setting.HasValue) + { + newSettings[setting.Value.name] = setting.Value.value; + } + } + catch + { + break; + } + } + + _currentSettings.Clear(); + foreach (var kvp in newSettings) + { + _currentSettings.Set(kvp.Key, kvp.Value); + } + } + + private (string name, XSettingValue value)? ReadSetting(byte[] bytes, ref int offset, XSettingsByteOrder byteOrder) + { + if (offset >= bytes.Length) + return null; + + XSettingType type = (XSettingType)bytes[offset++]; + offset++; + + ushort nameLen = ReadUInt16(bytes, ref offset, byteOrder); + if (offset + nameLen > bytes.Length) + return null; + + string name = Encoding.UTF8.GetString(bytes, offset, nameLen); + offset += nameLen; + offset += Pad(nameLen); + + uint lastChangeSerial = ReadUInt32(bytes, ref offset, byteOrder); + + object value = null; + + switch (type) + { + case XSettingType.Integer: + if (offset + 4 > bytes.Length) + return null; + value = ReadInt32(bytes, ref offset, byteOrder); + break; + + case XSettingType.String: + if (offset + 4 > bytes.Length) + return null; + uint strLen = ReadUInt32(bytes, ref offset, byteOrder); + if (offset + strLen > bytes.Length) + return null; + value = Encoding.UTF8.GetString(bytes, offset, (int)strLen); + offset += (int)strLen; + offset += Pad((int)strLen); + break; + + case XSettingType.Color: + if (offset + 8 > bytes.Length) + return null; + ushort red = ReadUInt16(bytes, ref offset, byteOrder); + ushort green = ReadUInt16(bytes, ref offset, byteOrder); + ushort blue = ReadUInt16(bytes, ref offset, byteOrder); + ushort alpha = ReadUInt16(bytes, ref offset, byteOrder); + value = (red, green, blue, alpha); + break; + + default: + return null; + } + + return (name, new XSettingValue + { + Type = type, + Value = value, + Serial = lastChangeSerial + }); + } + + private static int Pad(int n) + { + return ((n + 3) & ~3) - n; + } + + // all of the read methods assume the system is little-endian - rest of the emulator won't likely even work on big-endian systems + + private static uint ReadUInt32(byte[] bytes, ref int offset, XSettingsByteOrder byteOrder) + { + uint value = BitConverter.ToUInt32(bytes, offset); + offset += 4; + if (byteOrder != XSettingsByteOrder.LittleEndian) + { + return ReverseBytes(value); + } + return value; + } + + private static int ReadInt32(byte[] bytes, ref int offset, XSettingsByteOrder byteOrder) + { + return (int)ReadUInt32(bytes, ref offset, byteOrder); + } + + private static ushort ReadUInt16(byte[] bytes, ref int offset, XSettingsByteOrder byteOrder) + { + ushort value = BitConverter.ToUInt16(bytes, offset); + offset += 2; + if (byteOrder != XSettingsByteOrder.LittleEndian) + { + return ReverseBytes(value); + } + return value; + } + + private static uint ReverseBytes(uint value) + { + return ((value & 0x000000FFU) << 24) | + ((value & 0x0000FF00U) << 8) | + ((value & 0x00FF0000U) >> 8) | + ((value & 0xFF000000U) >> 24); + } + + private static ushort ReverseBytes(ushort value) + { + return (ushort)(((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8)); + } + + private void HandleSettingsChange() + { + ReadSettings(); + NotifyListeners(); + } + + private void HandleSettingsManagerDestroyed() + { + _currentSettings.Clear(); + _settingsWindow = nint.Zero; + + InitializeSettings(); + NotifyListeners(); + } + + private void NotifyListeners() + { + foreach (var listener in _listeners) + { + try + { + listener?.Invoke(_currentSettings); + } + catch + { + } + } + } + + public void Dispose() + { + _display.Dispose(); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate int ErrorHandler(nint display, nint errorEvent); + } + + public struct XDisplay : IDisposable + { + public nint Handle; + + public static XDisplay Open(string display = null) + { + nint handle = XOpenDisplay(display); + if (handle == nint.Zero) + { + throw new Exception("Couldn't open X11 display."); + } + + return new XDisplay { Handle = handle }; + } + + public nint GetDefault(string program, string option) + { + return XGetDefault(Handle, program, option); + } + + public int GetWidth(int screenNumber) + { + return XDisplayWidth(Handle, screenNumber); + } + + public int GetWidthMM(int screenNumber) + { + return XDisplayWidthMM(Handle, screenNumber); + } + + public void Dispose() + { + if (Handle != nint.Zero) + { + _ = XCloseDisplay(Handle); + Handle = nint.Zero; + } + } + } + } +} diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index f0e0c2ec3..ef5f6dae5 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -30,7 +30,6 @@ namespace Ryujinx.Ava { internal partial class Program { - public static double WindowScaleFactor { get; set; } public static double DesktopScaleFactor { get; set; } = 1.0; public static string Version { get; private set; } public static string ConfigurationPath { get; private set; } @@ -63,7 +62,7 @@ namespace Ryujinx.Ava return 0; } - Initialize(args); + Initialize(args, out var builder); LoggerAdapter.Register(); @@ -71,10 +70,10 @@ namespace Ryujinx.Ava .Register() .Register(); - return BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); + return builder.StartWithClassicDesktopLifetime(args); } - public static AppBuilder BuildAvaloniaApp() => + private static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() .With(new X11PlatformOptions @@ -94,8 +93,10 @@ namespace Ryujinx.Ava : [Win32RenderingMode.Software] }); - private static void Initialize(string[] args) + private static void Initialize(string[] args, out AppBuilder builder) { + builder = BuildAvaloniaApp(); + // Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched DiscordIntegrationModule.EmulatorStartedAt = Timestamps.Now; @@ -136,7 +137,7 @@ namespace Ryujinx.Ava ReloadConfig(); - WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor(); + ForceDpiAware.ConfigureDPIScaling(builder.GetWindowingSystemType()); // Logging system information. PrintSystemInfo(); diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs index f6bf43795..b93245d21 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -232,11 +232,11 @@ namespace Ryujinx.Ava.UI.Views.Main ); // Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024) - double barsHeight = ((Window.StatusBarHeight + Window.MenuBarHeight) + - (ConfigurationState.Instance.ShowOldUI ? (int)Window.TitleBar.Height : 0)); + double barsHeight = Window.StatusBarHeight + Window.MenuBarHeight + + (ConfigurationState.Instance.ShowOldUI ? (int)Window.TitleBar.Height : 0); - double windowWidthScaled = (resolutionWidth * Program.WindowScaleFactor); - double windowHeightScaled = ((resolutionHeight + barsHeight) * Program.WindowScaleFactor); + double windowWidthScaled = resolutionWidth; + double windowHeightScaled = resolutionHeight + barsHeight; Dispatcher.UIThread.Post(() => { diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index e363ee2cd..351f1fcc2 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -440,8 +440,8 @@ namespace Ryujinx.Ava.UI.Windows if (!ConfigurationState.Instance.RememberWindowState) { // Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024) - ViewModel.WindowHeight = (720 + StatusBarHeight + MenuBarHeight + TitleBarHeight) * Program.WindowScaleFactor; - ViewModel.WindowWidth = 1280 * Program.WindowScaleFactor; + ViewModel.WindowHeight = 720 + StatusBarHeight + MenuBarHeight + TitleBarHeight; + ViewModel.WindowWidth = 1280; WindowState = WindowState.Normal; WindowStartupLocation = WindowStartupLocation.CenterScreen; @@ -452,8 +452,8 @@ namespace Ryujinx.Ava.UI.Windows PixelPoint savedPoint = new(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX, ConfigurationState.Instance.UI.WindowStartup.WindowPositionY); - ViewModel.WindowHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor; - ViewModel.WindowWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor; + ViewModel.WindowHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight; + ViewModel.WindowWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth; ViewModel.WindowState = ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal; @@ -475,10 +475,8 @@ namespace Ryujinx.Ava.UI.Windows // Only save rectangle properties if the window is not in a maximized state. if (WindowState != WindowState.Maximized) { - // 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; + ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)Width; ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X; ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y; diff --git a/src/Ryujinx/Utilities/WindowingSystemExtensions.cs b/src/Ryujinx/Utilities/WindowingSystemExtensions.cs new file mode 100644 index 000000000..7a4db6ff6 --- /dev/null +++ b/src/Ryujinx/Utilities/WindowingSystemExtensions.cs @@ -0,0 +1,43 @@ +using Avalonia; +using Ryujinx.Common.SystemInterop; +using System; + +namespace Ryujinx.Ava.Utilities +{ + public static class WindowingSystemTypeExtensions + { + public static WindowingSystemType GetWindowingSystemType(this AppBuilder builder) + { + // null means uninitialized, "" means the name isn't set. + if (builder?.WindowingSubsystemName is null) + { + throw new InvalidOperationException("The windowing subsystem must be configured before calling this method."); + } + + if (builder.WindowingSubsystemName == "") + { + // TODO: They forget to set the WindowingSubsystemName for X11, we assume it's X11 because every other Linux backend sets it. + // https://github.com/AvaloniaUI/Avalonia/blob/22c4c630ce5910343006fd58d611b286ed87c740/src/Avalonia.X11/X11Platform.cs#L414 + if (OperatingSystem.IsLinux()) + { + return WindowingSystemType.X11; + } + + // TODO: Same for Cocoa. + // https://github.com/AvaloniaUI/Avalonia/blob/22c4c630ce5910343006fd58d611b286ed87c740/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs#L26 + if (OperatingSystem.IsMacOS()) + { + return WindowingSystemType.Cocoa; + } + } + + return builder.WindowingSubsystemName switch + { + "Win32" => WindowingSystemType.Win32, + "X11" => WindowingSystemType.X11, + "Cocoa" => WindowingSystemType.Cocoa, + _ => WindowingSystemType.Unknown, + }; + } + } +}