From 8071c8c8c044ee56bc7578a4ba3178d2d03733db Mon Sep 17 00:00:00 2001 From: Ac_K Date: Sun, 15 Jan 2023 01:05:44 +0100 Subject: [PATCH] Ava UI: Fixes "Hide Cursor on Idle" for Windows (#4266) * Ava: Fixes "Hide Cursor on Idle" for Windows * Add check in MouseDriver and reduce the time of idling * Fix linux error * Change idle time everywhere for consistencies --- Ryujinx.Ava/AppHost.cs | 128 +++++++++++-------- Ryujinx.Ava/Input/AvaloniaMouseDriver.cs | 32 ++++- Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs | 7 +- Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs | 16 +++ Ryujinx.Headless.SDL2/SDL2MouseDriver.cs | 2 +- Ryujinx/Ui/RendererWidgetBase.cs | 2 +- 6 files changed, 125 insertions(+), 62 deletions(-) diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs index 65f84c494..5c4f5bd8b 100644 --- a/Ryujinx.Ava/AppHost.cs +++ b/Ryujinx.Ava/AppHost.cs @@ -44,8 +44,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; +using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop; using Image = SixLabors.ImageSharp.Image; using InputManager = Ryujinx.Input.HLE.InputManager; using Key = Ryujinx.Input.Key; @@ -58,12 +60,14 @@ namespace Ryujinx.Ava { internal class AppHost { - private const int CursorHideIdleTime = 8; // Hide Cursor seconds. + private const int CursorHideIdleTime = 5; // Hide Cursor seconds. private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping. private const int TargetFps = 60; private const float VolumeDelta = 0.05f; private static readonly Cursor InvisibleCursor = new(StandardCursorType.None); + private readonly IntPtr InvisibleCursorWin; + private readonly IntPtr DefaultCursorWin; private readonly long _ticksPerFrame; private readonly Stopwatch _chrono; @@ -81,7 +85,6 @@ namespace Ryujinx.Ava private float _newVolume; private KeyboardHotkeyState _prevHotkeyState; - private bool _hideCursorOnIdle; private long _lastCursorMoveTime; private bool _isCursorInRenderer; @@ -131,7 +134,6 @@ namespace Ryujinx.Ava _accountManager = accountManager; _userChannelPersistence = userChannelPersistence; _renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" }; - _hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle; _lastCursorMoveTime = Stopwatch.GetTimestamp(); _glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel; _topLevel = topLevel; @@ -159,9 +161,14 @@ namespace Ryujinx.Ava ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed; - _topLevel.PointerLeave += TopLevel_PointerLeave; _topLevel.PointerMoved += TopLevel_PointerMoved; + if (OperatingSystem.IsWindows()) + { + InvisibleCursorWin = CreateEmptyCursor(); + DefaultCursorWin = CreateArrowCursor(); + } + ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState; ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; @@ -172,20 +179,47 @@ namespace Ryujinx.Ava private void TopLevel_PointerMoved(object sender, PointerEventArgs e) { - if (sender is Control visual) + if (sender is MainWindow window) { _lastCursorMoveTime = Stopwatch.GetTimestamp(); - var point = e.GetCurrentPoint(visual).Position; + if ((Renderer.Content as EmbeddedWindow).TransformedBounds != null) + { + var point = e.GetCurrentPoint(window).Position; + var bounds = (Renderer.Content as EmbeddedWindow).TransformedBounds.Value.Clip; - _isCursorInRenderer = Equals(visual.InputHitTest(point), Renderer); + _isCursorInRenderer = point.X >= bounds.X && + point.X <= bounds.Width + bounds.X && + point.Y >= bounds.Y && + point.Y <= bounds.Height + bounds.Y; + } } } - private void TopLevel_PointerLeave(object sender, PointerEventArgs e) + private void ShowCursor() { - _isCursorInRenderer = false; - _viewModel.Cursor = Cursor.Default; + Dispatcher.UIThread.Post(() => + { + _viewModel.Cursor = Cursor.Default; + + if (OperatingSystem.IsWindows()) + { + SetCursor(DefaultCursorWin); + } + }); + } + + private void HideCursor() + { + Dispatcher.UIThread.Post(() => + { + _viewModel.Cursor = InvisibleCursor; + + if (OperatingSystem.IsWindows()) + { + SetCursor(InvisibleCursorWin); + } + }); } private void SetRendererWindowSize(Size size) @@ -380,7 +414,6 @@ namespace Ryujinx.Ava ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState; ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState; - _topLevel.PointerLeave -= TopLevel_PointerLeave; _topLevel.PointerMoved -= TopLevel_PointerMoved; _gpuCancellationTokenSource.Cancel(); @@ -406,19 +439,10 @@ namespace Ryujinx.Ava private void HideCursorState_Changed(object sender, ReactiveEventArgs state) { - Dispatcher.UIThread.InvokeAsync(delegate + if (state.NewValue) { - _hideCursorOnIdle = state.NewValue; - - if (_hideCursorOnIdle) - { - _lastCursorMoveTime = Stopwatch.GetTimestamp(); - } - else - { - _viewModel.Cursor = Cursor.Default; - } - }); + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + } } public async Task LoadGuestApplication() @@ -860,29 +884,6 @@ namespace Ryujinx.Ava } } - private void HandleScreenState() - { - if (ConfigurationState.Instance.Hid.EnableMouse) - { - Dispatcher.UIThread.Post(() => - { - _viewModel.Cursor = _isCursorInRenderer ? InvisibleCursor : Cursor.Default; - }); - } - else - { - if (_hideCursorOnIdle) - { - long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime; - - Dispatcher.UIThread.Post(() => - { - _viewModel.Cursor = cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency ? InvisibleCursor : Cursor.Default; - }); - } - } - } - private bool UpdateFrame() { if (!_isActive) @@ -890,23 +891,44 @@ namespace Ryujinx.Ava return false; } + NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + if (_viewModel.IsActive) { + if (ConfigurationState.Instance.Hid.EnableMouse) + { + if (_isCursorInRenderer) + { + HideCursor(); + } + else + { + ShowCursor(); + } + } + else + { + if (ConfigurationState.Instance.HideCursorOnIdle) + { + if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency) + { + HideCursor(); + } + else + { + ShowCursor(); + } + } + } + Dispatcher.UIThread.Post(() => { - HandleScreenState(); - if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen) { Device.Application.DiskCacheLoadState?.Cancel(); } }); - } - NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); - - if (_viewModel.IsActive) - { KeyboardHotkeyState currentHotkeyState = GetHotkeyState(); if (currentHotkeyState != _prevHotkeyState) diff --git a/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs b/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs index b0b6cdf05..b3e1a21a1 100644 --- a/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs +++ b/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Input; +using FluentAvalonia.Core; using Ryujinx.Input; using System; using System.Numerics; @@ -69,12 +70,22 @@ namespace Ryujinx.Ava.Input private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args) { - PressedButtons[(int)args.InitialPressMouseButton - 1] = false; + int button = (int)args.InitialPressMouseButton - 1; + + if (PressedButtons.Count() >= button) + { + PressedButtons[button] = false; + } } private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args) { - PressedButtons[(int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind] = true; + int button = (int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind; + + if (PressedButtons.Count() >= button) + { + PressedButtons[button] = true; + } } private void Parent_PointerMovedEvent(object o, PointerEventArgs args) @@ -86,12 +97,18 @@ namespace Ryujinx.Ava.Input public void SetMousePressed(MouseButton button) { - PressedButtons[(int)button] = true; + if (PressedButtons.Count() >= (int)button) + { + PressedButtons[(int)button] = true; + } } public void SetMouseReleased(MouseButton button) { - PressedButtons[(int)button] = false; + if (PressedButtons.Count() >= (int)button) + { + PressedButtons[(int)button] = false; + } } public void SetPosition(double x, double y) @@ -101,7 +118,12 @@ namespace Ryujinx.Ava.Input public bool IsButtonPressed(MouseButton button) { - return PressedButtons[(int)button]; + if (PressedButtons.Count() >= (int)button) + { + return PressedButtons[(int)button]; + } + + return false; } public Size GetClientSize() diff --git a/Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs b/Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs index 8247a89b5..67ab80aa7 100644 --- a/Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs +++ b/Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs @@ -34,6 +34,8 @@ namespace Ryujinx.Ava.UI.Helpers { WindowHandle = IntPtr.Zero; X11Display = IntPtr.Zero; + NsView = IntPtr.Zero; + MetalLayer = IntPtr.Zero; } public EmbeddedWindow() @@ -42,7 +44,7 @@ namespace Ryujinx.Ava.UI.Helpers stateObserverable.Subscribe(StateChanged); - this.Initialized += NativeEmbeddedWindow_Initialized; + Initialized += NativeEmbeddedWindow_Initialized; } public virtual void OnWindowCreated() { } @@ -127,7 +129,7 @@ namespace Ryujinx.Ava.UI.Helpers lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate), style = ClassStyles.CS_OWNDC, lpszClassName = Marshal.StringToHGlobalUni(_className), - hCursor = LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW) + hCursor = CreateArrowCursor() }; var atom = RegisterClassEx(ref wndClassEx); @@ -198,6 +200,7 @@ namespace Ryujinx.Ava.UI.Helpers KeyModifiers.None)); break; } + return DefWindowProc(hWnd, msg, wParam, lParam); } diff --git a/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs b/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs index 1e6e3c3bd..03d3a49f3 100644 --- a/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs +++ b/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs @@ -70,6 +70,22 @@ namespace Ryujinx.Ava.UI.Helpers } } + public static IntPtr CreateEmptyCursor() + { + return CreateCursor(IntPtr.Zero, 0, 0, 1, 1, new byte[] { 0xFF }, new byte[] { 0x00 }); + } + + public static IntPtr CreateArrowCursor() + { + return LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW); + } + + [LibraryImport("user32.dll")] + public static partial IntPtr SetCursor(IntPtr handle); + + [LibraryImport("user32.dll")] + public static partial IntPtr CreateCursor(IntPtr hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, byte[] pvANDPlane, byte[] pvXORPlane); + [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")] public static partial ushort RegisterClassEx(ref WNDCLASSEX param); diff --git a/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs b/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs index bdf428cc4..8c3412ff9 100644 --- a/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs +++ b/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs @@ -10,7 +10,7 @@ namespace Ryujinx.Headless.SDL2 { class SDL2MouseDriver : IGamepadDriver { - private const int CursorHideIdleTime = 8; // seconds + private const int CursorHideIdleTime = 5; // seconds private bool _isDisposed; private HideCursor _hideCursor; diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs index 8db023bec..4bf2a70ff 100644 --- a/Ryujinx/Ui/RendererWidgetBase.cs +++ b/Ryujinx/Ui/RendererWidgetBase.cs @@ -68,7 +68,7 @@ namespace Ryujinx.Ui private readonly CancellationTokenSource _gpuCancellationTokenSource; // Hide Cursor - const int CursorHideIdleTime = 8; // seconds + const int CursorHideIdleTime = 5; // seconds private static readonly Cursor _invisibleCursor = new Cursor(Display.Default, CursorType.BlankCursor); private long _lastCursorMoveTime; private bool _hideCursorOnIdle;