Mesen2/UI/Windows/GetKeyWindow.axaml.cs
Sour bdbcf4e221 Input: Only apply recent KeyUp processing changes to keys that cause the original issue
Some typical scenarios can cause KeyUp only on regular keys, which can be annoying (e.g pressing Esc to exit a configuration dialog ends up toggling pause)
2024-07-14 20:28:29 +09:00

169 lines
5.5 KiB
C#

using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Interactivity;
using System;
using Mesen.Config.Shortcuts;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input;
using System.ComponentModel;
using Mesen.Interop;
using Avalonia.Threading;
using Avalonia;
using Mesen.Config;
using Mesen.Utilities;
using Mesen.Localization;
using Avalonia.Layout;
using System.Diagnostics;
namespace Mesen.Windows
{
public class GetKeyWindow : MesenWindow
{
private DispatcherTimer _timer;
private List<UInt16> _prevScanCodes = new List<UInt16>();
private TextBlock lblCurrentKey;
private bool _allowKeyboardOnly;
private Stopwatch _stopWatch = Stopwatch.StartNew();
private Dictionary<Key, long> _keyPressedStamp = new();
public string HintLabel { get; }
public bool SingleKeyMode { get; set; } = false;
public DbgShortKeys DbgShortcutKey { get; set; } = new DbgShortKeys();
public KeyCombination ShortcutKey { get; set; } = new KeyCombination();
[Obsolete("For designer only")]
public GetKeyWindow() : this(false) { }
public GetKeyWindow(bool allowKeyboardOnly)
{
_allowKeyboardOnly = allowKeyboardOnly;
HintLabel = ResourceHelper.GetMessage(_allowKeyboardOnly ? "SetKeyHint" : "SetKeyMouseHint");
//Required for keyboard input to work properly in Linux/macOS
this.Focusable = true;
InitializeComponent();
lblCurrentKey = this.GetControl<TextBlock>("lblCurrentKey");
_timer = new DispatcherTimer(TimeSpan.FromMilliseconds(25), DispatcherPriority.Normal, (s, e) => UpdateKeyDisplay());
_timer.Start();
//Allow us to catch LeftAlt/RightAlt key presses
this.AddHandler(InputElement.KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel, true);
this.AddHandler(InputElement.KeyUpEvent, OnPreviewKeyUp, RoutingStrategies.Tunnel, true);
}
protected override void OnClosed(EventArgs e)
{
_timer?.Stop();
base.OnClosed(e);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void OnPreviewKeyDown(object? sender, KeyEventArgs e)
{
InputApi.SetKeyState((UInt16)e.Key, true);
DbgShortcutKey = new DbgShortKeys(e.KeyModifiers, e.Key);
_keyPressedStamp[e.Key] = _stopWatch.ElapsedTicks;
e.Handled = true;
}
private void OnPreviewKeyUp(object? sender, KeyEventArgs e)
{
e.Handled = true;
if(e.Key.IsSpecialKey() && !_allowKeyboardOnly && (!_keyPressedStamp.TryGetValue(e.Key, out long stamp) || ((_stopWatch.ElapsedTicks - stamp) * 1000 / Stopwatch.Frequency) < 10)) {
//Key up received without key down, or key pressed for less than 10 ms, pretend the key was pressed for 50ms
//Some special keys can behave this way (e.g printscreen)
DbgShortcutKey = new DbgShortKeys(e.KeyModifiers, e.Key);
InputApi.SetKeyState((UInt16)e.Key, true);
UpdateKeyDisplay();
DispatcherTimer.RunOnce(() => InputApi.SetKeyState((UInt16)e.Key, false), TimeSpan.FromMilliseconds(50), DispatcherPriority.MaxValue);
_keyPressedStamp.Remove(e.Key);
return;
}
_keyPressedStamp.Remove(e.Key);
InputApi.SetKeyState((UInt16)e.Key, false);
if(_allowKeyboardOnly) {
this.Close();
}
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
lblCurrentKey.IsVisible = !this.SingleKeyMode;
lblCurrentKey.Height = this.SingleKeyMode ? 0 : 40;
ShortcutKey = new KeyCombination();
InputApi.UpdateInputDevices();
InputApi.ResetKeyState();
//Prevent other keybindings from interfering/activating
InputApi.DisableAllKeys(true);
}
protected override void OnClosing(WindowClosingEventArgs e)
{
base.OnClosing(e);
InputApi.DisableAllKeys(false);
}
private void SelectKeyCombination(KeyCombination key)
{
if(!string.IsNullOrWhiteSpace(key.ToString())) {
ShortcutKey = key;
this.Close();
}
}
private void UpdateKeyDisplay()
{
if(!_allowKeyboardOnly) {
SystemMouseState mouseState = InputApi.GetSystemMouseState(IntPtr.Zero);
PixelPoint mousePos = new PixelPoint(mouseState.XPosition, mouseState.YPosition);
PixelRect clientBounds = new PixelRect(this.PointToScreen(new Point(0, 0)), PixelSize.FromSize(Bounds.Size, LayoutHelper.GetLayoutScale(this) / InputApi.GetPixelScale()));
bool mouseInsideWindow = clientBounds.Contains(mousePos);
InputApi.SetKeyState(MouseManager.LeftMouseButtonKeyCode, mouseInsideWindow && mouseState.LeftButton);
InputApi.SetKeyState(MouseManager.RightMouseButtonKeyCode, mouseInsideWindow && mouseState.RightButton);
InputApi.SetKeyState(MouseManager.MiddleMouseButtonKeyCode, mouseInsideWindow && mouseState.MiddleButton);
InputApi.SetKeyState(MouseManager.MouseButton4KeyCode, mouseInsideWindow && mouseState.Button4);
InputApi.SetKeyState(MouseManager.MouseButton5KeyCode, mouseInsideWindow && mouseState.Button5);
List<UInt16> scanCodes = InputApi.GetPressedKeys();
if(this.SingleKeyMode) {
if(scanCodes.Count >= 1) {
//Always use the largest scancode (when multiple buttons are pressed at once)
scanCodes = new List<UInt16> { scanCodes.OrderBy(code => -code).First() };
this.SelectKeyCombination(new KeyCombination(scanCodes));
}
} else {
KeyCombination key = new KeyCombination(_prevScanCodes);
this.GetControl<TextBlock>("lblCurrentKey").Text = key.ToString();
if(scanCodes.Count < _prevScanCodes.Count) {
//Confirm key selection when the user releases a key
this.SelectKeyCombination(key);
}
_prevScanCodes = scanCodes;
}
} else {
this.GetControl<TextBlock>("lblCurrentKey").Text = DbgShortcutKey.ToString();
}
}
}
}