mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-05-27 07:29:14 +00:00
Compare commits
2 Commits
Canary-1.3
...
Canary-1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb7c1fde11 | ||
|
|
18226decf1 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -72,6 +72,9 @@ ipch/
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
|
||||
# .NET
|
||||
.dotnet-home/
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
|
||||
1904
assets/Locales/KeyboardLayout.json
Normal file
1904
assets/Locales/KeyboardLayout.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
namespace Ryujinx.Common.Configuration.Hid.Keyboard
|
||||
{
|
||||
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<Key> { }
|
||||
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<PhysicalKey> { }
|
||||
}
|
||||
|
||||
142
src/Ryujinx.Common/Configuration/Hid/PhysicalKey.cs
Normal file
142
src/Ryujinx.Common/Configuration/Hid/PhysicalKey.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonConverter(typeof(JsonStringEnumConverter<PhysicalKey>))]
|
||||
public enum PhysicalKey
|
||||
{
|
||||
Unknown,
|
||||
ShiftLeft,
|
||||
ShiftRight,
|
||||
ControlLeft,
|
||||
ControlRight,
|
||||
AltLeft,
|
||||
AltRight,
|
||||
WinLeft,
|
||||
WinRight,
|
||||
Menu,
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
F13,
|
||||
F14,
|
||||
F15,
|
||||
F16,
|
||||
F17,
|
||||
F18,
|
||||
F19,
|
||||
F20,
|
||||
F21,
|
||||
F22,
|
||||
F23,
|
||||
F24,
|
||||
F25,
|
||||
F26,
|
||||
F27,
|
||||
F28,
|
||||
F29,
|
||||
F30,
|
||||
F31,
|
||||
F32,
|
||||
F33,
|
||||
F34,
|
||||
F35,
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
Enter,
|
||||
Escape,
|
||||
Space,
|
||||
Tab,
|
||||
BackSpace,
|
||||
Insert,
|
||||
Delete,
|
||||
PageUp,
|
||||
PageDown,
|
||||
Home,
|
||||
End,
|
||||
CapsLock,
|
||||
ScrollLock,
|
||||
PrintScreen,
|
||||
Pause,
|
||||
NumLock,
|
||||
Clear,
|
||||
Keypad0,
|
||||
Keypad1,
|
||||
Keypad2,
|
||||
Keypad3,
|
||||
Keypad4,
|
||||
Keypad5,
|
||||
Keypad6,
|
||||
Keypad7,
|
||||
Keypad8,
|
||||
Keypad9,
|
||||
KeypadDivide,
|
||||
KeypadMultiply,
|
||||
KeypadSubtract,
|
||||
KeypadAdd,
|
||||
KeypadDecimal,
|
||||
KeypadEnter,
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
Number0,
|
||||
Number1,
|
||||
Number2,
|
||||
Number3,
|
||||
Number4,
|
||||
Number5,
|
||||
Number6,
|
||||
Number7,
|
||||
Number8,
|
||||
Number9,
|
||||
Tilde,
|
||||
Grave,
|
||||
Minus,
|
||||
Plus,
|
||||
BracketLeft,
|
||||
BracketRight,
|
||||
Semicolon,
|
||||
Quote,
|
||||
Comma,
|
||||
Period,
|
||||
Slash,
|
||||
BackSlash,
|
||||
Unbound,
|
||||
|
||||
Count,
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS
|
||||
public IpcMessage Response { get; }
|
||||
public BinaryReader RequestData { get; }
|
||||
public BinaryWriter ResponseData { get; }
|
||||
public ulong ClientProcessId => Request.HandleDesc.HasPId ? Request.HandleDesc.PId : Process.Pid;
|
||||
public ulong ClientProcessId => Request.HandleDesc is { HasPId: true } ? Request.HandleDesc.PId : Process.Pid;
|
||||
|
||||
public ServiceCtx(
|
||||
Switch device,
|
||||
|
||||
@@ -165,6 +165,9 @@ namespace Ryujinx.Input.SDL3
|
||||
public string Id { get; }
|
||||
public string Name { get; }
|
||||
|
||||
// Expose vendor id for higher-fidelity device detection in UI
|
||||
public ushort VendorId => _gamepadHandle != null ? SDL_GetGamepadVendor(_gamepadHandle) : (ushort)0;
|
||||
|
||||
public bool IsConnected => SDL_GamepadConnected(_gamepadHandle);
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
|
||||
@@ -9,25 +9,15 @@ using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using SDL;
|
||||
using static SDL.SDL3;
|
||||
|
||||
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
|
||||
namespace Ryujinx.Input.SDL3
|
||||
{
|
||||
class SDL3Keyboard : IKeyboard
|
||||
{
|
||||
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, Key From)
|
||||
{
|
||||
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not Key.Unbound;
|
||||
}
|
||||
|
||||
private readonly Lock _userMappingLock = new();
|
||||
|
||||
#pragma warning disable IDE0052 // Remove unread private member
|
||||
private readonly SDL3KeyboardDriver _driver;
|
||||
#pragma warning restore IDE0052
|
||||
private StandardKeyboardInputConfig _configuration;
|
||||
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
|
||||
private readonly List<KeyboardInputMappingHelper.KeyboardButtonMapping> _buttonsUserMapping;
|
||||
|
||||
|
||||
private static readonly SDL_Keycode[] _keysDriverMapping =
|
||||
@@ -172,9 +162,8 @@ namespace Ryujinx.Input.SDL3
|
||||
SDL_Keycode.SDLK_0
|
||||
];
|
||||
|
||||
public SDL3Keyboard(SDL3KeyboardDriver driver, string id, string name)
|
||||
public SDL3Keyboard(string id, string name)
|
||||
{
|
||||
_driver = driver;
|
||||
Id = id;
|
||||
Name = name;
|
||||
_buttonsUserMapping = [];
|
||||
@@ -196,9 +185,9 @@ namespace Ryujinx.Input.SDL3
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe static int ToSDL3Scancode(Key key)
|
||||
private unsafe static int ToSDL3Scancode(ConfigPhysicalKey key)
|
||||
{
|
||||
if (key is >= Key.Unknown and <= Key.Menu)
|
||||
if (key is >= ConfigPhysicalKey.Unknown and <= ConfigPhysicalKey.Menu)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
@@ -206,18 +195,18 @@ namespace Ryujinx.Input.SDL3
|
||||
return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key], null);
|
||||
}
|
||||
|
||||
private static SDL_Keymod GetKeyboardModifierMask(Key key)
|
||||
private static SDL_Keymod GetKeyboardModifierMask(ConfigPhysicalKey key)
|
||||
{
|
||||
return key switch
|
||||
{
|
||||
Key.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT,
|
||||
Key.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT,
|
||||
Key.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL,
|
||||
Key.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL,
|
||||
Key.AltLeft => SDL_Keymod.SDL_KMOD_LALT,
|
||||
Key.AltRight => SDL_Keymod.SDL_KMOD_RALT,
|
||||
Key.WinLeft => SDL_Keymod.SDL_KMOD_LGUI,
|
||||
Key.WinRight => SDL_Keymod.SDL_KMOD_RGUI,
|
||||
ConfigPhysicalKey.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT,
|
||||
ConfigPhysicalKey.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT,
|
||||
ConfigPhysicalKey.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL,
|
||||
ConfigPhysicalKey.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL,
|
||||
ConfigPhysicalKey.AltLeft => SDL_Keymod.SDL_KMOD_LALT,
|
||||
ConfigPhysicalKey.AltRight => SDL_Keymod.SDL_KMOD_RALT,
|
||||
ConfigPhysicalKey.WinLeft => SDL_Keymod.SDL_KMOD_LGUI,
|
||||
ConfigPhysicalKey.WinRight => SDL_Keymod.SDL_KMOD_RGUI,
|
||||
// NOTE: Menu key isn't supported by SDL3.
|
||||
_ => SDL_Keymod.SDL_KMOD_NONE
|
||||
};
|
||||
@@ -233,9 +222,9 @@ namespace Ryujinx.Input.SDL3
|
||||
rawKeyboardState = SDL_GetKeyboardState(null);
|
||||
}
|
||||
|
||||
bool[] keysState = new bool[(int)Key.Count];
|
||||
bool[] keysState = new bool[(int)ConfigPhysicalKey.Count];
|
||||
|
||||
for (Key key = 0; key < Key.Count; key++)
|
||||
for (ConfigPhysicalKey key = 0; key < ConfigPhysicalKey.Count; key++)
|
||||
{
|
||||
int index = ToSDL3Scancode(key);
|
||||
if (index == -1)
|
||||
@@ -265,36 +254,6 @@ namespace Ryujinx.Input.SDL3
|
||||
return value * ConvertRate;
|
||||
}
|
||||
|
||||
private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
|
||||
{
|
||||
short stickX = 0;
|
||||
short stickY = 0;
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickUp))
|
||||
{
|
||||
stickY += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickDown))
|
||||
{
|
||||
stickY -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickRight))
|
||||
{
|
||||
stickX += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickLeft))
|
||||
{
|
||||
stickX -= 1;
|
||||
}
|
||||
|
||||
Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY));
|
||||
|
||||
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
|
||||
}
|
||||
|
||||
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||
{
|
||||
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
|
||||
@@ -307,9 +266,9 @@ namespace Ryujinx.Input.SDL3
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
|
||||
foreach (KeyboardInputMappingHelper.KeyboardButtonMapping entry in _buttonsUserMapping)
|
||||
{
|
||||
if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound)
|
||||
if (!entry.IsValid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -321,8 +280,8 @@ namespace Ryujinx.Input.SDL3
|
||||
}
|
||||
}
|
||||
|
||||
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
||||
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
||||
(short leftStickX, short leftStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
||||
(short rightStickX, short rightStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
||||
|
||||
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
|
||||
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
|
||||
@@ -358,38 +317,15 @@ namespace Ryujinx.Input.SDL3
|
||||
{
|
||||
_configuration = (StandardKeyboardInputConfig)configuration;
|
||||
|
||||
// First clear the buttons mapping
|
||||
_buttonsUserMapping.Clear();
|
||||
|
||||
// Then configure left joycon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
|
||||
|
||||
// Finally configure right joycon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
|
||||
_buttonsUserMapping.AddRange(KeyboardInputMappingHelper.BuildButtonMappings(_configuration));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLed(uint packedRgb)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL3Keyboard");
|
||||
Logger.Debug?.Print(LogClass.UI, "SetLed called on an SDL3Keyboard");
|
||||
}
|
||||
|
||||
public void SetTriggerThreshold(float triggerThreshold)
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Ryujinx.Input.SDL3
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SDL3Keyboard(this, _keyboardIdentifers[0], "All keyboards");
|
||||
return new SDL3Keyboard(_keyboardIdentifers[0], "All keyboards");
|
||||
}
|
||||
|
||||
public IEnumerable<IGamepad> GetGamepads()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
|
||||
namespace Ryujinx.Input.Assigner
|
||||
{
|
||||
/// <summary>
|
||||
@@ -8,22 +10,40 @@ namespace Ryujinx.Input.Assigner
|
||||
private readonly IKeyboard _keyboard;
|
||||
|
||||
private KeyboardStateSnapshot _keyboardState;
|
||||
private Button? _pressedButton;
|
||||
|
||||
public KeyboardKeyAssigner(IKeyboard keyboard)
|
||||
{
|
||||
_keyboard = keyboard;
|
||||
}
|
||||
|
||||
public void Initialize() { }
|
||||
public void Initialize()
|
||||
{
|
||||
_pressedButton = null;
|
||||
}
|
||||
|
||||
public void ReadInput()
|
||||
{
|
||||
_keyboardState = _keyboard.GetKeyboardStateSnapshot();
|
||||
|
||||
if (_pressedButton is null)
|
||||
{
|
||||
Button? buttonFromState = GetPressedButtonFromState();
|
||||
Button? buttonFromBufferedPress = buttonFromState is null ? GetPressedButtonFromBufferedPress() : null;
|
||||
|
||||
_pressedButton = buttonFromState ?? buttonFromBufferedPress;
|
||||
}
|
||||
|
||||
if (_pressedButton is not null)
|
||||
{
|
||||
string source = _pressedButton.HasValue && GetPressedButtonFromState() is not null ? "state" : "buffered-press";
|
||||
Logger.Debug?.Print(LogClass.UI, $"Keyboard assigner registered key={_pressedButton.Value.AsHidType<Key>()}, source={source}, cancelPressed={ShouldCancel()}");
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAnyButtonPressed()
|
||||
{
|
||||
return GetPressedButton() is not null;
|
||||
return _pressedButton is not null;
|
||||
}
|
||||
|
||||
public bool ShouldCancel()
|
||||
@@ -33,18 +53,53 @@ namespace Ryujinx.Input.Assigner
|
||||
|
||||
public Button? GetPressedButton()
|
||||
{
|
||||
Button? keyPressed = null;
|
||||
return !ShouldCancel() ? _pressedButton : null;
|
||||
}
|
||||
|
||||
private Button? GetPressedButtonFromState()
|
||||
{
|
||||
Key aliasedKey = GetAliasedPressedKey();
|
||||
|
||||
if (aliasedKey != Key.Unknown)
|
||||
{
|
||||
return new Button(aliasedKey);
|
||||
}
|
||||
|
||||
for (Key key = Key.Unknown; key < Key.Count; key++)
|
||||
{
|
||||
if (_keyboardState.IsPressed(key))
|
||||
{
|
||||
keyPressed = new(key);
|
||||
break;
|
||||
return new Button(key);
|
||||
}
|
||||
}
|
||||
|
||||
return !ShouldCancel() ? keyPressed : null;
|
||||
return null;
|
||||
}
|
||||
|
||||
private Button? GetPressedButtonFromBufferedPress()
|
||||
{
|
||||
return _keyboard.TryConsumePressedKey(out Key key) ? new Button(key) : null;
|
||||
}
|
||||
|
||||
private Key GetAliasedPressedKey()
|
||||
{
|
||||
// On some layouts (for example AltGr on Windows), Right Alt is reported as Ctrl+Alt.
|
||||
// Prefer AltRight in that case so the binding reflects the physical key used.
|
||||
if (_keyboardState.IsPressed(Key.ControlLeft) && _keyboardState.IsPressed(Key.AltRight))
|
||||
{
|
||||
return Key.AltRight;
|
||||
}
|
||||
|
||||
// On some Copilot keyboards, the key in the right-control position is reported as
|
||||
// ShiftLeft+Win+F23. Prefer ControlRight so the binding reflects that physical key.
|
||||
if (_keyboardState.IsPressed(Key.ShiftLeft) &&
|
||||
_keyboardState.IsPressed(Key.F23) &&
|
||||
(_keyboardState.IsPressed(Key.WinLeft) || _keyboardState.IsPressed(Key.WinRight)))
|
||||
{
|
||||
return Key.ControlRight;
|
||||
}
|
||||
|
||||
return Key.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using System;
|
||||
@@ -233,7 +234,9 @@ namespace Ryujinx.Input.HLE
|
||||
_gamepad?.Dispose();
|
||||
|
||||
Id = config.Id;
|
||||
_gamepad = GamepadDriver.GetGamepad(Id);
|
||||
_gamepad = config is StandardKeyboardInputConfig && GamepadDriver is IKeyboardModeDriver keyboardModeDriver
|
||||
? keyboardModeDriver.GetKeyboard(Id, KeyboardInputMode.Physical)
|
||||
: GamepadDriver.GetGamepad(Id);
|
||||
|
||||
UpdateUserConfiguration(config);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Ryujinx.Input.HLE
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private bool _blockInputUpdates;
|
||||
private int _inputUpdateBlockCount;
|
||||
|
||||
private const int MaxControllers = 9;
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace Ryujinx.Input.HLE
|
||||
private bool _isDisposed;
|
||||
|
||||
private List<InputConfig> _inputConfig;
|
||||
private List<InputConfig> _requestedInputConfig;
|
||||
private bool _enableKeyboard;
|
||||
private bool _enableMouse;
|
||||
private Switch _device;
|
||||
@@ -52,6 +53,7 @@ namespace Ryujinx.Input.HLE
|
||||
_gamepadDriver = gamepadDriver;
|
||||
_mouseDriver = mouseDriver;
|
||||
_inputConfig = [];
|
||||
_requestedInputConfig = [];
|
||||
|
||||
_gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
|
||||
_gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
|
||||
@@ -89,29 +91,23 @@ namespace Ryujinx.Input.HLE
|
||||
}
|
||||
}
|
||||
|
||||
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
|
||||
ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOnGamepadConnected(string id)
|
||||
private void HandleOnGamepadConnected(string _)
|
||||
{
|
||||
// Force input reload
|
||||
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
|
||||
ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool DriverConfigurationUpdate(ref NpadController controller, InputConfig config)
|
||||
{
|
||||
IGamepadDriver targetDriver = _gamepadDriver;
|
||||
|
||||
if (config is StandardControllerInputConfig)
|
||||
{
|
||||
targetDriver = _gamepadDriver;
|
||||
}
|
||||
else if (config is StandardKeyboardInputConfig)
|
||||
{
|
||||
targetDriver = _keyboardDriver;
|
||||
}
|
||||
IGamepadDriver targetDriver =
|
||||
config is StandardKeyboardInputConfig
|
||||
? _keyboardDriver
|
||||
: _gamepadDriver;
|
||||
|
||||
Debug.Assert(targetDriver != null, "Unknown input configuration!");
|
||||
|
||||
@@ -127,11 +123,13 @@ namespace Ryujinx.Input.HLE
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_requestedInputConfig = inputConfig?.ToList() ?? [];
|
||||
|
||||
NpadController[] oldControllers = _controllers.ToArray();
|
||||
|
||||
List<InputConfig> validInputs = [];
|
||||
|
||||
foreach (InputConfig inputConfigEntry in inputConfig)
|
||||
foreach (InputConfig inputConfigEntry in _requestedInputConfig)
|
||||
{
|
||||
NpadController controller;
|
||||
int index = (int)inputConfigEntry.PlayerIndex;
|
||||
@@ -147,7 +145,16 @@ namespace Ryujinx.Input.HLE
|
||||
controller = new(_cemuHookClient);
|
||||
}
|
||||
|
||||
bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry);
|
||||
InputConfig activeConfig = inputConfigEntry;
|
||||
bool isValid = DriverConfigurationUpdate(ref controller, activeConfig);
|
||||
|
||||
if (!isValid &&
|
||||
inputConfigEntry is StandardControllerInputConfig &&
|
||||
TryGetKeyboardFallback(inputConfigEntry, out StandardKeyboardInputConfig fallbackConfig))
|
||||
{
|
||||
activeConfig = fallbackConfig;
|
||||
isValid = DriverConfigurationUpdate(ref controller, activeConfig);
|
||||
}
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
@@ -157,7 +164,7 @@ namespace Ryujinx.Input.HLE
|
||||
else
|
||||
{
|
||||
_controllers[index] = controller;
|
||||
validInputs.Add(inputConfigEntry);
|
||||
validInputs.Add(activeConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +176,7 @@ namespace Ryujinx.Input.HLE
|
||||
oldControllers[i] = null;
|
||||
}
|
||||
|
||||
_inputConfig = inputConfig;
|
||||
_inputConfig = validInputs;
|
||||
_enableKeyboard = enableKeyboard;
|
||||
_enableMouse = enableMouse;
|
||||
|
||||
@@ -177,16 +184,58 @@ namespace Ryujinx.Input.HLE
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetKeyboardFallback(InputConfig inputConfig, out StandardKeyboardInputConfig fallbackConfig)
|
||||
{
|
||||
fallbackConfig = null;
|
||||
|
||||
ReadOnlySpan<string> keyboardIds = _keyboardDriver.GamepadsIds;
|
||||
|
||||
if (keyboardIds.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string keyboardId = keyboardIds[0];
|
||||
|
||||
using IGamepad keyboard = _keyboardDriver.GetGamepad(keyboardId);
|
||||
|
||||
if (keyboard == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fallbackConfig = InputConfigDefaults.CreateDefaultKeyboardConfiguration(
|
||||
keyboardId,
|
||||
keyboard.Name,
|
||||
inputConfig.ControllerType,
|
||||
inputConfig.PlayerIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ClearInputDriverStates()
|
||||
{
|
||||
foreach (InputConfig inputConfig in _inputConfig)
|
||||
{
|
||||
_controllers[(int)inputConfig.PlayerIndex]?.GamepadDriver?.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void UnblockInputUpdates()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (InputConfig inputConfig in _inputConfig)
|
||||
if (_inputUpdateBlockCount == 0)
|
||||
{
|
||||
_controllers[(int)inputConfig.PlayerIndex]?.GamepadDriver?.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
_blockInputUpdates = false;
|
||||
_inputUpdateBlockCount--;
|
||||
|
||||
if (_inputUpdateBlockCount == 0)
|
||||
{
|
||||
ClearInputDriverStates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +244,7 @@ namespace Ryujinx.Input.HLE
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
return _blockInputUpdates;
|
||||
return _inputUpdateBlockCount > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +252,7 @@ namespace Ryujinx.Input.HLE
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_blockInputUpdates = true;
|
||||
_inputUpdateBlockCount++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +284,7 @@ namespace Ryujinx.Input.HLE
|
||||
bool isJoyconPair = false;
|
||||
|
||||
// Do we allow input updates and is a controller connected?
|
||||
if (!_blockInputUpdates && controller != null)
|
||||
if (_inputUpdateBlockCount == 0 && controller != null)
|
||||
{
|
||||
DriverConfigurationUpdate(ref controller, inputConfig);
|
||||
|
||||
@@ -273,7 +322,7 @@ namespace Ryujinx.Input.HLE
|
||||
}
|
||||
}
|
||||
|
||||
if (!_blockInputUpdates && _enableKeyboard)
|
||||
if (_inputUpdateBlockCount == 0 && _enableKeyboard)
|
||||
{
|
||||
hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver);
|
||||
}
|
||||
@@ -334,7 +383,7 @@ namespace Ryujinx.Input.HLE
|
||||
}
|
||||
}
|
||||
|
||||
internal InputConfig GetPlayerInputConfigByIndex(int index)
|
||||
public InputConfig GetPlayerInputConfigByIndex(int index)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
|
||||
namespace Ryujinx.Input
|
||||
{
|
||||
@@ -33,15 +34,26 @@ namespace Ryujinx.Input
|
||||
{
|
||||
if (_keyState is null)
|
||||
{
|
||||
_keyState = new bool[(int)Key.Count];
|
||||
_keyState = new bool[(int)ConfigPhysicalKey.Count];
|
||||
}
|
||||
|
||||
for (Key key = 0; key < Key.Count; key++)
|
||||
for (ConfigPhysicalKey key = 0; key < ConfigPhysicalKey.Count; key++)
|
||||
{
|
||||
_keyState[(int)key] = keyboard.IsPressed(key);
|
||||
_keyState[(int)key] = keyboard.IsPressed((Key)(int)key);
|
||||
}
|
||||
|
||||
return new KeyboardStateSnapshot(_keyState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to consume a recently pressed key.
|
||||
/// </summary>
|
||||
/// <param name="key">The pressed key, if available.</param>
|
||||
/// <returns>True if a key press was consumed.</returns>
|
||||
bool TryConsumePressedKey(out Key key)
|
||||
{
|
||||
key = Key.Unknown;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
src/Ryujinx.Input/IKeyboardModeDriver.cs
Normal file
7
src/Ryujinx.Input/IKeyboardModeDriver.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Ryujinx.Input
|
||||
{
|
||||
public interface IKeyboardModeDriver : IGamepadDriver
|
||||
{
|
||||
IKeyboard GetKeyboard(string id, KeyboardInputMode mode);
|
||||
}
|
||||
}
|
||||
148
src/Ryujinx.Input/InputConfigDefaults.cs
Normal file
148
src/Ryujinx.Input/InputConfigDefaults.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
|
||||
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
||||
|
||||
namespace Ryujinx.Input
|
||||
{
|
||||
public static class InputConfigDefaults
|
||||
{
|
||||
public static StandardKeyboardInputConfig CreateDefaultKeyboardConfiguration(
|
||||
string id,
|
||||
string name,
|
||||
ControllerType controllerType,
|
||||
PlayerIndex playerIndex)
|
||||
{
|
||||
return new StandardKeyboardInputConfig
|
||||
{
|
||||
Version = InputConfig.CurrentVersion,
|
||||
Backend = InputBackendType.WindowKeyboard,
|
||||
Id = id,
|
||||
Name = name,
|
||||
PlayerIndex = playerIndex,
|
||||
ControllerType = controllerType,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<ConfigPhysicalKey>
|
||||
{
|
||||
DpadUp = ConfigPhysicalKey.Up,
|
||||
DpadDown = ConfigPhysicalKey.Down,
|
||||
DpadLeft = ConfigPhysicalKey.Left,
|
||||
DpadRight = ConfigPhysicalKey.Right,
|
||||
ButtonMinus = ConfigPhysicalKey.Minus,
|
||||
ButtonL = ConfigPhysicalKey.E,
|
||||
ButtonZl = ConfigPhysicalKey.Q,
|
||||
ButtonSl = ConfigPhysicalKey.Unbound,
|
||||
ButtonSr = ConfigPhysicalKey.Unbound,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<ConfigPhysicalKey>
|
||||
{
|
||||
StickUp = ConfigPhysicalKey.W,
|
||||
StickDown = ConfigPhysicalKey.S,
|
||||
StickLeft = ConfigPhysicalKey.A,
|
||||
StickRight = ConfigPhysicalKey.D,
|
||||
StickButton = ConfigPhysicalKey.F,
|
||||
},
|
||||
RightJoycon = new RightJoyconCommonConfig<ConfigPhysicalKey>
|
||||
{
|
||||
ButtonA = ConfigPhysicalKey.Z,
|
||||
ButtonB = ConfigPhysicalKey.X,
|
||||
ButtonX = ConfigPhysicalKey.C,
|
||||
ButtonY = ConfigPhysicalKey.V,
|
||||
ButtonPlus = ConfigPhysicalKey.Plus,
|
||||
ButtonR = ConfigPhysicalKey.U,
|
||||
ButtonZr = ConfigPhysicalKey.O,
|
||||
ButtonSl = ConfigPhysicalKey.Unbound,
|
||||
ButtonSr = ConfigPhysicalKey.Unbound,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<ConfigPhysicalKey>
|
||||
{
|
||||
StickUp = ConfigPhysicalKey.I,
|
||||
StickDown = ConfigPhysicalKey.K,
|
||||
StickLeft = ConfigPhysicalKey.J,
|
||||
StickRight = ConfigPhysicalKey.L,
|
||||
StickButton = ConfigPhysicalKey.H,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public static StandardControllerInputConfig CreateDefaultControllerConfiguration(
|
||||
string id,
|
||||
string name,
|
||||
ControllerType controllerType,
|
||||
PlayerIndex playerIndex,
|
||||
bool isNintendoStyle)
|
||||
{
|
||||
return new StandardControllerInputConfig
|
||||
{
|
||||
Version = InputConfig.CurrentVersion,
|
||||
Backend = InputBackendType.GamepadSDL3,
|
||||
Id = id,
|
||||
Name = name,
|
||||
PlayerIndex = playerIndex,
|
||||
ControllerType = controllerType,
|
||||
DeadzoneLeft = 0.1f,
|
||||
DeadzoneRight = 0.1f,
|
||||
RangeLeft = 1.0f,
|
||||
RangeRight = 1.0f,
|
||||
TriggerThreshold = 0.5f,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
|
||||
{
|
||||
DpadUp = ConfigGamepadInputId.DpadUp,
|
||||
DpadDown = ConfigGamepadInputId.DpadDown,
|
||||
DpadLeft = ConfigGamepadInputId.DpadLeft,
|
||||
DpadRight = ConfigGamepadInputId.DpadRight,
|
||||
ButtonMinus = ConfigGamepadInputId.Minus,
|
||||
ButtonL = ConfigGamepadInputId.LeftShoulder,
|
||||
ButtonZl = ConfigGamepadInputId.LeftTrigger,
|
||||
ButtonSl = ConfigGamepadInputId.SingleLeftTrigger0,
|
||||
ButtonSr = ConfigGamepadInputId.SingleRightTrigger0,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||
{
|
||||
Joystick = ConfigStickInputId.Left,
|
||||
StickButton = ConfigGamepadInputId.LeftStick,
|
||||
InvertStickX = false,
|
||||
InvertStickY = false,
|
||||
Rotate90CW = false,
|
||||
},
|
||||
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
|
||||
{
|
||||
ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
|
||||
ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
|
||||
ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
|
||||
ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
|
||||
ButtonPlus = ConfigGamepadInputId.Plus,
|
||||
ButtonR = ConfigGamepadInputId.RightShoulder,
|
||||
ButtonZr = ConfigGamepadInputId.RightTrigger,
|
||||
ButtonSl = ConfigGamepadInputId.SingleLeftTrigger1,
|
||||
ButtonSr = ConfigGamepadInputId.SingleRightTrigger1,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||
{
|
||||
Joystick = ConfigStickInputId.Right,
|
||||
StickButton = ConfigGamepadInputId.RightStick,
|
||||
InvertStickX = false,
|
||||
InvertStickY = false,
|
||||
Rotate90CW = false,
|
||||
},
|
||||
Motion = new StandardMotionConfigController
|
||||
{
|
||||
MotionBackend = MotionInputBackendType.GamepadDriver,
|
||||
EnableMotion = true,
|
||||
Sensitivity = 100,
|
||||
GyroDeadzone = 1,
|
||||
},
|
||||
Rumble = new RumbleConfigController
|
||||
{
|
||||
StrongRumble = 1f,
|
||||
WeakRumble = 1f,
|
||||
EnableRumble = false,
|
||||
UseHDRumble = true,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
78
src/Ryujinx.Input/KeyboardInputMappingHelper.cs
Normal file
78
src/Ryujinx.Input/KeyboardInputMappingHelper.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using System.Numerics;
|
||||
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
|
||||
namespace Ryujinx.Input
|
||||
{
|
||||
public static class KeyboardInputMappingHelper
|
||||
{
|
||||
public readonly record struct KeyboardButtonMapping(GamepadButtonInputId To, ConfigPhysicalKey From)
|
||||
{
|
||||
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound;
|
||||
}
|
||||
|
||||
public static KeyboardButtonMapping[] BuildButtonMappings(StandardKeyboardInputConfig configuration) =>
|
||||
[
|
||||
// Left JoyCon
|
||||
new(GamepadButtonInputId.LeftStick, configuration.LeftJoyconStick.StickButton),
|
||||
new(GamepadButtonInputId.DpadUp, configuration.LeftJoycon.DpadUp),
|
||||
new(GamepadButtonInputId.DpadDown, configuration.LeftJoycon.DpadDown),
|
||||
new(GamepadButtonInputId.DpadLeft, configuration.LeftJoycon.DpadLeft),
|
||||
new(GamepadButtonInputId.DpadRight, configuration.LeftJoycon.DpadRight),
|
||||
new(GamepadButtonInputId.Minus, configuration.LeftJoycon.ButtonMinus),
|
||||
new(GamepadButtonInputId.LeftShoulder, configuration.LeftJoycon.ButtonL),
|
||||
new(GamepadButtonInputId.LeftTrigger, configuration.LeftJoycon.ButtonZl),
|
||||
new(GamepadButtonInputId.SingleRightTrigger0, configuration.LeftJoycon.ButtonSr),
|
||||
new(GamepadButtonInputId.SingleLeftTrigger0, configuration.LeftJoycon.ButtonSl),
|
||||
|
||||
// Right JoyCon
|
||||
new(GamepadButtonInputId.RightStick, configuration.RightJoyconStick.StickButton),
|
||||
new(GamepadButtonInputId.A, configuration.RightJoycon.ButtonA),
|
||||
new(GamepadButtonInputId.B, configuration.RightJoycon.ButtonB),
|
||||
new(GamepadButtonInputId.X, configuration.RightJoycon.ButtonX),
|
||||
new(GamepadButtonInputId.Y, configuration.RightJoycon.ButtonY),
|
||||
new(GamepadButtonInputId.Plus, configuration.RightJoycon.ButtonPlus),
|
||||
new(GamepadButtonInputId.RightShoulder, configuration.RightJoycon.ButtonR),
|
||||
new(GamepadButtonInputId.RightTrigger, configuration.RightJoycon.ButtonZr),
|
||||
new(GamepadButtonInputId.SingleRightTrigger1, configuration.RightJoycon.ButtonSr),
|
||||
new(GamepadButtonInputId.SingleLeftTrigger1, configuration.RightJoycon.ButtonSl),
|
||||
];
|
||||
|
||||
public static (short X, short Y) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigPhysicalKey> stickConfig)
|
||||
{
|
||||
short stickX = 0;
|
||||
short stickY = 0;
|
||||
|
||||
if (snapshot.IsPressed(stickConfig.StickUp))
|
||||
{
|
||||
stickY += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(stickConfig.StickDown))
|
||||
{
|
||||
stickY -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(stickConfig.StickRight))
|
||||
{
|
||||
stickX += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(stickConfig.StickLeft))
|
||||
{
|
||||
stickX -= 1;
|
||||
}
|
||||
|
||||
if (stickX == 0 && stickY == 0)
|
||||
{
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY));
|
||||
|
||||
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/Ryujinx.Input/KeyboardInputMode.cs
Normal file
8
src/Ryujinx.Input/KeyboardInputMode.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Ryujinx.Input
|
||||
{
|
||||
public enum KeyboardInputMode
|
||||
{
|
||||
Semantic,
|
||||
Physical,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
|
||||
namespace Ryujinx.Input
|
||||
{
|
||||
@@ -25,5 +26,8 @@ namespace Ryujinx.Input
|
||||
/// <returns>True if the given key is pressed</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsPressed(Key key) => KeysState[(int)key];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsPressed(ConfigPhysicalKey key) => KeysState[(int)key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ using Ryujinx.Ava.Systems.Configuration;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Cpu;
|
||||
@@ -17,14 +15,12 @@ using Ryujinx.Graphics.OpenGL;
|
||||
using Ryujinx.Graphics.Vulkan;
|
||||
using Ryujinx.HLE;
|
||||
using Ryujinx.Input;
|
||||
using Ryujinx.Input.SDL3;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
||||
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
|
||||
namespace Ryujinx.Headless
|
||||
{
|
||||
@@ -90,6 +86,19 @@ namespace Ryujinx.Headless
|
||||
}
|
||||
|
||||
string gamepadName = gamepad.Name;
|
||||
|
||||
bool isNintendoStyle = false;
|
||||
|
||||
if (gamepad is SDL3Gamepad sdlGp)
|
||||
{
|
||||
// Nintendo vendor ID is 0x057E
|
||||
isNintendoStyle = sdlGp.VendorId == 0x057E;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to name-based detection
|
||||
isNintendoStyle = gamepadName.Contains("Nintendo", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
gamepad.Dispose();
|
||||
|
||||
@@ -99,131 +108,21 @@ namespace Ryujinx.Headless
|
||||
{
|
||||
if (isKeyboard)
|
||||
{
|
||||
config = new StandardKeyboardInputConfig
|
||||
{
|
||||
Version = InputConfig.CurrentVersion,
|
||||
Backend = InputBackendType.WindowKeyboard,
|
||||
Id = null,
|
||||
ControllerType = ControllerType.JoyconPair,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||
{
|
||||
DpadUp = Key.Up,
|
||||
DpadDown = Key.Down,
|
||||
DpadLeft = Key.Left,
|
||||
DpadRight = Key.Right,
|
||||
ButtonMinus = Key.Minus,
|
||||
ButtonL = Key.E,
|
||||
ButtonZl = Key.Q,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
},
|
||||
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
StickUp = Key.W,
|
||||
StickDown = Key.S,
|
||||
StickLeft = Key.A,
|
||||
StickRight = Key.D,
|
||||
StickButton = Key.F,
|
||||
},
|
||||
|
||||
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||
{
|
||||
ButtonA = Key.Z,
|
||||
ButtonB = Key.X,
|
||||
ButtonX = Key.C,
|
||||
ButtonY = Key.V,
|
||||
ButtonPlus = Key.Plus,
|
||||
ButtonR = Key.U,
|
||||
ButtonZr = Key.O,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
},
|
||||
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
StickUp = Key.I,
|
||||
StickDown = Key.K,
|
||||
StickLeft = Key.J,
|
||||
StickRight = Key.L,
|
||||
StickButton = Key.H,
|
||||
},
|
||||
};
|
||||
config = InputConfigDefaults.CreateDefaultKeyboardConfiguration(
|
||||
null,
|
||||
null,
|
||||
ControllerType.JoyconPair,
|
||||
index);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isNintendoStyle = gamepadName.Contains("Nintendo");
|
||||
|
||||
config = new StandardControllerInputConfig
|
||||
{
|
||||
Version = InputConfig.CurrentVersion,
|
||||
Backend = InputBackendType.GamepadSDL3,
|
||||
Id = null,
|
||||
ControllerType = ControllerType.JoyconPair,
|
||||
DeadzoneLeft = 0.1f,
|
||||
DeadzoneRight = 0.1f,
|
||||
RangeLeft = 1.0f,
|
||||
RangeRight = 1.0f,
|
||||
TriggerThreshold = 0.5f,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
|
||||
{
|
||||
DpadUp = ConfigGamepadInputId.DpadUp,
|
||||
DpadDown = ConfigGamepadInputId.DpadDown,
|
||||
DpadLeft = ConfigGamepadInputId.DpadLeft,
|
||||
DpadRight = ConfigGamepadInputId.DpadRight,
|
||||
ButtonMinus = ConfigGamepadInputId.Minus,
|
||||
ButtonL = ConfigGamepadInputId.LeftShoulder,
|
||||
ButtonZl = ConfigGamepadInputId.LeftTrigger,
|
||||
ButtonSl = ConfigGamepadInputId.SingleLeftTrigger0,
|
||||
ButtonSr = ConfigGamepadInputId.SingleRightTrigger0,
|
||||
},
|
||||
|
||||
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||
{
|
||||
Joystick = ConfigStickInputId.Left,
|
||||
StickButton = ConfigGamepadInputId.LeftStick,
|
||||
InvertStickX = false,
|
||||
InvertStickY = false,
|
||||
Rotate90CW = false,
|
||||
},
|
||||
|
||||
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
|
||||
{
|
||||
ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
|
||||
ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
|
||||
ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
|
||||
ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
|
||||
ButtonPlus = ConfigGamepadInputId.Plus,
|
||||
ButtonR = ConfigGamepadInputId.RightShoulder,
|
||||
ButtonZr = ConfigGamepadInputId.RightTrigger,
|
||||
ButtonSl = ConfigGamepadInputId.SingleLeftTrigger1,
|
||||
ButtonSr = ConfigGamepadInputId.SingleRightTrigger1,
|
||||
},
|
||||
|
||||
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||
{
|
||||
Joystick = ConfigStickInputId.Right,
|
||||
StickButton = ConfigGamepadInputId.RightStick,
|
||||
InvertStickX = false,
|
||||
InvertStickY = false,
|
||||
Rotate90CW = false,
|
||||
},
|
||||
|
||||
Motion = new StandardMotionConfigController
|
||||
{
|
||||
MotionBackend = MotionInputBackendType.GamepadDriver,
|
||||
EnableMotion = true,
|
||||
Sensitivity = 100,
|
||||
GyroDeadzone = 1,
|
||||
},
|
||||
Rumble = new RumbleConfigController
|
||||
{
|
||||
StrongRumble = 1f,
|
||||
WeakRumble = 1f,
|
||||
EnableRumble = false,
|
||||
UseHDRumble = true
|
||||
},
|
||||
};
|
||||
config = InputConfigDefaults.CreateDefaultControllerConfiguration(
|
||||
null,
|
||||
null,
|
||||
ControllerType.JoyconPair,
|
||||
index,
|
||||
isNintendoStyle);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -7,15 +7,15 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
|
||||
using Key = Ryujinx.Input.Key;
|
||||
|
||||
namespace Ryujinx.Ava.Input
|
||||
{
|
||||
internal class AvaloniaKeyboard : IKeyboard
|
||||
{
|
||||
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
|
||||
private readonly List<KeyboardInputMappingHelper.KeyboardButtonMapping> _buttonsUserMapping;
|
||||
private readonly AvaloniaKeyboardDriver _driver;
|
||||
private readonly KeyboardInputMode _mode;
|
||||
private StandardKeyboardInputConfig _configuration;
|
||||
|
||||
private readonly Lock _userMappingLock = new();
|
||||
@@ -25,18 +25,12 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
public bool IsConnected => true;
|
||||
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
|
||||
|
||||
private class ButtonMappingEntry(GamepadButtonInputId to, Key from)
|
||||
{
|
||||
public readonly GamepadButtonInputId To = to;
|
||||
public readonly Key From = from;
|
||||
}
|
||||
|
||||
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
|
||||
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name, KeyboardInputMode mode)
|
||||
{
|
||||
_buttonsUserMapping = [];
|
||||
|
||||
_driver = driver;
|
||||
_mode = mode;
|
||||
Id = id;
|
||||
Name = name;
|
||||
}
|
||||
@@ -58,22 +52,18 @@ namespace Ryujinx.Ava.Input
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
|
||||
foreach (KeyboardInputMappingHelper.KeyboardButtonMapping entry in _buttonsUserMapping)
|
||||
{
|
||||
if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound)
|
||||
if (!entry.IsValid || result.IsPressed(entry.To))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: Do not touch state of the button already pressed.
|
||||
if (!result.IsPressed(entry.To))
|
||||
{
|
||||
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
|
||||
}
|
||||
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
|
||||
}
|
||||
|
||||
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
||||
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
||||
(short leftStickX, short leftStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.LeftJoyconStick);
|
||||
(short rightStickX, short rightStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.RightJoyconStick);
|
||||
|
||||
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
|
||||
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
|
||||
@@ -101,7 +91,7 @@ namespace Ryujinx.Ava.Input
|
||||
{
|
||||
try
|
||||
{
|
||||
return _driver.IsPressed(key);
|
||||
return _driver.IsPressed(key, _mode);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -109,6 +99,19 @@ namespace Ryujinx.Ava.Input
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryConsumePressedKey(out Key key)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _driver.TryConsumePressedKey(_mode, out key);
|
||||
}
|
||||
catch
|
||||
{
|
||||
key = Key.Unknown;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetConfiguration(InputConfig configuration)
|
||||
{
|
||||
lock (_userMappingLock)
|
||||
@@ -117,53 +120,20 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
_buttonsUserMapping.Clear();
|
||||
|
||||
#pragma warning disable IDE0055 // Disable formatting
|
||||
// Left JoyCon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
|
||||
|
||||
// Right JoyCon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
|
||||
#pragma warning restore IDE0055
|
||||
_buttonsUserMapping.AddRange(KeyboardInputMappingHelper.BuildButtonMappings(_configuration));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLed(uint packedRgb)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard");
|
||||
Logger.Debug?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard");
|
||||
}
|
||||
|
||||
public void SetTriggerThreshold(float triggerThreshold)
|
||||
{
|
||||
// No operations
|
||||
}
|
||||
public bool HDRumble(VibrationValue left, VibrationValue right) => false;
|
||||
|
||||
public bool HDRumble(VibrationValue left, VibrationValue right)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
public void SetTriggerThreshold(float triggerThreshold) { }
|
||||
|
||||
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs) => false;
|
||||
|
||||
public Vector3 GetMotionData(MotionInputId inputId) => Vector3.Zero;
|
||||
|
||||
@@ -174,41 +144,9 @@ namespace Ryujinx.Ava.Input
|
||||
return value * ConvertRate;
|
||||
}
|
||||
|
||||
private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
|
||||
{
|
||||
short stickX = 0;
|
||||
short stickY = 0;
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickUp))
|
||||
{
|
||||
stickY += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickDown))
|
||||
{
|
||||
stickY -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickRight))
|
||||
{
|
||||
stickX += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)stickConfig.StickLeft))
|
||||
{
|
||||
stickX -= 1;
|
||||
}
|
||||
|
||||
Vector2 stick = new(stickX, stickY);
|
||||
|
||||
stick = Vector2.Normalize(stick);
|
||||
|
||||
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_driver?.Clear();
|
||||
_driver?.Clear(_mode);
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
@@ -1,19 +1,55 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Systems.Configuration;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using AvaKey = Avalonia.Input.Key;
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
using Key = Ryujinx.Input.Key;
|
||||
|
||||
namespace Ryujinx.Ava.Input
|
||||
{
|
||||
internal class AvaloniaKeyboardDriver : IGamepadDriver
|
||||
internal class AvaloniaKeyboardDriver : IKeyboardModeDriver
|
||||
{
|
||||
private enum PhysicalKeySource
|
||||
{
|
||||
Direct,
|
||||
ObservedFallback,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum CGEventFlags : ulong
|
||||
{
|
||||
AlphaShift = 1UL << 16 // CapsLock
|
||||
}
|
||||
|
||||
private enum CGEventSourceStateID : uint
|
||||
{
|
||||
HIDSystemState = 1
|
||||
}
|
||||
|
||||
[DllImport("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics")]
|
||||
private static extern CGEventFlags CGEventSourceFlagsState(CGEventSourceStateID stateID);
|
||||
private static readonly string[] _keyboardIdentifers = ["0"];
|
||||
private readonly Control _control;
|
||||
private readonly HashSet<AvaKey> _pressedKeys;
|
||||
private readonly Window _window;
|
||||
private readonly HashSet<Key> _semanticPressedKeys;
|
||||
private readonly HashSet<ConfigPhysicalKey> _physicalPressedKeys;
|
||||
private readonly HashSet<Key> _keysToRestoreAfterActivation;
|
||||
private readonly Dictionary<Key, ConfigPhysicalKey> _observedPhysicalKeysBySemanticKey;
|
||||
private readonly Queue<Key> _semanticPressedKeyQueue;
|
||||
private readonly Queue<Key> _physicalPressedKeyQueue;
|
||||
private readonly Lock _pressedKeyQueueLock;
|
||||
private readonly KeyboardInputMode _defaultMode;
|
||||
|
||||
public event EventHandler<KeyEventArgs> KeyPressed;
|
||||
public event EventHandler<KeyEventArgs> KeyRelease;
|
||||
@@ -22,14 +58,41 @@ namespace Ryujinx.Ava.Input
|
||||
public string DriverName => "AvaloniaKeyboardDriver";
|
||||
public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
|
||||
|
||||
public AvaloniaKeyboardDriver(Control control)
|
||||
public AvaloniaKeyboardDriver(Control control, KeyboardInputMode defaultMode = KeyboardInputMode.Semantic)
|
||||
{
|
||||
_control = control;
|
||||
_pressedKeys = [];
|
||||
_window = control as Window ?? TopLevel.GetTopLevel(control) as Window;
|
||||
_semanticPressedKeys = [];
|
||||
_physicalPressedKeys = [];
|
||||
_keysToRestoreAfterActivation = [];
|
||||
_observedPhysicalKeysBySemanticKey = [];
|
||||
_semanticPressedKeyQueue = [];
|
||||
_physicalPressedKeyQueue = [];
|
||||
_pressedKeyQueueLock = new();
|
||||
_defaultMode = defaultMode;
|
||||
|
||||
_control.KeyDown += OnKeyPress;
|
||||
_control.KeyUp += OnKeyRelease;
|
||||
_control.AddHandler(InputElement.KeyDownEvent, OnKeyPress, RoutingStrategies.Tunnel, true);
|
||||
_control.AddHandler(InputElement.KeyUpEvent, OnKeyRelease, RoutingStrategies.Tunnel, true);
|
||||
_control.TextInput += Control_TextInput;
|
||||
_window?.Activated += Window_Activated;
|
||||
_window?.Deactivated += Window_Deactivated;
|
||||
}
|
||||
|
||||
private void Window_Activated(object sender, EventArgs e)
|
||||
{
|
||||
RestorePressedKeysAfterActivation();
|
||||
}
|
||||
|
||||
private void Window_Deactivated(object sender, EventArgs e)
|
||||
{
|
||||
lock (_pressedKeyQueueLock)
|
||||
{
|
||||
_keysToRestoreAfterActivation.Clear();
|
||||
_keysToRestoreAfterActivation.UnionWith(_semanticPressedKeys);
|
||||
_observedPhysicalKeysBySemanticKey.Clear();
|
||||
}
|
||||
|
||||
Clear();
|
||||
}
|
||||
|
||||
private void Control_TextInput(object sender, TextInputEventArgs e)
|
||||
@@ -50,13 +113,18 @@ namespace Ryujinx.Ava.Input
|
||||
}
|
||||
|
||||
public IGamepad GetGamepad(string id)
|
||||
{
|
||||
return GetKeyboard(id, _defaultMode);
|
||||
}
|
||||
|
||||
public IKeyboard GetKeyboard(string id, KeyboardInputMode mode)
|
||||
{
|
||||
if (!_keyboardIdentifers[0].Equals(id))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.AllKeyboards]);
|
||||
return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.KeyboardLayout_KeyboardInputMode], mode);
|
||||
}
|
||||
|
||||
public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
|
||||
@@ -65,40 +133,448 @@ namespace Ryujinx.Ava.Input
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_control.KeyUp -= OnKeyPress;
|
||||
_control.KeyDown -= OnKeyRelease;
|
||||
_control.RemoveHandler(InputElement.KeyDownEvent, OnKeyPress);
|
||||
_control.RemoveHandler(InputElement.KeyUpEvent, OnKeyRelease);
|
||||
_control.TextInput -= Control_TextInput;
|
||||
if (_window != null)
|
||||
{
|
||||
_window.Activated -= Window_Activated;
|
||||
_window.Deactivated -= Window_Deactivated;
|
||||
}
|
||||
_observedPhysicalKeysBySemanticKey.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnKeyPress(object sender, KeyEventArgs args)
|
||||
{
|
||||
_pressedKeys.Add(args.Key);
|
||||
|
||||
UpdateKeyStates(args, true);
|
||||
KeyPressed?.Invoke(this, args);
|
||||
}
|
||||
|
||||
protected void OnKeyRelease(object sender, KeyEventArgs args)
|
||||
{
|
||||
_pressedKeys.Remove(args.Key);
|
||||
|
||||
UpdateKeyStates(args, false);
|
||||
KeyRelease?.Invoke(this, args);
|
||||
}
|
||||
|
||||
internal bool IsPressed(Key key)
|
||||
internal bool IsPressed(Key key, KeyboardInputMode mode)
|
||||
{
|
||||
if (key is Key.Unbound or Key.Unknown)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AvaloniaKeyboardMappingHelper.TryGetAvaKey(key, out AvaKey nativeKey);
|
||||
if (key == Key.CapsLock)
|
||||
{
|
||||
return IsCapsLockOnMacOS();
|
||||
}
|
||||
|
||||
return _pressedKeys.Contains(nativeKey);
|
||||
return mode == KeyboardInputMode.Physical
|
||||
? _physicalPressedKeys.Contains((ConfigPhysicalKey)(int)key)
|
||||
: _semanticPressedKeys.Contains(key);
|
||||
}
|
||||
|
||||
private bool IsCapsLockOnMacOS()
|
||||
{
|
||||
bool currentState = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
CGEventFlags flags = CGEventSourceFlagsState(CGEventSourceStateID.HIDSystemState);
|
||||
currentState = (flags & CGEventFlags.AlphaShift) != 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: use Avalonia's tracked key state (semantic CapsLock)
|
||||
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey(Key.CapsLock, out AvaKey nativeKey))
|
||||
{
|
||||
currentState = _semanticPressedKeys.Contains(Key.CapsLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.UI, $"Failed to query CapsLock state: {ex}");
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
internal void Clear(KeyboardInputMode mode)
|
||||
{
|
||||
lock (_pressedKeyQueueLock)
|
||||
{
|
||||
if (mode == KeyboardInputMode.Physical)
|
||||
{
|
||||
_physicalPressedKeys.Clear();
|
||||
_physicalPressedKeyQueue.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
_semanticPressedKeys.Clear();
|
||||
_semanticPressedKeyQueue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_pressedKeys.Clear();
|
||||
lock (_pressedKeyQueueLock)
|
||||
{
|
||||
_semanticPressedKeys.Clear();
|
||||
_physicalPressedKeys.Clear();
|
||||
_semanticPressedKeyQueue.Clear();
|
||||
_physicalPressedKeyQueue.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void RestorePressedKeysAfterActivation()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
lock (_pressedKeyQueueLock)
|
||||
{
|
||||
_keysToRestoreAfterActivation.Clear();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_pressedKeyQueueLock)
|
||||
{
|
||||
if (_keysToRestoreAfterActivation.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Key key in _keysToRestoreAfterActivation)
|
||||
{
|
||||
if (!TryGetWindowsVirtualKey(key, out int virtualKey) ||
|
||||
!IsWindowsKeyPressed(virtualKey))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_semanticPressedKeys.Add(key);
|
||||
|
||||
ConfigPhysicalKey physicalKey = GetPhysicalKeyForSemanticKey(key);
|
||||
|
||||
if (physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound)
|
||||
{
|
||||
_physicalPressedKeys.Add(physicalKey);
|
||||
}
|
||||
}
|
||||
|
||||
_keysToRestoreAfterActivation.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private ConfigPhysicalKey GetPhysicalKeyForSemanticKey(Key key)
|
||||
{
|
||||
if (_observedPhysicalKeysBySemanticKey.TryGetValue(key, out ConfigPhysicalKey physicalKey))
|
||||
{
|
||||
return physicalKey;
|
||||
}
|
||||
|
||||
return key is >= Key.Unknown and < Key.Count
|
||||
? (ConfigPhysicalKey)(int)key
|
||||
: ConfigPhysicalKey.Unknown;
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static bool IsWindowsKeyPressed(int virtualKey)
|
||||
{
|
||||
return (Win32NativeInterop.GetAsyncKeyState(virtualKey) & 0x8000) != 0;
|
||||
}
|
||||
|
||||
private static bool TryGetWindowsVirtualKey(Key key, out int virtualKey)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case >= Key.A and <= Key.Z:
|
||||
virtualKey = 'A' + (int)(key - Key.A);
|
||||
return true;
|
||||
case >= Key.Number0 and <= Key.Number9:
|
||||
virtualKey = '0' + (int)(key - Key.Number0);
|
||||
return true;
|
||||
case >= Key.F1 and <= Key.F24:
|
||||
virtualKey = 0x70 + (int)(key - Key.F1);
|
||||
return true;
|
||||
case Key.ShiftLeft:
|
||||
virtualKey = 0xA0;
|
||||
return true;
|
||||
case Key.ShiftRight:
|
||||
virtualKey = 0xA1;
|
||||
return true;
|
||||
case Key.ControlLeft:
|
||||
virtualKey = 0xA2;
|
||||
return true;
|
||||
case Key.ControlRight:
|
||||
virtualKey = 0xA3;
|
||||
return true;
|
||||
case Key.AltLeft:
|
||||
virtualKey = 0xA4;
|
||||
return true;
|
||||
case Key.AltRight:
|
||||
virtualKey = 0xA5;
|
||||
return true;
|
||||
case Key.WinLeft:
|
||||
virtualKey = 0x5B;
|
||||
return true;
|
||||
case Key.WinRight:
|
||||
virtualKey = 0x5C;
|
||||
return true;
|
||||
case Key.Menu:
|
||||
virtualKey = 0x5D;
|
||||
return true;
|
||||
case Key.Up:
|
||||
virtualKey = 0x26;
|
||||
return true;
|
||||
case Key.Down:
|
||||
virtualKey = 0x28;
|
||||
return true;
|
||||
case Key.Left:
|
||||
virtualKey = 0x25;
|
||||
return true;
|
||||
case Key.Right:
|
||||
virtualKey = 0x27;
|
||||
return true;
|
||||
case Key.Enter:
|
||||
virtualKey = 0x0D;
|
||||
return true;
|
||||
case Key.Escape:
|
||||
virtualKey = 0x1B;
|
||||
return true;
|
||||
case Key.Space:
|
||||
virtualKey = 0x20;
|
||||
return true;
|
||||
case Key.Tab:
|
||||
virtualKey = 0x09;
|
||||
return true;
|
||||
case Key.BackSpace:
|
||||
virtualKey = 0x08;
|
||||
return true;
|
||||
case Key.Insert:
|
||||
virtualKey = 0x2D;
|
||||
return true;
|
||||
case Key.Delete:
|
||||
virtualKey = 0x2E;
|
||||
return true;
|
||||
case Key.PageUp:
|
||||
virtualKey = 0x21;
|
||||
return true;
|
||||
case Key.PageDown:
|
||||
virtualKey = 0x22;
|
||||
return true;
|
||||
case Key.Home:
|
||||
virtualKey = 0x24;
|
||||
return true;
|
||||
case Key.End:
|
||||
virtualKey = 0x23;
|
||||
return true;
|
||||
case Key.CapsLock:
|
||||
virtualKey = 0x14;
|
||||
return true;
|
||||
case Key.ScrollLock:
|
||||
virtualKey = 0x91;
|
||||
return true;
|
||||
case Key.PrintScreen:
|
||||
virtualKey = 0x2C;
|
||||
return true;
|
||||
case Key.Pause:
|
||||
virtualKey = 0x13;
|
||||
return true;
|
||||
case Key.NumLock:
|
||||
virtualKey = 0x90;
|
||||
return true;
|
||||
case Key.Clear:
|
||||
virtualKey = 0x0C;
|
||||
return true;
|
||||
case >= Key.Keypad0 and <= Key.Keypad9:
|
||||
virtualKey = 0x60 + (int)(key - Key.Keypad0);
|
||||
return true;
|
||||
case Key.KeypadDivide:
|
||||
virtualKey = 0x6F;
|
||||
return true;
|
||||
case Key.KeypadMultiply:
|
||||
virtualKey = 0x6A;
|
||||
return true;
|
||||
case Key.KeypadSubtract:
|
||||
virtualKey = 0x6D;
|
||||
return true;
|
||||
case Key.KeypadAdd:
|
||||
virtualKey = 0x6B;
|
||||
return true;
|
||||
case Key.KeypadDecimal:
|
||||
virtualKey = 0x6E;
|
||||
return true;
|
||||
case Key.KeypadEnter:
|
||||
virtualKey = 0x0D;
|
||||
return true;
|
||||
case Key.Tilde:
|
||||
virtualKey = 0xC0;
|
||||
return true;
|
||||
case Key.Grave:
|
||||
virtualKey = 0xE2;
|
||||
return true;
|
||||
case Key.Minus:
|
||||
virtualKey = 0xBD;
|
||||
return true;
|
||||
case Key.Plus:
|
||||
virtualKey = 0xBB;
|
||||
return true;
|
||||
case Key.BracketLeft:
|
||||
virtualKey = 0xDB;
|
||||
return true;
|
||||
case Key.BracketRight:
|
||||
virtualKey = 0xDD;
|
||||
return true;
|
||||
case Key.Semicolon:
|
||||
virtualKey = 0xBA;
|
||||
return true;
|
||||
case Key.Quote:
|
||||
virtualKey = 0xDE;
|
||||
return true;
|
||||
case Key.Comma:
|
||||
virtualKey = 0xBC;
|
||||
return true;
|
||||
case Key.Period:
|
||||
virtualKey = 0xBE;
|
||||
return true;
|
||||
case Key.Slash:
|
||||
virtualKey = 0xBF;
|
||||
return true;
|
||||
case Key.BackSlash:
|
||||
virtualKey = 0xDC;
|
||||
return true;
|
||||
default:
|
||||
virtualKey = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool TryConsumePressedKey(KeyboardInputMode mode, out Key key)
|
||||
{
|
||||
lock (_pressedKeyQueueLock)
|
||||
{
|
||||
Queue<Key> queue = mode == KeyboardInputMode.Physical ? _physicalPressedKeyQueue : _semanticPressedKeyQueue;
|
||||
|
||||
if (queue.TryDequeue(out key))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
key = Key.Unknown;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void UpdateKeyState(HashSet<Key> pressedKeys, Key key, bool isPressed)
|
||||
{
|
||||
if (key is Key.Unknown or Key.Unbound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPressed)
|
||||
{
|
||||
pressedKeys.Add(key);
|
||||
return;
|
||||
}
|
||||
|
||||
pressedKeys.Remove(key);
|
||||
}
|
||||
|
||||
private static void UpdateKeyState(HashSet<ConfigPhysicalKey> pressedKeys, ConfigPhysicalKey key, bool isPressed)
|
||||
{
|
||||
if (key is ConfigPhysicalKey.Unknown or ConfigPhysicalKey.Unbound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPressed)
|
||||
{
|
||||
pressedKeys.Add(key);
|
||||
return;
|
||||
}
|
||||
|
||||
pressedKeys.Remove(key);
|
||||
}
|
||||
|
||||
private void UpdateKeyStates(KeyEventArgs args, bool isPressed)
|
||||
{
|
||||
Key semanticKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.Key);
|
||||
Key resolvedSemanticKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key);
|
||||
ConfigPhysicalKey physicalKey = GetPhysicalInputKey(args, semanticKey, out PhysicalKeySource physicalKeySource);
|
||||
bool semanticWasPressed = _semanticPressedKeys.Contains(resolvedSemanticKey);
|
||||
bool physicalWasPressed = _physicalPressedKeys.Contains(physicalKey);
|
||||
bool semanticStateChanged = resolvedSemanticKey is not Key.Unknown and not Key.Unbound && semanticWasPressed != isPressed;
|
||||
bool physicalStateChanged = physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound && physicalWasPressed != isPressed;
|
||||
bool bufferedSemanticPress = false;
|
||||
bool bufferedPhysicalPress = false;
|
||||
|
||||
UpdateKeyState(_semanticPressedKeys, resolvedSemanticKey, isPressed);
|
||||
UpdateKeyState(_physicalPressedKeys, physicalKey, isPressed);
|
||||
|
||||
if (isPressed)
|
||||
{
|
||||
lock (_pressedKeyQueueLock)
|
||||
{
|
||||
if (!semanticWasPressed && resolvedSemanticKey is not Key.Unknown and not Key.Unbound)
|
||||
{
|
||||
_semanticPressedKeyQueue.Enqueue(resolvedSemanticKey);
|
||||
bufferedSemanticPress = true;
|
||||
}
|
||||
|
||||
if (!physicalWasPressed && physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound)
|
||||
{
|
||||
_physicalPressedKeyQueue.Enqueue((Key)(int)physicalKey);
|
||||
bufferedPhysicalPress = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isPressed &&
|
||||
semanticKey is not Key.Unknown and not Key.Unbound &&
|
||||
physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound)
|
||||
{
|
||||
_observedPhysicalKeysBySemanticKey[semanticKey] = physicalKey;
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Logger.EnableAvaloniaLog &&
|
||||
(semanticStateChanged || physicalStateChanged))
|
||||
{
|
||||
Logger.Info?.Print(
|
||||
LogClass.UI,
|
||||
$"Keyboard {(isPressed ? "down" : "up")}: avaloniaKey={args.Key}, avaloniaPhysical={args.PhysicalKey}, keySymbol={FormatKeySymbol(args.KeySymbol)}, modifiers={args.KeyModifiers}, semantic={semanticKey}, resolvedSemantic={resolvedSemanticKey}, physical={physicalKey}, physicalSource={physicalKeySource}, bufferedSemantic={bufferedSemanticPress}, bufferedPhysical={bufferedPhysicalPress}, semanticPressed={_semanticPressedKeys.Count}, physicalPressed={_physicalPressedKeys.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
private ConfigPhysicalKey GetPhysicalInputKey(KeyEventArgs args, Key semanticKey, out PhysicalKeySource source)
|
||||
{
|
||||
Key key = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey);
|
||||
|
||||
if (key is >= Key.Unknown and < Key.Count)
|
||||
{
|
||||
source = PhysicalKeySource.Direct;
|
||||
return (ConfigPhysicalKey)(int)key;
|
||||
}
|
||||
|
||||
if (semanticKey is not Key.Unknown and not Key.Unbound &&
|
||||
_observedPhysicalKeysBySemanticKey.TryGetValue(semanticKey, out ConfigPhysicalKey observedPhysicalKey))
|
||||
{
|
||||
source = PhysicalKeySource.ObservedFallback;
|
||||
return observedPhysicalKey;
|
||||
}
|
||||
|
||||
source = PhysicalKeySource.Unknown;
|
||||
return ConfigPhysicalKey.Unknown;
|
||||
}
|
||||
|
||||
private static string FormatKeySymbol(string keySymbol)
|
||||
{
|
||||
return string.IsNullOrEmpty(keySymbol) ? "<none>" : keySymbol;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -2,6 +2,7 @@ using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AvaKey = Avalonia.Input.Key;
|
||||
using AvaPhysicalKey = Avalonia.Input.PhysicalKey;
|
||||
|
||||
namespace Ryujinx.Ava.Input
|
||||
{
|
||||
@@ -132,7 +133,8 @@ namespace Ryujinx.Ava.Input
|
||||
AvaKey.D8,
|
||||
AvaKey.D9,
|
||||
AvaKey.OemTilde,
|
||||
AvaKey.OemTilde,AvaKey.OemMinus,
|
||||
AvaKey.Oem102,
|
||||
AvaKey.OemMinus,
|
||||
AvaKey.OemPlus,
|
||||
AvaKey.OemOpenBrackets,
|
||||
AvaKey.OemCloseBrackets,
|
||||
@@ -147,7 +149,149 @@ namespace Ryujinx.Ava.Input
|
||||
AvaKey.None
|
||||
];
|
||||
|
||||
private static readonly AvaPhysicalKey[] _physicalKeyMapping =
|
||||
[
|
||||
// NOTE: Invalid
|
||||
AvaPhysicalKey.None,
|
||||
|
||||
AvaPhysicalKey.ShiftLeft,
|
||||
AvaPhysicalKey.ShiftRight,
|
||||
AvaPhysicalKey.ControlLeft,
|
||||
AvaPhysicalKey.ControlRight,
|
||||
AvaPhysicalKey.AltLeft,
|
||||
AvaPhysicalKey.AltRight,
|
||||
AvaPhysicalKey.MetaLeft,
|
||||
AvaPhysicalKey.MetaRight,
|
||||
AvaPhysicalKey.ContextMenu,
|
||||
AvaPhysicalKey.F1,
|
||||
AvaPhysicalKey.F2,
|
||||
AvaPhysicalKey.F3,
|
||||
AvaPhysicalKey.F4,
|
||||
AvaPhysicalKey.F5,
|
||||
AvaPhysicalKey.F6,
|
||||
AvaPhysicalKey.F7,
|
||||
AvaPhysicalKey.F8,
|
||||
AvaPhysicalKey.F9,
|
||||
AvaPhysicalKey.F10,
|
||||
AvaPhysicalKey.F11,
|
||||
AvaPhysicalKey.F12,
|
||||
AvaPhysicalKey.F13,
|
||||
AvaPhysicalKey.F14,
|
||||
AvaPhysicalKey.F15,
|
||||
AvaPhysicalKey.F16,
|
||||
AvaPhysicalKey.F17,
|
||||
AvaPhysicalKey.F18,
|
||||
AvaPhysicalKey.F19,
|
||||
AvaPhysicalKey.F20,
|
||||
AvaPhysicalKey.F21,
|
||||
AvaPhysicalKey.F22,
|
||||
AvaPhysicalKey.F23,
|
||||
AvaPhysicalKey.F24,
|
||||
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
AvaPhysicalKey.None,
|
||||
|
||||
AvaPhysicalKey.ArrowUp,
|
||||
AvaPhysicalKey.ArrowDown,
|
||||
AvaPhysicalKey.ArrowLeft,
|
||||
AvaPhysicalKey.ArrowRight,
|
||||
AvaPhysicalKey.Enter,
|
||||
AvaPhysicalKey.Escape,
|
||||
AvaPhysicalKey.Space,
|
||||
AvaPhysicalKey.Tab,
|
||||
AvaPhysicalKey.Backspace,
|
||||
AvaPhysicalKey.Insert,
|
||||
AvaPhysicalKey.Delete,
|
||||
AvaPhysicalKey.PageUp,
|
||||
AvaPhysicalKey.PageDown,
|
||||
AvaPhysicalKey.Home,
|
||||
AvaPhysicalKey.End,
|
||||
AvaPhysicalKey.CapsLock,
|
||||
AvaPhysicalKey.ScrollLock,
|
||||
AvaPhysicalKey.PrintScreen,
|
||||
AvaPhysicalKey.Pause,
|
||||
AvaPhysicalKey.NumLock,
|
||||
AvaPhysicalKey.NumPadClear,
|
||||
AvaPhysicalKey.NumPad0,
|
||||
AvaPhysicalKey.NumPad1,
|
||||
AvaPhysicalKey.NumPad2,
|
||||
AvaPhysicalKey.NumPad3,
|
||||
AvaPhysicalKey.NumPad4,
|
||||
AvaPhysicalKey.NumPad5,
|
||||
AvaPhysicalKey.NumPad6,
|
||||
AvaPhysicalKey.NumPad7,
|
||||
AvaPhysicalKey.NumPad8,
|
||||
AvaPhysicalKey.NumPad9,
|
||||
AvaPhysicalKey.NumPadDivide,
|
||||
AvaPhysicalKey.NumPadMultiply,
|
||||
AvaPhysicalKey.NumPadSubtract,
|
||||
AvaPhysicalKey.NumPadAdd,
|
||||
AvaPhysicalKey.NumPadDecimal,
|
||||
AvaPhysicalKey.NumPadEnter,
|
||||
AvaPhysicalKey.A,
|
||||
AvaPhysicalKey.B,
|
||||
AvaPhysicalKey.C,
|
||||
AvaPhysicalKey.D,
|
||||
AvaPhysicalKey.E,
|
||||
AvaPhysicalKey.F,
|
||||
AvaPhysicalKey.G,
|
||||
AvaPhysicalKey.H,
|
||||
AvaPhysicalKey.I,
|
||||
AvaPhysicalKey.J,
|
||||
AvaPhysicalKey.K,
|
||||
AvaPhysicalKey.L,
|
||||
AvaPhysicalKey.M,
|
||||
AvaPhysicalKey.N,
|
||||
AvaPhysicalKey.O,
|
||||
AvaPhysicalKey.P,
|
||||
AvaPhysicalKey.Q,
|
||||
AvaPhysicalKey.R,
|
||||
AvaPhysicalKey.S,
|
||||
AvaPhysicalKey.T,
|
||||
AvaPhysicalKey.U,
|
||||
AvaPhysicalKey.V,
|
||||
AvaPhysicalKey.W,
|
||||
AvaPhysicalKey.X,
|
||||
AvaPhysicalKey.Y,
|
||||
AvaPhysicalKey.Z,
|
||||
AvaPhysicalKey.Digit0,
|
||||
AvaPhysicalKey.Digit1,
|
||||
AvaPhysicalKey.Digit2,
|
||||
AvaPhysicalKey.Digit3,
|
||||
AvaPhysicalKey.Digit4,
|
||||
AvaPhysicalKey.Digit5,
|
||||
AvaPhysicalKey.Digit6,
|
||||
AvaPhysicalKey.Digit7,
|
||||
AvaPhysicalKey.Digit8,
|
||||
AvaPhysicalKey.Digit9,
|
||||
AvaPhysicalKey.Backquote,
|
||||
AvaPhysicalKey.IntlBackslash,
|
||||
AvaPhysicalKey.Minus,
|
||||
AvaPhysicalKey.Equal,
|
||||
AvaPhysicalKey.BracketLeft,
|
||||
AvaPhysicalKey.BracketRight,
|
||||
AvaPhysicalKey.Semicolon,
|
||||
AvaPhysicalKey.Quote,
|
||||
AvaPhysicalKey.Comma,
|
||||
AvaPhysicalKey.Period,
|
||||
AvaPhysicalKey.Slash,
|
||||
AvaPhysicalKey.Backslash,
|
||||
|
||||
// NOTE: invalid
|
||||
AvaPhysicalKey.None
|
||||
];
|
||||
|
||||
private static readonly Dictionary<AvaKey, Key> _avaKeyMapping;
|
||||
private static readonly Dictionary<AvaPhysicalKey, Key> _avaPhysicalKeyMapping;
|
||||
|
||||
static AvaloniaKeyboardMappingHelper()
|
||||
{
|
||||
@@ -155,21 +299,42 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
// NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
|
||||
_avaKeyMapping = new Dictionary<AvaKey, Key>();
|
||||
_avaPhysicalKeyMapping = new Dictionary<AvaPhysicalKey, Key>();
|
||||
|
||||
foreach (Key key in inputKeys)
|
||||
{
|
||||
if (TryGetAvaKey(key, out AvaKey index))
|
||||
if (TryGetAvaKey(key, out AvaKey avaKey))
|
||||
{
|
||||
_avaKeyMapping[index] = key;
|
||||
_avaKeyMapping[avaKey] = key;
|
||||
}
|
||||
|
||||
if (TryGetAvaPhysicalKey(key, out AvaPhysicalKey avaPhysicalKey))
|
||||
{
|
||||
_avaPhysicalKeyMapping[avaPhysicalKey] = key;
|
||||
}
|
||||
}
|
||||
|
||||
// Alias additional Avalonia key values to improve non-US layout support.
|
||||
_avaKeyMapping[AvaKey.Oem1] = Key.Semicolon;
|
||||
_avaKeyMapping[AvaKey.Oem2] = Key.Slash;
|
||||
_avaKeyMapping[AvaKey.Oem3] = Key.Tilde;
|
||||
_avaKeyMapping[AvaKey.Oem4] = Key.BracketLeft;
|
||||
_avaKeyMapping[AvaKey.Oem5] = Key.BackSlash;
|
||||
_avaKeyMapping[AvaKey.Oem6] = Key.BracketRight;
|
||||
_avaKeyMapping[AvaKey.Oem7] = Key.Quote;
|
||||
_avaKeyMapping[AvaKey.OemBackslash] = Key.Grave;
|
||||
_avaKeyMapping[AvaKey.Oem102] = Key.Grave;
|
||||
|
||||
// Common alternates for non-US/JIS physical keys.
|
||||
_avaPhysicalKeyMapping[AvaPhysicalKey.IntlRo] = Key.BackSlash;
|
||||
_avaPhysicalKeyMapping[AvaPhysicalKey.IntlYen] = Key.BackSlash;
|
||||
}
|
||||
|
||||
public static bool TryGetAvaKey(Key key, out AvaKey avaKey)
|
||||
{
|
||||
avaKey = AvaKey.None;
|
||||
|
||||
bool keyExist = (int)key < _keyMapping.Length;
|
||||
bool keyExist = key < Key.Count && (int)key < _keyMapping.Length;
|
||||
if (keyExist)
|
||||
{
|
||||
avaKey = _keyMapping[(int)key];
|
||||
@@ -178,9 +343,34 @@ namespace Ryujinx.Ava.Input
|
||||
return keyExist;
|
||||
}
|
||||
|
||||
public static bool TryGetAvaPhysicalKey(Key key, out AvaPhysicalKey avaPhysicalKey)
|
||||
{
|
||||
avaPhysicalKey = AvaPhysicalKey.None;
|
||||
|
||||
bool keyExist = key < Key.Count && (int)key < _physicalKeyMapping.Length;
|
||||
if (keyExist)
|
||||
{
|
||||
avaPhysicalKey = _physicalKeyMapping[(int)key];
|
||||
}
|
||||
|
||||
return keyExist;
|
||||
}
|
||||
|
||||
public static Key ToInputKey(AvaKey key)
|
||||
{
|
||||
return _avaKeyMapping.GetValueOrDefault(key, Key.Unknown);
|
||||
}
|
||||
|
||||
public static Key ToInputKey(AvaPhysicalKey key)
|
||||
{
|
||||
return _avaPhysicalKeyMapping.GetValueOrDefault(key, Key.Unknown);
|
||||
}
|
||||
|
||||
public static Key ToInputKey(AvaPhysicalKey physicalKey, AvaKey key)
|
||||
{
|
||||
Key inputKey = ToInputKey(key);
|
||||
|
||||
return inputKey != Key.Unknown ? inputKey : ToInputKey(physicalKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1304,6 +1304,11 @@ namespace Ryujinx.Ava.Systems
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_viewModel.IsActive)
|
||||
{
|
||||
_inputManager.KeyboardDriver.Clear();
|
||||
}
|
||||
|
||||
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
||||
|
||||
if (_viewModel.IsActive)
|
||||
|
||||
@@ -13,6 +13,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
using RyuLogger = Ryujinx.Common.Logging.Logger;
|
||||
|
||||
namespace Ryujinx.Ava.Systems.Configuration
|
||||
@@ -271,45 +273,45 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
Id = "0",
|
||||
PlayerIndex = PlayerIndex.Player1,
|
||||
ControllerType = ControllerType.ProController,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
|
||||
{
|
||||
DpadUp = Key.Up,
|
||||
DpadDown = Key.Down,
|
||||
DpadLeft = Key.Left,
|
||||
DpadRight = Key.Right,
|
||||
ButtonMinus = Key.Minus,
|
||||
ButtonL = Key.E,
|
||||
ButtonZl = Key.Q,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
DpadUp = PhysicalKey.Up,
|
||||
DpadDown = PhysicalKey.Down,
|
||||
DpadLeft = PhysicalKey.Left,
|
||||
DpadRight = PhysicalKey.Right,
|
||||
ButtonMinus = PhysicalKey.Minus,
|
||||
ButtonL = PhysicalKey.E,
|
||||
ButtonZl = PhysicalKey.Q,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
{
|
||||
StickUp = Key.W,
|
||||
StickDown = Key.S,
|
||||
StickLeft = Key.A,
|
||||
StickRight = Key.D,
|
||||
StickButton = Key.F,
|
||||
StickUp = PhysicalKey.W,
|
||||
StickDown = PhysicalKey.S,
|
||||
StickLeft = PhysicalKey.A,
|
||||
StickRight = PhysicalKey.D,
|
||||
StickButton = PhysicalKey.F,
|
||||
},
|
||||
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
|
||||
{
|
||||
ButtonA = Key.Z,
|
||||
ButtonB = Key.X,
|
||||
ButtonX = Key.C,
|
||||
ButtonY = Key.V,
|
||||
ButtonPlus = Key.Plus,
|
||||
ButtonR = Key.U,
|
||||
ButtonZr = Key.O,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
ButtonA = PhysicalKey.Z,
|
||||
ButtonB = PhysicalKey.X,
|
||||
ButtonX = PhysicalKey.C,
|
||||
ButtonY = PhysicalKey.V,
|
||||
ButtonPlus = PhysicalKey.Plus,
|
||||
ButtonR = PhysicalKey.U,
|
||||
ButtonZr = PhysicalKey.O,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
{
|
||||
StickUp = Key.I,
|
||||
StickDown = Key.K,
|
||||
StickLeft = Key.J,
|
||||
StickRight = Key.L,
|
||||
StickButton = Key.H,
|
||||
StickUp = PhysicalKey.I,
|
||||
StickDown = PhysicalKey.K,
|
||||
StickLeft = PhysicalKey.J,
|
||||
StickRight = PhysicalKey.L,
|
||||
StickButton = PhysicalKey.H,
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
@@ -8,6 +8,8 @@ using Ryujinx.Graphics.Vulkan;
|
||||
using Ryujinx.HLE;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
|
||||
namespace Ryujinx.Ava.Systems.Configuration
|
||||
{
|
||||
@@ -288,45 +290,45 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
Name = "Keyboard",
|
||||
PlayerIndex = PlayerIndex.Player1,
|
||||
ControllerType = ControllerType.ProController,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
|
||||
{
|
||||
DpadUp = Key.Up,
|
||||
DpadDown = Key.Down,
|
||||
DpadLeft = Key.Left,
|
||||
DpadRight = Key.Right,
|
||||
ButtonMinus = Key.Minus,
|
||||
ButtonL = Key.E,
|
||||
ButtonZl = Key.Q,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
DpadUp = PhysicalKey.Up,
|
||||
DpadDown = PhysicalKey.Down,
|
||||
DpadLeft = PhysicalKey.Left,
|
||||
DpadRight = PhysicalKey.Right,
|
||||
ButtonMinus = PhysicalKey.Minus,
|
||||
ButtonL = PhysicalKey.E,
|
||||
ButtonZl = PhysicalKey.Q,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
{
|
||||
StickUp = Key.W,
|
||||
StickDown = Key.S,
|
||||
StickLeft = Key.A,
|
||||
StickRight = Key.D,
|
||||
StickButton = Key.F,
|
||||
StickUp = PhysicalKey.W,
|
||||
StickDown = PhysicalKey.S,
|
||||
StickLeft = PhysicalKey.A,
|
||||
StickRight = PhysicalKey.D,
|
||||
StickButton = PhysicalKey.F,
|
||||
},
|
||||
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
|
||||
{
|
||||
ButtonA = Key.Z,
|
||||
ButtonB = Key.X,
|
||||
ButtonX = Key.C,
|
||||
ButtonY = Key.V,
|
||||
ButtonPlus = Key.Plus,
|
||||
ButtonR = Key.U,
|
||||
ButtonZr = Key.O,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
ButtonA = PhysicalKey.Z,
|
||||
ButtonB = PhysicalKey.X,
|
||||
ButtonX = PhysicalKey.C,
|
||||
ButtonY = PhysicalKey.V,
|
||||
ButtonPlus = PhysicalKey.Plus,
|
||||
ButtonR = PhysicalKey.U,
|
||||
ButtonZr = PhysicalKey.O,
|
||||
ButtonSl = PhysicalKey.Unbound,
|
||||
ButtonSr = PhysicalKey.Unbound,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
{
|
||||
StickUp = Key.I,
|
||||
StickDown = Key.K,
|
||||
StickLeft = Key.J,
|
||||
StickRight = Key.L,
|
||||
StickButton = Key.H,
|
||||
StickUp = PhysicalKey.I,
|
||||
StickDown = PhysicalKey.K,
|
||||
StickLeft = PhysicalKey.J,
|
||||
StickRight = PhysicalKey.L,
|
||||
StickButton = PhysicalKey.H,
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
class AvaloniaDynamicTextInputHandler : IDynamicTextInputHandler
|
||||
{
|
||||
private MainWindow _parent;
|
||||
private AvaloniaKeyboardDriver _avaloniaKeyboardDriver;
|
||||
private readonly OffscreenTextBox _hiddenTextBox;
|
||||
private bool _canProcessInput;
|
||||
private IDisposable _textChangedSubscription;
|
||||
@@ -27,6 +28,7 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
|
||||
if (_parent.InputManager.KeyboardDriver is AvaloniaKeyboardDriver avaloniaKeyboardDriver)
|
||||
{
|
||||
_avaloniaKeyboardDriver = avaloniaKeyboardDriver;
|
||||
avaloniaKeyboardDriver.KeyPressed += AvaloniaDynamicTextInputHandler_KeyPressed;
|
||||
avaloniaKeyboardDriver.KeyRelease += AvaloniaDynamicTextInputHandler_KeyRelease;
|
||||
avaloniaKeyboardDriver.TextInput += AvaloniaDynamicTextInputHandler_TextInput;
|
||||
@@ -65,7 +67,7 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
|
||||
private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e)
|
||||
{
|
||||
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
|
||||
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.PhysicalKey, e.Key);
|
||||
|
||||
if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true))
|
||||
{
|
||||
@@ -85,7 +87,7 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
|
||||
private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e)
|
||||
{
|
||||
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
|
||||
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.PhysicalKey, e.Key);
|
||||
|
||||
if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true))
|
||||
{
|
||||
@@ -115,11 +117,11 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_parent.InputManager.KeyboardDriver is AvaloniaKeyboardDriver avaloniaKeyboardDriver)
|
||||
if (_avaloniaKeyboardDriver != null)
|
||||
{
|
||||
avaloniaKeyboardDriver.KeyPressed -= AvaloniaDynamicTextInputHandler_KeyPressed;
|
||||
avaloniaKeyboardDriver.KeyRelease -= AvaloniaDynamicTextInputHandler_KeyRelease;
|
||||
avaloniaKeyboardDriver.TextInput -= AvaloniaDynamicTextInputHandler_TextInput;
|
||||
_avaloniaKeyboardDriver.KeyPressed -= AvaloniaDynamicTextInputHandler_KeyPressed;
|
||||
_avaloniaKeyboardDriver.KeyRelease -= AvaloniaDynamicTextInputHandler_KeyRelease;
|
||||
_avaloniaKeyboardDriver.TextInput -= AvaloniaDynamicTextInputHandler_TextInput;
|
||||
}
|
||||
|
||||
_textChangedSubscription?.Dispose();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Input;
|
||||
using Ryujinx.Input.Assigner;
|
||||
using System;
|
||||
@@ -25,6 +26,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
|
||||
private bool _isWaitingForInput;
|
||||
private bool _shouldUnbind;
|
||||
private IKeyboard _keyboard;
|
||||
public event EventHandler<ButtonAssignedEventArgs> ButtonAssigned;
|
||||
|
||||
public ButtonKeyAssigner(ToggleButton toggleButton)
|
||||
@@ -34,6 +36,9 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
|
||||
public async void GetInputAndAssign(IButtonAssigner assigner, IKeyboard keyboard = null)
|
||||
{
|
||||
_keyboard = keyboard;
|
||||
ClearKeyboardState(_keyboard);
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
ToggledButton.IsChecked = true;
|
||||
@@ -82,6 +87,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
_isWaitingForInput = false;
|
||||
|
||||
ToggledButton.IsChecked = false;
|
||||
ClearKeyboardState(_keyboard);
|
||||
|
||||
if (pressedButton.HasValue && pressedButton.Value.AsHidType<Key>() == Key.BackSpace)
|
||||
{
|
||||
@@ -98,6 +104,15 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
_isWaitingForInput = false;
|
||||
ToggledButton.IsChecked = false;
|
||||
_shouldUnbind = shouldUnbind;
|
||||
ClearKeyboardState(_keyboard);
|
||||
}
|
||||
|
||||
private static void ClearKeyboardState(IKeyboard keyboard)
|
||||
{
|
||||
if (keyboard is AvaloniaKeyboard avaloniaKeyboard)
|
||||
{
|
||||
avaloniaKeyboard.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Key = Ryujinx.Input.Key;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
@@ -12,79 +13,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
public static readonly KeyValueConverter Instance = new();
|
||||
|
||||
private static readonly Dictionary<Key, LocaleKeys> _keysMap = new()
|
||||
{
|
||||
{ Key.Unknown, LocaleKeys.KeyUnknown },
|
||||
{ Key.ShiftLeft, LocaleKeys.KeyShiftLeft },
|
||||
{ Key.ShiftRight, LocaleKeys.KeyShiftRight },
|
||||
{ Key.ControlLeft, LocaleKeys.KeyControlLeft },
|
||||
{ Key.ControlRight, LocaleKeys.KeyControlRight },
|
||||
{ Key.AltLeft, LocaleKeys.KeyAltLeft },
|
||||
{ Key.AltRight, LocaleKeys.KeyAltRight },
|
||||
{ Key.WinLeft, LocaleKeys.KeyWinLeft },
|
||||
{ Key.WinRight, LocaleKeys.KeyWinRight },
|
||||
{ Key.Up, LocaleKeys.KeyUp },
|
||||
{ Key.Down, LocaleKeys.KeyDown },
|
||||
{ Key.Left, LocaleKeys.KeyLeft },
|
||||
{ Key.Right, LocaleKeys.KeyRight },
|
||||
{ Key.Enter, LocaleKeys.KeyEnter },
|
||||
{ Key.Escape, LocaleKeys.KeyEscape },
|
||||
{ Key.Space, LocaleKeys.KeySpace },
|
||||
{ Key.Tab, LocaleKeys.KeyTab },
|
||||
{ Key.BackSpace, LocaleKeys.KeyBackSpace },
|
||||
{ Key.Insert, LocaleKeys.KeyInsert },
|
||||
{ Key.Delete, LocaleKeys.KeyDelete },
|
||||
{ Key.PageUp, LocaleKeys.KeyPageUp },
|
||||
{ Key.PageDown, LocaleKeys.KeyPageDown },
|
||||
{ Key.Home, LocaleKeys.KeyHome },
|
||||
{ Key.End, LocaleKeys.KeyEnd },
|
||||
{ Key.CapsLock, LocaleKeys.KeyCapsLock },
|
||||
{ Key.ScrollLock, LocaleKeys.KeyScrollLock },
|
||||
{ Key.PrintScreen, LocaleKeys.KeyPrintScreen },
|
||||
{ Key.Pause, LocaleKeys.KeyPause },
|
||||
{ Key.NumLock, LocaleKeys.KeyNumLock },
|
||||
{ Key.Clear, LocaleKeys.KeyClear },
|
||||
{ Key.Keypad0, LocaleKeys.KeyKeypad0 },
|
||||
{ Key.Keypad1, LocaleKeys.KeyKeypad1 },
|
||||
{ Key.Keypad2, LocaleKeys.KeyKeypad2 },
|
||||
{ Key.Keypad3, LocaleKeys.KeyKeypad3 },
|
||||
{ Key.Keypad4, LocaleKeys.KeyKeypad4 },
|
||||
{ Key.Keypad5, LocaleKeys.KeyKeypad5 },
|
||||
{ Key.Keypad6, LocaleKeys.KeyKeypad6 },
|
||||
{ Key.Keypad7, LocaleKeys.KeyKeypad7 },
|
||||
{ Key.Keypad8, LocaleKeys.KeyKeypad8 },
|
||||
{ Key.Keypad9, LocaleKeys.KeyKeypad9 },
|
||||
{ Key.KeypadDivide, LocaleKeys.KeyKeypadDivide },
|
||||
{ Key.KeypadMultiply, LocaleKeys.KeyKeypadMultiply },
|
||||
{ Key.KeypadSubtract, LocaleKeys.KeyKeypadSubtract },
|
||||
{ Key.KeypadAdd, LocaleKeys.KeyKeypadAdd },
|
||||
{ Key.KeypadDecimal, LocaleKeys.KeyKeypadDecimal },
|
||||
{ Key.KeypadEnter, LocaleKeys.KeyKeypadEnter },
|
||||
{ Key.Number0, LocaleKeys.KeyNumber0 },
|
||||
{ Key.Number1, LocaleKeys.KeyNumber1 },
|
||||
{ Key.Number2, LocaleKeys.KeyNumber2 },
|
||||
{ Key.Number3, LocaleKeys.KeyNumber3 },
|
||||
{ Key.Number4, LocaleKeys.KeyNumber4 },
|
||||
{ Key.Number5, LocaleKeys.KeyNumber5 },
|
||||
{ Key.Number6, LocaleKeys.KeyNumber6 },
|
||||
{ Key.Number7, LocaleKeys.KeyNumber7 },
|
||||
{ Key.Number8, LocaleKeys.KeyNumber8 },
|
||||
{ Key.Number9, LocaleKeys.KeyNumber9 },
|
||||
{ Key.Tilde, LocaleKeys.KeyTilde },
|
||||
{ Key.Grave, LocaleKeys.KeyGrave },
|
||||
{ Key.Minus, LocaleKeys.KeyMinus },
|
||||
{ Key.Plus, LocaleKeys.KeyPlus },
|
||||
{ Key.BracketLeft, LocaleKeys.KeyBracketLeft },
|
||||
{ Key.BracketRight, LocaleKeys.KeyBracketRight },
|
||||
{ Key.Semicolon, LocaleKeys.KeySemicolon },
|
||||
{ Key.Quote, LocaleKeys.KeyQuote },
|
||||
{ Key.Comma, LocaleKeys.KeyComma },
|
||||
{ Key.Period, LocaleKeys.KeyPeriod },
|
||||
{ Key.Slash, LocaleKeys.KeySlash },
|
||||
{ Key.BackSlash, LocaleKeys.KeyBackSlash },
|
||||
{ Key.Unbound, LocaleKeys.KeyUnbound },
|
||||
};
|
||||
|
||||
private static readonly Dictionary<GamepadInputId, LocaleKeys> _gamepadInputIdMap = new()
|
||||
{
|
||||
{ GamepadInputId.LeftStick, LocaleKeys.GamepadLeftStick },
|
||||
@@ -110,78 +38,40 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
{ GamepadInputId.SingleRightTrigger0, LocaleKeys.GamepadSingleRightTrigger0},
|
||||
{ GamepadInputId.SingleLeftTrigger1, LocaleKeys.GamepadSingleLeftTrigger1},
|
||||
{ GamepadInputId.SingleRightTrigger1, LocaleKeys.GamepadSingleRightTrigger1},
|
||||
{ GamepadInputId.Unbound, LocaleKeys.KeyUnbound},
|
||||
{ GamepadInputId.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound},
|
||||
};
|
||||
|
||||
private static readonly Dictionary<StickInputId, LocaleKeys> _stickInputIdMap = new()
|
||||
{
|
||||
{ StickInputId.Left, LocaleKeys.StickLeft},
|
||||
{ StickInputId.Right, LocaleKeys.StickRight},
|
||||
{ StickInputId.Unbound, LocaleKeys.KeyUnbound},
|
||||
{ StickInputId.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound},
|
||||
};
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
string keyString = string.Empty;
|
||||
LocaleKeys localeKey;
|
||||
|
||||
switch (value)
|
||||
return value switch
|
||||
{
|
||||
case Key key:
|
||||
if (_keysMap.TryGetValue(key, out localeKey))
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
localeKey = localeKey switch
|
||||
{
|
||||
LocaleKeys.KeyControlLeft => LocaleKeys.KeyMacControlLeft,
|
||||
LocaleKeys.KeyControlRight => LocaleKeys.KeyMacControlRight,
|
||||
LocaleKeys.KeyAltLeft => LocaleKeys.KeyMacAltLeft,
|
||||
LocaleKeys.KeyAltRight => LocaleKeys.KeyMacAltRight,
|
||||
LocaleKeys.KeyWinLeft => LocaleKeys.KeyMacWinLeft,
|
||||
LocaleKeys.KeyWinRight => LocaleKeys.KeyMacWinRight,
|
||||
_ => localeKey
|
||||
};
|
||||
}
|
||||
|
||||
keyString = LocaleManager.Instance[localeKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
keyString = key.ToString();
|
||||
}
|
||||
|
||||
break;
|
||||
case GamepadInputId gamepadInputId:
|
||||
if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey))
|
||||
{
|
||||
keyString = LocaleManager.Instance[localeKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
keyString = gamepadInputId.ToString();
|
||||
}
|
||||
|
||||
break;
|
||||
case StickInputId stickInputId:
|
||||
if (_stickInputIdMap.TryGetValue(stickInputId, out localeKey))
|
||||
{
|
||||
keyString = LocaleManager.Instance[localeKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
keyString = stickInputId.ToString();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return keyString;
|
||||
Key key => KeyboardLayoutLocaleHelper.TryGetSemanticLabel(key, out string localizedKeyLabel)
|
||||
? localizedKeyLabel
|
||||
: key.ToString(),
|
||||
PhysicalKey physicalKey => PhysicalKeyLabelHelper.GetDisplayString(physicalKey),
|
||||
GamepadInputId gamepadInputId => GetLocalizedMappedValue(gamepadInputId, _gamepadInputIdMap),
|
||||
StickInputId stickInputId => GetLocalizedMappedValue(stickInputId, _stickInputIdMap),
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private static string GetLocalizedMappedValue<T>(T value, IReadOnlyDictionary<T, LocaleKeys> map) where T : notnull
|
||||
{
|
||||
return map.TryGetValue(value, out LocaleKeys localeKey)
|
||||
? LocaleManager.Instance[localeKey]
|
||||
: value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
src/Ryujinx/UI/Helpers/InputDeviceNameConverter.cs
Normal file
28
src/Ryujinx/UI/Helpers/InputDeviceNameConverter.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
internal class InputDeviceNameConverter : MarkupExtension, IValueConverter
|
||||
{
|
||||
public static readonly InputDeviceNameConverter Instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value is ValueTuple<DeviceType, string, string> device ? device.Item3 : string.Empty;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
return Instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
142
src/Ryujinx/UI/Helpers/KeyboardLayoutLocaleHelper.cs
Normal file
142
src/Ryujinx/UI/Helpers/KeyboardLayoutLocaleHelper.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
using InputKey = Ryujinx.Input.Key;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
internal static class KeyboardLayoutLocaleHelper
|
||||
{
|
||||
private static readonly Dictionary<InputKey, LocaleKeys> _sharedLocalizedKeysMap = new()
|
||||
{
|
||||
[InputKey.Unknown] = LocaleKeys.KeyboardLayout_KeyUnknown,
|
||||
[InputKey.ShiftLeft] = LocaleKeys.KeyboardLayout_KeyShiftLeft,
|
||||
[InputKey.ShiftRight] = LocaleKeys.KeyboardLayout_KeyShiftRight,
|
||||
[InputKey.ControlLeft] = LocaleKeys.KeyboardLayout_KeyControlLeft,
|
||||
[InputKey.ControlRight] = LocaleKeys.KeyboardLayout_KeyControlRight,
|
||||
[InputKey.AltLeft] = LocaleKeys.KeyboardLayout_KeyAltLeft,
|
||||
[InputKey.AltRight] = LocaleKeys.KeyboardLayout_KeyAltRight,
|
||||
[InputKey.WinLeft] = LocaleKeys.KeyboardLayout_KeyWinLeft,
|
||||
[InputKey.WinRight] = LocaleKeys.KeyboardLayout_KeyWinRight,
|
||||
[InputKey.Up] = LocaleKeys.KeyboardLayout_KeyUp,
|
||||
[InputKey.Down] = LocaleKeys.KeyboardLayout_KeyDown,
|
||||
[InputKey.Left] = LocaleKeys.KeyboardLayout_KeyLeft,
|
||||
[InputKey.Right] = LocaleKeys.KeyboardLayout_KeyRight,
|
||||
[InputKey.Enter] = LocaleKeys.KeyboardLayout_KeyEnter,
|
||||
[InputKey.Escape] = LocaleKeys.KeyboardLayout_KeyEscape,
|
||||
[InputKey.Space] = LocaleKeys.KeyboardLayout_KeySpace,
|
||||
[InputKey.Tab] = LocaleKeys.KeyboardLayout_KeyTab,
|
||||
[InputKey.BackSpace] = LocaleKeys.KeyboardLayout_KeyBackSpace,
|
||||
[InputKey.Insert] = LocaleKeys.KeyboardLayout_KeyInsert,
|
||||
[InputKey.Delete] = LocaleKeys.KeyboardLayout_KeyDelete,
|
||||
[InputKey.PageUp] = LocaleKeys.KeyboardLayout_KeyPageUp,
|
||||
[InputKey.PageDown] = LocaleKeys.KeyboardLayout_KeyPageDown,
|
||||
[InputKey.Home] = LocaleKeys.KeyboardLayout_KeyHome,
|
||||
[InputKey.End] = LocaleKeys.KeyboardLayout_KeyEnd,
|
||||
[InputKey.CapsLock] = LocaleKeys.KeyboardLayout_KeyCapsLock,
|
||||
[InputKey.ScrollLock] = LocaleKeys.KeyboardLayout_KeyScrollLock,
|
||||
[InputKey.PrintScreen] = LocaleKeys.KeyboardLayout_KeyPrintScreen,
|
||||
[InputKey.Pause] = LocaleKeys.KeyboardLayout_KeyPause,
|
||||
[InputKey.NumLock] = LocaleKeys.KeyboardLayout_KeyNumLock,
|
||||
[InputKey.Clear] = LocaleKeys.KeyboardLayout_KeyClear,
|
||||
[InputKey.Keypad0] = LocaleKeys.KeyboardLayout_KeyKeypad0,
|
||||
[InputKey.Keypad1] = LocaleKeys.KeyboardLayout_KeyKeypad1,
|
||||
[InputKey.Keypad2] = LocaleKeys.KeyboardLayout_KeyKeypad2,
|
||||
[InputKey.Keypad3] = LocaleKeys.KeyboardLayout_KeyKeypad3,
|
||||
[InputKey.Keypad4] = LocaleKeys.KeyboardLayout_KeyKeypad4,
|
||||
[InputKey.Keypad5] = LocaleKeys.KeyboardLayout_KeyKeypad5,
|
||||
[InputKey.Keypad6] = LocaleKeys.KeyboardLayout_KeyKeypad6,
|
||||
[InputKey.Keypad7] = LocaleKeys.KeyboardLayout_KeyKeypad7,
|
||||
[InputKey.Keypad8] = LocaleKeys.KeyboardLayout_KeyKeypad8,
|
||||
[InputKey.Keypad9] = LocaleKeys.KeyboardLayout_KeyKeypad9,
|
||||
[InputKey.KeypadDivide] = LocaleKeys.KeyboardLayout_KeyKeypadDivide,
|
||||
[InputKey.KeypadMultiply] = LocaleKeys.KeyboardLayout_KeyKeypadMultiply,
|
||||
[InputKey.KeypadSubtract] = LocaleKeys.KeyboardLayout_KeyKeypadSubtract,
|
||||
[InputKey.KeypadAdd] = LocaleKeys.KeyboardLayout_KeyKeypadAdd,
|
||||
[InputKey.KeypadDecimal] = LocaleKeys.KeyboardLayout_KeyKeypadDecimal,
|
||||
[InputKey.KeypadEnter] = LocaleKeys.KeyboardLayout_KeyKeypadEnter,
|
||||
[InputKey.Unbound] = LocaleKeys.KeyboardLayout_KeyUnbound,
|
||||
};
|
||||
|
||||
private static readonly Dictionary<InputKey, LocaleKeys> _semanticPrintableKeysMap = new()
|
||||
{
|
||||
[InputKey.Number0] = LocaleKeys.KeyboardLayout_KeyNumber0,
|
||||
[InputKey.Number1] = LocaleKeys.KeyboardLayout_KeyNumber1,
|
||||
[InputKey.Number2] = LocaleKeys.KeyboardLayout_KeyNumber2,
|
||||
[InputKey.Number3] = LocaleKeys.KeyboardLayout_KeyNumber3,
|
||||
[InputKey.Number4] = LocaleKeys.KeyboardLayout_KeyNumber4,
|
||||
[InputKey.Number5] = LocaleKeys.KeyboardLayout_KeyNumber5,
|
||||
[InputKey.Number6] = LocaleKeys.KeyboardLayout_KeyNumber6,
|
||||
[InputKey.Number7] = LocaleKeys.KeyboardLayout_KeyNumber7,
|
||||
[InputKey.Number8] = LocaleKeys.KeyboardLayout_KeyNumber8,
|
||||
[InputKey.Number9] = LocaleKeys.KeyboardLayout_KeyNumber9,
|
||||
[InputKey.Tilde] = LocaleKeys.KeyboardLayout_KeyTilde,
|
||||
[InputKey.Grave] = LocaleKeys.KeyboardLayout_KeyGrave,
|
||||
[InputKey.Minus] = LocaleKeys.KeyboardLayout_KeyMinus,
|
||||
[InputKey.Plus] = LocaleKeys.KeyboardLayout_KeyPlus,
|
||||
[InputKey.BracketLeft] = LocaleKeys.KeyboardLayout_KeyBracketLeft,
|
||||
[InputKey.BracketRight] = LocaleKeys.KeyboardLayout_KeyBracketRight,
|
||||
[InputKey.Semicolon] = LocaleKeys.KeyboardLayout_KeySemicolon,
|
||||
[InputKey.Quote] = LocaleKeys.KeyboardLayout_KeyQuote,
|
||||
[InputKey.Comma] = LocaleKeys.KeyboardLayout_KeyComma,
|
||||
[InputKey.Period] = LocaleKeys.KeyboardLayout_KeyPeriod,
|
||||
[InputKey.Slash] = LocaleKeys.KeyboardLayout_KeySlash,
|
||||
[InputKey.BackSlash] = LocaleKeys.KeyboardLayout_KeyBackSlash,
|
||||
};
|
||||
|
||||
public static bool TryGetSemanticLabel(InputKey key, out string label)
|
||||
{
|
||||
if (TryGetSemanticLocaleKey(key, out LocaleKeys localeKey))
|
||||
{
|
||||
label = GetLocalizedString(localeKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
label = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetPhysicalLabel(ConfigPhysicalKey key, out string label)
|
||||
{
|
||||
if (TryGetPhysicalLocaleKey(key, out LocaleKeys localeKey))
|
||||
{
|
||||
label = GetLocalizedString(localeKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
label = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetPhysicalLocaleKey(ConfigPhysicalKey key, out LocaleKeys localeKey)
|
||||
{
|
||||
return _sharedLocalizedKeysMap.TryGetValue((InputKey)(int)key, out localeKey);
|
||||
}
|
||||
|
||||
private static bool TryGetSemanticLocaleKey(InputKey key, out LocaleKeys localeKey)
|
||||
{
|
||||
return _sharedLocalizedKeysMap.TryGetValue(key, out localeKey) ||
|
||||
_semanticPrintableKeysMap.TryGetValue(key, out localeKey);
|
||||
}
|
||||
|
||||
private static string GetLocalizedString(LocaleKeys localeKey)
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
localeKey = localeKey switch
|
||||
{
|
||||
LocaleKeys.KeyboardLayout_KeyControlLeft => LocaleKeys.KeyboardLayout_KeyMacControlLeft,
|
||||
LocaleKeys.KeyboardLayout_KeyControlRight => LocaleKeys.KeyboardLayout_KeyMacControlRight,
|
||||
LocaleKeys.KeyboardLayout_KeyAltLeft => LocaleKeys.KeyboardLayout_KeyMacAltLeft,
|
||||
LocaleKeys.KeyboardLayout_KeyAltRight => LocaleKeys.KeyboardLayout_KeyMacAltRight,
|
||||
LocaleKeys.KeyboardLayout_KeyWinLeft => LocaleKeys.KeyboardLayout_KeyMacWinLeft,
|
||||
LocaleKeys.KeyboardLayout_KeyWinRight => LocaleKeys.KeyboardLayout_KeyMacWinRight,
|
||||
_ => localeKey
|
||||
};
|
||||
}
|
||||
|
||||
return LocaleManager.Instance[localeKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
234
src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs
Normal file
234
src/Ryujinx/UI/Helpers/PhysicalKeyLabelHelper.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using Avalonia.Input;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AvaPhysicalKey = Avalonia.Input.PhysicalKey;
|
||||
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
using InputKey = Ryujinx.Input.Key;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
internal static class PhysicalKeyLabelHelper
|
||||
{
|
||||
private const string ObservedLabelsFileName = "keyboard_layout_labels.json";
|
||||
private static readonly ConcurrentDictionary<ConfigPhysicalKey, string> _observedLayoutLabels = new();
|
||||
private static readonly object _observedLayoutLabelsLock = new();
|
||||
private static readonly JsonSerializerOptions _serializerOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
private static bool _observedLayoutLabelsLoaded;
|
||||
public static event Action LabelsChanged;
|
||||
|
||||
public static string GetDisplayString(ConfigPhysicalKey key)
|
||||
{
|
||||
EnsureObservedLayoutLabelsLoaded();
|
||||
|
||||
if (KeyboardLayoutLocaleHelper.TryGetPhysicalLabel(key, out string localizedLabel))
|
||||
{
|
||||
return localizedLabel;
|
||||
}
|
||||
|
||||
if (_observedLayoutLabels.TryGetValue(key, out string observedLabel))
|
||||
{
|
||||
return observedLabel;
|
||||
}
|
||||
|
||||
if (TryGetFallbackPrintableKeyLabel(key, out string label))
|
||||
{
|
||||
return label;
|
||||
}
|
||||
|
||||
return key.ToString();
|
||||
}
|
||||
|
||||
public static void ObserveKeyPress(object sender, KeyEventArgs args)
|
||||
{
|
||||
EnsureObservedLayoutLabelsLoaded();
|
||||
|
||||
if (args.KeyModifiers != KeyModifiers.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InputKey inputKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey);
|
||||
if (!TryConvertToConfigPhysicalKey(inputKey, out ConfigPhysicalKey physicalKey) ||
|
||||
KeyboardLayoutLocaleHelper.TryGetPhysicalLocaleKey(physicalKey, out _))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryNormalizeObservedPrintableLabel(args.KeySymbol, out string label))
|
||||
{
|
||||
if (IsCapsLockOn() && !char.IsLetter(label[0]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_observedLayoutLabels.TryGetValue(physicalKey, out string existingLabel) && existingLabel == label)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_observedLayoutLabels[physicalKey] = label;
|
||||
SaveObservedLayoutLabels();
|
||||
LabelsChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureObservedLayoutLabelsLoaded()
|
||||
{
|
||||
if (_observedLayoutLabelsLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_observedLayoutLabelsLock)
|
||||
{
|
||||
if (_observedLayoutLabelsLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string labelsPath = GetObservedLabelsPath();
|
||||
if (!File.Exists(labelsPath))
|
||||
{
|
||||
_observedLayoutLabelsLoaded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string labelsJson = File.ReadAllText(labelsPath);
|
||||
Dictionary<string, string>? labels = JsonSerializer.Deserialize<Dictionary<string, string>>(labelsJson, _serializerOptions);
|
||||
|
||||
if (labels != null)
|
||||
{
|
||||
foreach ((string key, string value) in labels)
|
||||
{
|
||||
if (Enum.TryParse(key, out ConfigPhysicalKey physicalKey) &&
|
||||
!string.IsNullOrEmpty(value))
|
||||
{
|
||||
_observedLayoutLabels[physicalKey] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.UI, $"Unable to load observed keyboard layout labels from '{labelsPath}': {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_observedLayoutLabelsLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveObservedLayoutLabels()
|
||||
{
|
||||
lock (_observedLayoutLabelsLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dictionary<string, string> labels = new();
|
||||
|
||||
foreach ((ConfigPhysicalKey key, string value) in _observedLayoutLabels)
|
||||
{
|
||||
labels[key.ToString()] = value;
|
||||
}
|
||||
|
||||
File.WriteAllText(GetObservedLabelsPath(), JsonSerializer.Serialize(labels, _serializerOptions));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.UI, $"Unable to save observed keyboard layout labels: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetObservedLabelsPath()
|
||||
{
|
||||
return Path.Combine(AppDataManager.BaseDirPath, ObservedLabelsFileName);
|
||||
}
|
||||
|
||||
private static bool TryGetFallbackPrintableKeyLabel(ConfigPhysicalKey key, out string label)
|
||||
{
|
||||
// The legacy enum name for the ISO extra key is misleading, so give it a distinct physical label.
|
||||
if (key == ConfigPhysicalKey.Grave)
|
||||
{
|
||||
label = "<>";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!AvaloniaKeyboardMappingHelper.TryGetAvaPhysicalKey((InputKey)(int)key, out AvaPhysicalKey avaPhysicalKey))
|
||||
{
|
||||
label = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
label = PhysicalKeyExtensions.ToQwertyKeySymbol(avaPhysicalKey, false);
|
||||
|
||||
if (string.IsNullOrEmpty(label) || label.Length != 1 || char.IsControl(label[0]))
|
||||
{
|
||||
label = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (char.IsLetter(label[0]))
|
||||
{
|
||||
label = char.ToUpperInvariant(label[0]).ToString();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsCapsLockOn()
|
||||
{
|
||||
try
|
||||
{
|
||||
return OperatingSystem.IsWindows() && Console.CapsLock;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.UI, $"CapsLock state query failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryNormalizeObservedPrintableLabel(string keySymbol, out string label)
|
||||
{
|
||||
if (string.IsNullOrEmpty(keySymbol) || keySymbol.Length != 1 || char.IsControl(keySymbol[0]))
|
||||
{
|
||||
label = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
label = char.IsLetter(keySymbol[0])
|
||||
? char.ToUpperInvariant(keySymbol[0]).ToString()
|
||||
: keySymbol;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryConvertToConfigPhysicalKey(InputKey key, out ConfigPhysicalKey physicalKey)
|
||||
{
|
||||
if (key is >= InputKey.Unknown and < InputKey.Count)
|
||||
{
|
||||
physicalKey = (ConfigPhysicalKey)(int)key;
|
||||
return true;
|
||||
}
|
||||
|
||||
physicalKey = ConfigPhysicalKey.Unknown;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,88 +13,88 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
public PlayerIndex PlayerIndex { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key LeftStickUp { get; set; }
|
||||
public partial PhysicalKey LeftStickUp { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key LeftStickDown { get; set; }
|
||||
public partial PhysicalKey LeftStickDown { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key LeftStickLeft { get; set; }
|
||||
public partial PhysicalKey LeftStickLeft { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key LeftStickRight { get; set; }
|
||||
public partial PhysicalKey LeftStickRight { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key LeftStickButton { get; set; }
|
||||
public partial PhysicalKey LeftStickButton { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key RightStickUp { get; set; }
|
||||
public partial PhysicalKey RightStickUp { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key RightStickDown { get; set; }
|
||||
public partial PhysicalKey RightStickDown { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key RightStickLeft { get; set; }
|
||||
public partial PhysicalKey RightStickLeft { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key RightStickRight { get; set; }
|
||||
public partial PhysicalKey RightStickRight { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key RightStickButton { get; set; }
|
||||
public partial PhysicalKey RightStickButton { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key DpadUp { get; set; }
|
||||
public partial PhysicalKey DpadUp { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key DpadDown { get; set; }
|
||||
public partial PhysicalKey DpadDown { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key DpadLeft { get; set; }
|
||||
public partial PhysicalKey DpadLeft { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key DpadRight { get; set; }
|
||||
public partial PhysicalKey DpadRight { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key ButtonMinus { get; set; }
|
||||
public partial PhysicalKey ButtonMinus { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key ButtonPlus { get; set; }
|
||||
public partial PhysicalKey ButtonPlus { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key ButtonA { get; set; }
|
||||
public partial PhysicalKey ButtonA { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key ButtonB { get; set; }
|
||||
public partial PhysicalKey ButtonB { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key ButtonX { get; set; }
|
||||
public partial PhysicalKey ButtonX { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key ButtonY { get; set; }
|
||||
public partial PhysicalKey ButtonY { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key ButtonL { get; set; }
|
||||
public partial PhysicalKey ButtonL { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key ButtonR { get; set; }
|
||||
public partial PhysicalKey ButtonR { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key ButtonZl { get; set; }
|
||||
public partial PhysicalKey ButtonZl { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key ButtonZr { get; set; }
|
||||
public partial PhysicalKey ButtonZr { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key LeftButtonSl { get; set; }
|
||||
public partial PhysicalKey LeftButtonSl { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key LeftButtonSr { get; set; }
|
||||
public partial PhysicalKey LeftButtonSr { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key RightButtonSl { get; set; }
|
||||
public partial PhysicalKey RightButtonSl { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Key RightButtonSr { get; set; }
|
||||
public partial PhysicalKey RightButtonSr { get; set; }
|
||||
|
||||
public KeyboardInputConfig(InputConfig config)
|
||||
{
|
||||
@@ -153,7 +153,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
Backend = InputBackendType.WindowKeyboard,
|
||||
PlayerIndex = PlayerIndex,
|
||||
ControllerType = ControllerType,
|
||||
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
|
||||
{
|
||||
DpadUp = DpadUp,
|
||||
DpadDown = DpadDown,
|
||||
@@ -165,7 +165,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
ButtonSl = LeftButtonSl,
|
||||
ButtonSr = LeftButtonSr,
|
||||
},
|
||||
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
|
||||
{
|
||||
ButtonA = ButtonA,
|
||||
ButtonB = ButtonB,
|
||||
@@ -177,7 +177,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
ButtonR = ButtonR,
|
||||
ButtonZr = ButtonZr,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
{
|
||||
StickUp = LeftStickUp,
|
||||
StickDown = LeftStickDown,
|
||||
@@ -185,7 +185,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
StickLeft = LeftStickLeft,
|
||||
StickButton = LeftStickButton,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
|
||||
{
|
||||
StickUp = RightStickUp,
|
||||
StickDown = RightStickDown,
|
||||
@@ -198,5 +198,37 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public void NotifyKeyLabelsChanged()
|
||||
{
|
||||
OnPropertiesChanged(nameof(LeftStickUp),
|
||||
nameof(LeftStickDown),
|
||||
nameof(LeftStickLeft),
|
||||
nameof(LeftStickRight),
|
||||
nameof(LeftStickButton),
|
||||
nameof(RightStickUp),
|
||||
nameof(RightStickDown),
|
||||
nameof(RightStickLeft),
|
||||
nameof(RightStickRight),
|
||||
nameof(RightStickButton),
|
||||
nameof(DpadUp),
|
||||
nameof(DpadDown),
|
||||
nameof(DpadLeft),
|
||||
nameof(DpadRight),
|
||||
nameof(ButtonMinus),
|
||||
nameof(ButtonPlus),
|
||||
nameof(ButtonA),
|
||||
nameof(ButtonB),
|
||||
nameof(ButtonX),
|
||||
nameof(ButtonY),
|
||||
nameof(ButtonL),
|
||||
nameof(ButtonR),
|
||||
nameof(ButtonZl),
|
||||
nameof(ButtonZr),
|
||||
nameof(LeftButtonSl),
|
||||
nameof(LeftButtonSr),
|
||||
nameof(RightButtonSl),
|
||||
nameof(RightButtonSr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.ViewModels.Input;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Threading;
|
||||
@@ -117,6 +118,11 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
|
||||
public void UpdateConfig(object config)
|
||||
{
|
||||
KeyboardConfig = null;
|
||||
GamepadConfig = null;
|
||||
UiStickLeft = (0f, 0f);
|
||||
UiStickRight = (0f, 0f);
|
||||
|
||||
if (config is ControllerInputViewModel padConfig)
|
||||
{
|
||||
GamepadConfig = padConfig.Config;
|
||||
@@ -145,76 +151,86 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
leftBuffer = (0f, 0f);
|
||||
rightBuffer = (0f, 0f);
|
||||
|
||||
switch (Type)
|
||||
try
|
||||
{
|
||||
case DeviceType.Keyboard:
|
||||
IKeyboard keyboard = (IKeyboard)Parent.AvaloniaKeyboardDriver.GetGamepad("0");
|
||||
switch (Type)
|
||||
{
|
||||
case DeviceType.Keyboard:
|
||||
IKeyboard keyboard = Parent?.AvaloniaKeyboardDriver?.GetGamepad("0") as IKeyboard;
|
||||
|
||||
if (keyboard != null)
|
||||
{
|
||||
KeyboardStateSnapshot snapshot = keyboard.GetKeyboardStateSnapshot();
|
||||
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickRight))
|
||||
if (keyboard != null && KeyboardConfig != null)
|
||||
{
|
||||
leftBuffer.Item1 += 1;
|
||||
KeyboardStateSnapshot snapshot = keyboard.GetKeyboardStateSnapshot();
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.LeftStickRight))
|
||||
{
|
||||
leftBuffer.Item1 += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.LeftStickLeft))
|
||||
{
|
||||
leftBuffer.Item1 -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.LeftStickUp))
|
||||
{
|
||||
leftBuffer.Item2 += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.LeftStickDown))
|
||||
{
|
||||
leftBuffer.Item2 -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.RightStickRight))
|
||||
{
|
||||
rightBuffer.Item1 += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.RightStickLeft))
|
||||
{
|
||||
rightBuffer.Item1 -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.RightStickUp))
|
||||
{
|
||||
rightBuffer.Item2 += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed(KeyboardConfig.RightStickDown))
|
||||
{
|
||||
rightBuffer.Item2 -= 1;
|
||||
}
|
||||
|
||||
UiStickLeft = leftBuffer;
|
||||
UiStickRight = rightBuffer;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickLeft))
|
||||
break;
|
||||
|
||||
case DeviceType.Controller:
|
||||
IGamepad controller = Parent?.SelectedGamepad;
|
||||
|
||||
if (controller is IKeyboard)
|
||||
{
|
||||
leftBuffer.Item1 -= 1;
|
||||
}
|
||||
else if (controller != null && GamepadConfig != null)
|
||||
{
|
||||
leftBuffer = controller.GetStick((StickInputId)GamepadConfig.LeftJoystick);
|
||||
rightBuffer = controller.GetStick((StickInputId)GamepadConfig.RightJoystick);
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickUp))
|
||||
{
|
||||
leftBuffer.Item2 += 1;
|
||||
}
|
||||
break;
|
||||
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickDown))
|
||||
{
|
||||
leftBuffer.Item2 -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickRight))
|
||||
{
|
||||
rightBuffer.Item1 += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickLeft))
|
||||
{
|
||||
rightBuffer.Item1 -= 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickUp))
|
||||
{
|
||||
rightBuffer.Item2 += 1;
|
||||
}
|
||||
|
||||
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickDown))
|
||||
{
|
||||
rightBuffer.Item2 -= 1;
|
||||
}
|
||||
|
||||
UiStickLeft = leftBuffer;
|
||||
UiStickRight = rightBuffer;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case DeviceType.Controller:
|
||||
IGamepad controller = Parent.SelectedGamepad;
|
||||
|
||||
if (controller != null)
|
||||
{
|
||||
leftBuffer = controller.GetStick((StickInputId)GamepadConfig.LeftJoystick);
|
||||
rightBuffer = controller.GetStick((StickInputId)GamepadConfig.RightJoystick);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case DeviceType.None:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unable to poll device type \"{Type}\"");
|
||||
case DeviceType.None:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unable to poll device type \"{Type}\"");
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is ObjectDisposedException || ex is NullReferenceException || ex is NotSupportedException)
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.UI, $"StickVisualizer polling failed: {ex}");
|
||||
}
|
||||
|
||||
UiStickLeft = leftBuffer;
|
||||
|
||||
@@ -45,7 +45,6 @@ namespace Ryujinx.Ava.UI.Renderer
|
||||
|
||||
Content = EmbeddedWindow;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (EmbeddedWindow != null)
|
||||
|
||||
@@ -88,19 +88,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
public async void ShowMotionConfig()
|
||||
{
|
||||
await MotionInputView.Show(this);
|
||||
ParentModel.IsModified = true;
|
||||
ParentModel.RefreshModifiedState();
|
||||
}
|
||||
|
||||
public async void ShowRumbleConfig()
|
||||
{
|
||||
await RumbleInputView.Show(this);
|
||||
ParentModel.IsModified = true;
|
||||
ParentModel.RefreshModifiedState();
|
||||
}
|
||||
|
||||
public async void ShowLedConfig()
|
||||
{
|
||||
await LedInputView.Show(this);
|
||||
ParentModel.IsModified = true;
|
||||
ParentModel.RefreshModifiedState();
|
||||
}
|
||||
|
||||
public void OnParentModelChanged()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -104,7 +104,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
|
||||
PointerPressed += MouseClick;
|
||||
|
||||
ControllerInputViewModel viewModel = (DataContext as ControllerInputViewModel);
|
||||
ControllerInputViewModel viewModel = ViewModel;
|
||||
|
||||
IKeyboard keyboard =
|
||||
(IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver
|
||||
@@ -113,10 +113,9 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
|
||||
_currentAssigner.ButtonAssigned += (sender, e) =>
|
||||
{
|
||||
if (e.ButtonValue.HasValue)
|
||||
if (e.ButtonValue.HasValue && IsActiveAssignmentContext(viewModel))
|
||||
{
|
||||
Button buttonValue = e.ButtonValue.Value;
|
||||
FlagInputConfigChanged();
|
||||
|
||||
switch (button.Name)
|
||||
{
|
||||
@@ -187,6 +186,8 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
viewModel.Config.RightJoystick = buttonValue.AsHidType<StickInputId>();
|
||||
break;
|
||||
}
|
||||
|
||||
FlagInputConfigChanged();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -212,7 +213,15 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
|
||||
private void FlagInputConfigChanged()
|
||||
{
|
||||
(DataContext as ControllerInputViewModel)!.ParentModel.IsModified = true;
|
||||
if (DataContext is ControllerInputViewModel viewModel && VisualRoot is not null)
|
||||
{
|
||||
viewModel.ParentModel.RefreshModifiedState();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsActiveAssignmentContext(ControllerInputViewModel viewModel)
|
||||
{
|
||||
return VisualRoot is not null && ReferenceEquals(DataContext, viewModel);
|
||||
}
|
||||
|
||||
private void MouseClick(object sender, PointerPressedEventArgs e)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
@@ -77,7 +78,7 @@
|
||||
ToolTip.Tip="{ext:Locale ControllerSettingsCancelCurrentChangesToolTip}"
|
||||
Command="{Binding RevertChanges}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Undo"
|
||||
Symbol="Cancel"
|
||||
FontSize="15"
|
||||
Height="20" />
|
||||
</Button>
|
||||
@@ -148,7 +149,7 @@
|
||||
<Grid
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*,Auto">
|
||||
HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="5,0,10,0"
|
||||
@@ -161,16 +162,35 @@
|
||||
Name="DeviceBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{Binding DeviceList}"
|
||||
SelectedIndex="{Binding Device}" />
|
||||
ItemsSource="{Binding Devices}"
|
||||
SelectedItem="{Binding SelectedDeviceItem, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ., Converter={x:Static helpers:InputDeviceNameConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding LoadDevice}">
|
||||
ToolTip.Tip="{ext:Locale ControllerSettingsRefresh}"
|
||||
Command="{Binding RefreshInputDevices}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Refresh"
|
||||
Symbol="Sync"
|
||||
FontSize="15"
|
||||
Height="20"/>
|
||||
</Button>
|
||||
<Button
|
||||
Grid.Column="3"
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{ext:Locale ControllerSettingsResetKeybindsToDefault}"
|
||||
Click="ResetCurrentDeviceToDefaultsButton_OnClick">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Undo"
|
||||
FontSize="15"
|
||||
Height="20"/>
|
||||
</Button>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Systems.Configuration;
|
||||
@@ -15,9 +19,14 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
|
||||
public InputView()
|
||||
{
|
||||
ViewModel = new InputViewModel(this, ConfigurationState.Instance.System.UseInputGlobalConfig);
|
||||
ReplaceViewModel(ConfigurationState.Instance.System.UseInputGlobalConfig);
|
||||
}
|
||||
|
||||
InitializeComponent();
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
|
||||
ViewModel?.RetargetKeyboardDriver(this);
|
||||
}
|
||||
|
||||
public void SaveCurrentProfile()
|
||||
@@ -28,8 +37,18 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
public void ToggleLocalGlobalInput(bool enableConfigGlobal)
|
||||
{
|
||||
Dispose();
|
||||
ViewModel = new InputViewModel(this, enableConfigGlobal); // Create new Input Page with global input configs
|
||||
ReplaceViewModel(enableConfigGlobal);
|
||||
}
|
||||
|
||||
private void ReplaceViewModel(bool useGlobalConfig)
|
||||
{
|
||||
ViewModel = new InputViewModel(this, useGlobalConfig); // Create new Input Page with the selected input config scope.
|
||||
InitializeComponent();
|
||||
|
||||
if (VisualRoot is not null)
|
||||
{
|
||||
ViewModel.RetargetKeyboardDriver(this);
|
||||
}
|
||||
}
|
||||
|
||||
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
@@ -83,7 +102,56 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
if (sender is FAComboBox faComboBox)
|
||||
{
|
||||
faComboBox.IsDropDownOpen = false;
|
||||
ViewModel.IsModified = true;
|
||||
ViewModel.RefreshModifiedState();
|
||||
}
|
||||
}
|
||||
|
||||
private async void ResetCurrentDeviceToDefaultsButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!ViewModel.NeedsResetCurrentDeviceToDefaultsConfirmation())
|
||||
{
|
||||
ViewModel.ResetCurrentDeviceToDefaults();
|
||||
return;
|
||||
}
|
||||
|
||||
Window owner = TopLevel.GetTopLevel(this) as Window;
|
||||
|
||||
StackPanel content = new()
|
||||
{
|
||||
Spacing = 4,
|
||||
MaxWidth = 360,
|
||||
};
|
||||
|
||||
content.Children.Add(new TextBlock
|
||||
{
|
||||
Text = LocaleManager.Instance[LocaleKeys.DialogControllerSettingsResetKeybindsConfirmMessage],
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
MaxWidth = 360,
|
||||
});
|
||||
|
||||
content.Children.Add(new TextBlock
|
||||
{
|
||||
Text = LocaleManager.Instance[LocaleKeys.DialogControllerSettingsResetKeybindsConfirmSubMessage],
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
MaxWidth = 360,
|
||||
});
|
||||
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.RyujinxConfirm],
|
||||
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
DefaultButton = ContentDialogButton.Primary,
|
||||
Content = content,
|
||||
}.ApplyStyles();
|
||||
|
||||
ContentDialogResult result = owner is not null
|
||||
? await contentDialog.ShowAsync(owner)
|
||||
: await ContentDialogHelper.ShowAsync(contentDialog);
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
{
|
||||
ViewModel.ResetCurrentDeviceToDefaults();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ using Ryujinx.Input.Assigner;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Button = Ryujinx.Input.Button;
|
||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Input
|
||||
{
|
||||
@@ -63,105 +63,108 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
|
||||
PointerPressed += MouseClick;
|
||||
|
||||
KeyboardInputViewModel viewModel = ViewModel;
|
||||
|
||||
IKeyboard keyboard =
|
||||
(IKeyboard)ViewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
|
||||
(IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
|
||||
IButtonAssigner assigner =
|
||||
new KeyboardKeyAssigner((IKeyboard)ViewModel.ParentModel.SelectedGamepad);
|
||||
new KeyboardKeyAssigner((IKeyboard)viewModel.ParentModel.SelectedGamepad);
|
||||
|
||||
_currentAssigner.ButtonAssigned += (_, be) =>
|
||||
{
|
||||
if (be.ButtonValue.HasValue)
|
||||
if (be.ButtonValue.HasValue && IsActiveAssignmentContext(viewModel))
|
||||
{
|
||||
Button buttonValue = be.ButtonValue.Value;
|
||||
ViewModel.ParentModel.IsModified = true;
|
||||
|
||||
switch (button.Name)
|
||||
{
|
||||
case "ButtonZl":
|
||||
ViewModel.Config.ButtonZl = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.ButtonZl = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "ButtonL":
|
||||
ViewModel.Config.ButtonL = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.ButtonL = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "ButtonMinus":
|
||||
ViewModel.Config.ButtonMinus = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.ButtonMinus = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "LeftStickButton":
|
||||
ViewModel.Config.LeftStickButton = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.LeftStickButton = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "LeftStickUp":
|
||||
ViewModel.Config.LeftStickUp = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.LeftStickUp = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "LeftStickDown":
|
||||
ViewModel.Config.LeftStickDown = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.LeftStickDown = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "LeftStickRight":
|
||||
ViewModel.Config.LeftStickRight = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.LeftStickRight = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "LeftStickLeft":
|
||||
ViewModel.Config.LeftStickLeft = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.LeftStickLeft = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "DpadUp":
|
||||
ViewModel.Config.DpadUp = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.DpadUp = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "DpadDown":
|
||||
ViewModel.Config.DpadDown = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.DpadDown = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "DpadLeft":
|
||||
ViewModel.Config.DpadLeft = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.DpadLeft = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "DpadRight":
|
||||
ViewModel.Config.DpadRight = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.DpadRight = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "LeftButtonSr":
|
||||
ViewModel.Config.LeftButtonSr = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.LeftButtonSr = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "LeftButtonSl":
|
||||
ViewModel.Config.LeftButtonSl = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.LeftButtonSl = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "RightButtonSr":
|
||||
ViewModel.Config.RightButtonSr = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.RightButtonSr = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "RightButtonSl":
|
||||
ViewModel.Config.RightButtonSl = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.RightButtonSl = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "ButtonZr":
|
||||
ViewModel.Config.ButtonZr = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.ButtonZr = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "ButtonR":
|
||||
ViewModel.Config.ButtonR = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.ButtonR = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "ButtonPlus":
|
||||
ViewModel.Config.ButtonPlus = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.ButtonPlus = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "ButtonA":
|
||||
ViewModel.Config.ButtonA = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.ButtonA = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "ButtonB":
|
||||
ViewModel.Config.ButtonB = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.ButtonB = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "ButtonX":
|
||||
ViewModel.Config.ButtonX = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.ButtonX = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "ButtonY":
|
||||
ViewModel.Config.ButtonY = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.ButtonY = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "RightStickButton":
|
||||
ViewModel.Config.RightStickButton = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.RightStickButton = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "RightStickUp":
|
||||
ViewModel.Config.RightStickUp = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.RightStickUp = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "RightStickDown":
|
||||
ViewModel.Config.RightStickDown = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.RightStickDown = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "RightStickRight":
|
||||
ViewModel.Config.RightStickRight = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.RightStickRight = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
case "RightStickLeft":
|
||||
ViewModel.Config.RightStickLeft = buttonValue.AsHidType<Key>();
|
||||
viewModel.Config.RightStickLeft = buttonValue.AsHidType<PhysicalKey>();
|
||||
break;
|
||||
}
|
||||
|
||||
viewModel.ParentModel.RefreshModifiedState();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -207,40 +210,40 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
{
|
||||
Dictionary<string, Action> buttonActions = new()
|
||||
{
|
||||
{ "ButtonZl", () => ViewModel.Config.ButtonZl = Key.Unbound },
|
||||
{ "ButtonL", () => ViewModel.Config.ButtonL = Key.Unbound },
|
||||
{ "ButtonMinus", () => ViewModel.Config.ButtonMinus = Key.Unbound },
|
||||
{ "LeftStickButton", () => ViewModel.Config.LeftStickButton = Key.Unbound },
|
||||
{ "LeftStickUp", () => ViewModel.Config.LeftStickUp = Key.Unbound },
|
||||
{ "LeftStickDown", () => ViewModel.Config.LeftStickDown = Key.Unbound },
|
||||
{ "LeftStickRight", () => ViewModel.Config.LeftStickRight = Key.Unbound },
|
||||
{ "LeftStickLeft", () => ViewModel.Config.LeftStickLeft = Key.Unbound },
|
||||
{ "DpadUp", () => ViewModel.Config.DpadUp = Key.Unbound },
|
||||
{ "DpadDown", () => ViewModel.Config.DpadDown = Key.Unbound },
|
||||
{ "DpadLeft", () => ViewModel.Config.DpadLeft = Key.Unbound },
|
||||
{ "DpadRight", () => ViewModel.Config.DpadRight = Key.Unbound },
|
||||
{ "LeftButtonSr", () => ViewModel.Config.LeftButtonSr = Key.Unbound },
|
||||
{ "LeftButtonSl", () => ViewModel.Config.LeftButtonSl = Key.Unbound },
|
||||
{ "RightButtonSr", () => ViewModel.Config.RightButtonSr = Key.Unbound },
|
||||
{ "RightButtonSl", () => ViewModel.Config.RightButtonSl = Key.Unbound },
|
||||
{ "ButtonZr", () => ViewModel.Config.ButtonZr = Key.Unbound },
|
||||
{ "ButtonR", () => ViewModel.Config.ButtonR = Key.Unbound },
|
||||
{ "ButtonPlus", () => ViewModel.Config.ButtonPlus = Key.Unbound },
|
||||
{ "ButtonA", () => ViewModel.Config.ButtonA = Key.Unbound },
|
||||
{ "ButtonB", () => ViewModel.Config.ButtonB = Key.Unbound },
|
||||
{ "ButtonX", () => ViewModel.Config.ButtonX = Key.Unbound },
|
||||
{ "ButtonY", () => ViewModel.Config.ButtonY = Key.Unbound },
|
||||
{ "RightStickButton", () => ViewModel.Config.RightStickButton = Key.Unbound },
|
||||
{ "RightStickUp", () => ViewModel.Config.RightStickUp = Key.Unbound },
|
||||
{ "RightStickDown", () => ViewModel.Config.RightStickDown = Key.Unbound },
|
||||
{ "RightStickRight", () => ViewModel.Config.RightStickRight = Key.Unbound },
|
||||
{ "RightStickLeft", () => ViewModel.Config.RightStickLeft = Key.Unbound }
|
||||
{ "ButtonZl", () => ViewModel.Config.ButtonZl = PhysicalKey.Unbound },
|
||||
{ "ButtonL", () => ViewModel.Config.ButtonL = PhysicalKey.Unbound },
|
||||
{ "ButtonMinus", () => ViewModel.Config.ButtonMinus = PhysicalKey.Unbound },
|
||||
{ "LeftStickButton", () => ViewModel.Config.LeftStickButton = PhysicalKey.Unbound },
|
||||
{ "LeftStickUp", () => ViewModel.Config.LeftStickUp = PhysicalKey.Unbound },
|
||||
{ "LeftStickDown", () => ViewModel.Config.LeftStickDown = PhysicalKey.Unbound },
|
||||
{ "LeftStickRight", () => ViewModel.Config.LeftStickRight = PhysicalKey.Unbound },
|
||||
{ "LeftStickLeft", () => ViewModel.Config.LeftStickLeft = PhysicalKey.Unbound },
|
||||
{ "DpadUp", () => ViewModel.Config.DpadUp = PhysicalKey.Unbound },
|
||||
{ "DpadDown", () => ViewModel.Config.DpadDown = PhysicalKey.Unbound },
|
||||
{ "DpadLeft", () => ViewModel.Config.DpadLeft = PhysicalKey.Unbound },
|
||||
{ "DpadRight", () => ViewModel.Config.DpadRight = PhysicalKey.Unbound },
|
||||
{ "LeftButtonSr", () => ViewModel.Config.LeftButtonSr = PhysicalKey.Unbound },
|
||||
{ "LeftButtonSl", () => ViewModel.Config.LeftButtonSl = PhysicalKey.Unbound },
|
||||
{ "RightButtonSr", () => ViewModel.Config.RightButtonSr = PhysicalKey.Unbound },
|
||||
{ "RightButtonSl", () => ViewModel.Config.RightButtonSl = PhysicalKey.Unbound },
|
||||
{ "ButtonZr", () => ViewModel.Config.ButtonZr = PhysicalKey.Unbound },
|
||||
{ "ButtonR", () => ViewModel.Config.ButtonR = PhysicalKey.Unbound },
|
||||
{ "ButtonPlus", () => ViewModel.Config.ButtonPlus = PhysicalKey.Unbound },
|
||||
{ "ButtonA", () => ViewModel.Config.ButtonA = PhysicalKey.Unbound },
|
||||
{ "ButtonB", () => ViewModel.Config.ButtonB = PhysicalKey.Unbound },
|
||||
{ "ButtonX", () => ViewModel.Config.ButtonX = PhysicalKey.Unbound },
|
||||
{ "ButtonY", () => ViewModel.Config.ButtonY = PhysicalKey.Unbound },
|
||||
{ "RightStickButton", () => ViewModel.Config.RightStickButton = PhysicalKey.Unbound },
|
||||
{ "RightStickUp", () => ViewModel.Config.RightStickUp = PhysicalKey.Unbound },
|
||||
{ "RightStickDown", () => ViewModel.Config.RightStickDown = PhysicalKey.Unbound },
|
||||
{ "RightStickRight", () => ViewModel.Config.RightStickRight = PhysicalKey.Unbound },
|
||||
{ "RightStickLeft", () => ViewModel.Config.RightStickLeft = PhysicalKey.Unbound }
|
||||
};
|
||||
|
||||
if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action))
|
||||
{
|
||||
action();
|
||||
ViewModel.ParentModel.IsModified = true;
|
||||
ViewModel.ParentModel.RefreshModifiedState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,5 +254,10 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
_currentAssigner?.Cancel();
|
||||
_currentAssigner = null;
|
||||
}
|
||||
|
||||
private bool IsActiveAssignmentContext(KeyboardInputViewModel viewModel)
|
||||
{
|
||||
return VisualRoot is not null && ReferenceEquals(DataContext, viewModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
||||
}
|
||||
}
|
||||
|
||||
_avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this);
|
||||
_avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this, KeyboardInputMode.Semantic);
|
||||
_avaloniaKeyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress;
|
||||
}
|
||||
|
||||
protected override void OnPointerReleased(PointerReleasedEventArgs e)
|
||||
|
||||
@@ -1,17 +1,65 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
public partial class SettingsInputView : UserControl
|
||||
{
|
||||
private bool _inputUpdatesBlocked;
|
||||
|
||||
public SettingsInputView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
SetInputUpdatesBlocked(true);
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
SetInputUpdatesBlocked(false);
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
InputView.Dispose();
|
||||
try
|
||||
{
|
||||
InputView.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetInputUpdatesBlocked(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetInputUpdatesBlocked(bool blocked)
|
||||
{
|
||||
if (_inputUpdatesBlocked == blocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MainWindow? mainWindow = RyujinxApp.MainWindow;
|
||||
if (mainWindow?.ViewModel?.AppHost?.NpadManager is not { } npadManager)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (blocked)
|
||||
{
|
||||
npadManager.BlockInputUpdates();
|
||||
}
|
||||
else
|
||||
{
|
||||
npadManager.UnblockInputUpdates();
|
||||
}
|
||||
|
||||
_inputUpdatesBlocked = blocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.Input.HLE;
|
||||
using Ryujinx.Input.SDL3;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -107,7 +108,9 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL3GamepadDriver());
|
||||
AvaloniaKeyboardDriver keyboardDriver = new(this, KeyboardInputMode.Semantic);
|
||||
keyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress;
|
||||
InputManager = new InputManager(keyboardDriver, new SDL3GamepadDriver());
|
||||
|
||||
_ = this.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it);
|
||||
this.ScalingChanged += OnScalingChanged;
|
||||
|
||||
Reference in New Issue
Block a user