Merge branch 'keyboard-localisation-fracture-share' into 'master'

Draft: Input: Refactor Keyboard Handling To Use Physical Keys

See merge request [ryubing/ryujinx!292](https://legacy.git.ryujinx.app/ryubing/ryujinx/-/merge_requests/292)
This commit is contained in:
Babib3l
2026-04-08 23:32:28 +02:00
39 changed files with 3928 additions and 2625 deletions

3
.gitignore vendored
View File

@@ -72,6 +72,9 @@ ipch/
_ReSharper*/ _ReSharper*/
*.[Rr]e[Ss]harper *.[Rr]e[Ss]harper
#.NET
.dotnet-home/
# TeamCity is a build add-in # TeamCity is a build add-in
_TeamCity* _TeamCity*

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
namespace Ryujinx.Common.Configuration.Hid.Keyboard namespace Ryujinx.Common.Configuration.Hid.Keyboard
{ {
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<Key> { } public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<PhysicalKey> { }
} }

View 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,
}
}

View File

@@ -8,25 +8,15 @@ using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using SDL; using SDL;
using static SDL.SDL3; using static SDL.SDL3;
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Input.SDL3 namespace Ryujinx.Input.SDL3
{ {
class SDL3Keyboard : IKeyboard 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(); 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 StandardKeyboardInputConfig _configuration;
private readonly List<ButtonMappingEntry> _buttonsUserMapping; private readonly List<KeyboardInputMappingHelper.KeyboardButtonMapping> _buttonsUserMapping;
private static readonly SDL_Keycode[] _keysDriverMapping = private static readonly SDL_Keycode[] _keysDriverMapping =
@@ -171,9 +161,8 @@ namespace Ryujinx.Input.SDL3
SDL_Keycode.SDLK_0 SDL_Keycode.SDLK_0
]; ];
public SDL3Keyboard(SDL3KeyboardDriver driver, string id, string name) public SDL3Keyboard(string id, string name)
{ {
_driver = driver;
Id = id; Id = id;
Name = name; Name = name;
_buttonsUserMapping = []; _buttonsUserMapping = [];
@@ -195,9 +184,9 @@ namespace Ryujinx.Input.SDL3
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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; return -1;
} }
@@ -205,18 +194,18 @@ namespace Ryujinx.Input.SDL3
return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key], null); 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 return key switch
{ {
Key.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT, ConfigPhysicalKey.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT,
Key.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT, ConfigPhysicalKey.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT,
Key.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL, ConfigPhysicalKey.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL,
Key.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL, ConfigPhysicalKey.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL,
Key.AltLeft => SDL_Keymod.SDL_KMOD_LALT, ConfigPhysicalKey.AltLeft => SDL_Keymod.SDL_KMOD_LALT,
Key.AltRight => SDL_Keymod.SDL_KMOD_RALT, ConfigPhysicalKey.AltRight => SDL_Keymod.SDL_KMOD_RALT,
Key.WinLeft => SDL_Keymod.SDL_KMOD_LGUI, ConfigPhysicalKey.WinLeft => SDL_Keymod.SDL_KMOD_LGUI,
Key.WinRight => SDL_Keymod.SDL_KMOD_RGUI, ConfigPhysicalKey.WinRight => SDL_Keymod.SDL_KMOD_RGUI,
// NOTE: Menu key isn't supported by SDL3. // NOTE: Menu key isn't supported by SDL3.
_ => SDL_Keymod.SDL_KMOD_NONE _ => SDL_Keymod.SDL_KMOD_NONE
}; };
@@ -232,9 +221,9 @@ namespace Ryujinx.Input.SDL3
rawKeyboardState = SDL_GetKeyboardState(null); 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); int index = ToSDL3Scancode(key);
if (index == -1) if (index == -1)
@@ -264,36 +253,6 @@ namespace Ryujinx.Input.SDL3
return value * ConvertRate; 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() public GamepadStateSnapshot GetMappedStateSnapshot()
{ {
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot(); KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
@@ -306,9 +265,9 @@ namespace Ryujinx.Input.SDL3
return result; 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; continue;
} }
@@ -320,8 +279,8 @@ namespace Ryujinx.Input.SDL3
} }
} }
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick); (short leftStickX, short leftStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.LeftJoyconStick);
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick); (short rightStickX, short rightStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.RightJoyconStick);
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY)); result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY)); result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
@@ -357,38 +316,15 @@ namespace Ryujinx.Input.SDL3
{ {
_configuration = (StandardKeyboardInputConfig)configuration; _configuration = (StandardKeyboardInputConfig)configuration;
// First clear the buttons mapping
_buttonsUserMapping.Clear(); _buttonsUserMapping.Clear();
// Then configure left joycon _buttonsUserMapping.AddRange(KeyboardInputMappingHelper.BuildButtonMappings(_configuration));
_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));
} }
} }
public void SetLed(uint packedRgb) 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) public void SetTriggerThreshold(float triggerThreshold)

View File

@@ -50,7 +50,7 @@ namespace Ryujinx.Input.SDL3
return null; return null;
} }
return new SDL3Keyboard(this, _keyboardIdentifers[0], "All keyboards"); return new SDL3Keyboard(_keyboardIdentifers[0], "All keyboards");
} }
public IEnumerable<IGamepad> GetGamepads() public IEnumerable<IGamepad> GetGamepads()

View File

@@ -1,3 +1,5 @@
using Ryujinx.Common.Logging;
namespace Ryujinx.Input.Assigner namespace Ryujinx.Input.Assigner
{ {
/// <summary> /// <summary>
@@ -8,22 +10,42 @@ namespace Ryujinx.Input.Assigner
private readonly IKeyboard _keyboard; private readonly IKeyboard _keyboard;
private KeyboardStateSnapshot _keyboardState; private KeyboardStateSnapshot _keyboardState;
private Button? _pressedButton;
public KeyboardKeyAssigner(IKeyboard keyboard) public KeyboardKeyAssigner(IKeyboard keyboard)
{ {
_keyboard = keyboard; _keyboard = keyboard;
} }
public void Initialize() { } public void Initialize()
{
_pressedButton = null;
}
public void ReadInput() public void ReadInput()
{ {
_keyboardState = _keyboard.GetKeyboardStateSnapshot(); _keyboardState = _keyboard.GetKeyboardStateSnapshot();
if (_pressedButton is not null)
{
return;
}
Button? buttonFromState = GetPressedButtonFromState();
Button? buttonFromBufferedPress = buttonFromState is null ? GetPressedButtonFromBufferedPress() : null;
_pressedButton = buttonFromState ?? buttonFromBufferedPress;
if (_pressedButton is not null)
{
string source = buttonFromState 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() public bool IsAnyButtonPressed()
{ {
return GetPressedButton() is not null; return _pressedButton is not null;
} }
public bool ShouldCancel() public bool ShouldCancel()
@@ -33,18 +55,53 @@ namespace Ryujinx.Input.Assigner
public Button? GetPressedButton() 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++) for (Key key = Key.Unknown; key < Key.Count; key++)
{ {
if (_keyboardState.IsPressed(key)) if (_keyboardState.IsPressed(key))
{ {
keyPressed = new(key); return new Button(key);
break;
} }
} }
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;
} }
} }
} }

View File

@@ -2,6 +2,7 @@ using Ryujinx.Common;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Hid; using Ryujinx.HLE.HOS.Services.Hid;
using System; using System;
@@ -234,7 +235,9 @@ namespace Ryujinx.Input.HLE
_gamepad?.Dispose(); _gamepad?.Dispose();
Id = config.Id; 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); UpdateUserConfiguration(config);

View File

@@ -36,6 +36,7 @@ namespace Ryujinx.Input.HLE
private bool _isDisposed; private bool _isDisposed;
private List<InputConfig> _inputConfig; private List<InputConfig> _inputConfig;
private List<InputConfig> _requestedInputConfig;
private bool _enableKeyboard; private bool _enableKeyboard;
private bool _enableMouse; private bool _enableMouse;
private Switch _device; private Switch _device;
@@ -52,6 +53,7 @@ namespace Ryujinx.Input.HLE
_gamepadDriver = gamepadDriver; _gamepadDriver = gamepadDriver;
_mouseDriver = mouseDriver; _mouseDriver = mouseDriver;
_inputConfig = []; _inputConfig = [];
_requestedInputConfig = [];
_gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; _gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; _gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
@@ -89,14 +91,14 @@ namespace Ryujinx.Input.HLE
} }
} }
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse); ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse);
} }
} }
private void HandleOnGamepadConnected(string id) private void HandleOnGamepadConnected(string id)
{ {
// Force input reload // Force input reload
ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse); ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -127,11 +129,13 @@ namespace Ryujinx.Input.HLE
{ {
lock (_lock) lock (_lock)
{ {
_requestedInputConfig = inputConfig?.ToList() ?? [];
NpadController[] oldControllers = _controllers.ToArray(); NpadController[] oldControllers = _controllers.ToArray();
List<InputConfig> validInputs = []; List<InputConfig> validInputs = [];
foreach (InputConfig inputConfigEntry in inputConfig) foreach (InputConfig inputConfigEntry in _requestedInputConfig)
{ {
NpadController controller; NpadController controller;
int index = (int)inputConfigEntry.PlayerIndex; int index = (int)inputConfigEntry.PlayerIndex;
@@ -147,7 +151,17 @@ namespace Ryujinx.Input.HLE
controller = new(_cemuHookClient); controller = new(_cemuHookClient);
} }
bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry); InputConfig activeConfig = inputConfigEntry;
bool isValid = DriverConfigurationUpdate(ref controller, activeConfig);
if (!isValid &&
enableKeyboard &&
inputConfigEntry is StandardControllerInputConfig &&
TryGetKeyboardFallback(inputConfigEntry, out StandardKeyboardInputConfig fallbackConfig))
{
activeConfig = fallbackConfig;
isValid = DriverConfigurationUpdate(ref controller, activeConfig);
}
if (!isValid) if (!isValid)
{ {
@@ -157,7 +171,7 @@ namespace Ryujinx.Input.HLE
else else
{ {
_controllers[index] = controller; _controllers[index] = controller;
validInputs.Add(inputConfigEntry); validInputs.Add(activeConfig);
} }
} }
@@ -169,7 +183,7 @@ namespace Ryujinx.Input.HLE
oldControllers[i] = null; oldControllers[i] = null;
} }
_inputConfig = inputConfig; _inputConfig = validInputs;
_enableKeyboard = enableKeyboard; _enableKeyboard = enableKeyboard;
_enableMouse = enableMouse; _enableMouse = enableMouse;
@@ -177,6 +191,79 @@ 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 = new StandardKeyboardInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.WindowKeyboard,
Id = keyboardId,
Name = keyboard.Name,
PlayerIndex = inputConfig.PlayerIndex,
ControllerType = inputConfig.ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
{
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<PhysicalKey>
{
StickUp = PhysicalKey.W,
StickDown = PhysicalKey.S,
StickLeft = PhysicalKey.A,
StickRight = PhysicalKey.D,
StickButton = PhysicalKey.F,
},
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
{
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<PhysicalKey>
{
StickUp = PhysicalKey.I,
StickDown = PhysicalKey.K,
StickLeft = PhysicalKey.J,
StickRight = PhysicalKey.L,
StickButton = PhysicalKey.H,
},
};
return true;
}
public void UnblockInputUpdates() public void UnblockInputUpdates()
{ {
lock (_lock) lock (_lock)
@@ -334,7 +421,7 @@ namespace Ryujinx.Input.HLE
} }
} }
internal InputConfig GetPlayerInputConfigByIndex(int index) public InputConfig GetPlayerInputConfigByIndex(int index)
{ {
lock (_lock) lock (_lock)
{ {

View File

@@ -1,5 +1,6 @@
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
namespace Ryujinx.Input namespace Ryujinx.Input
{ {
@@ -33,15 +34,26 @@ namespace Ryujinx.Input
{ {
if (_keyState is null) 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); 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;
}
} }
} }

View File

@@ -0,0 +1,7 @@
namespace Ryujinx.Input
{
public interface IKeyboardModeDriver : IGamepadDriver
{
IKeyboard GetKeyboard(string id, KeyboardInputMode mode);
}
}

View File

@@ -0,0 +1,78 @@
using Ryujinx.Common.Configuration.Hid.Keyboard;
using System.Collections.Generic;
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));
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.Input
{
public enum KeyboardInputMode
{
Semantic,
Physical,
}
}

View File

@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
namespace Ryujinx.Input namespace Ryujinx.Input
{ {
@@ -25,5 +26,8 @@ namespace Ryujinx.Input
/// <returns>True if the given key is pressed</returns> /// <returns>True if the given key is pressed</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsPressed(Key key) => KeysState[(int)key]; public bool IsPressed(Key key) => KeysState[(int)key];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsPressed(ConfigPhysicalKey key) => KeysState[(int)key];
} }
} }

View File

@@ -24,7 +24,7 @@ using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key; using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
namespace Ryujinx.Headless namespace Ryujinx.Headless
{ {
@@ -105,48 +105,48 @@ namespace Ryujinx.Headless
Backend = InputBackendType.WindowKeyboard, Backend = InputBackendType.WindowKeyboard,
Id = null, Id = null,
ControllerType = ControllerType.JoyconPair, ControllerType = ControllerType.JoyconPair,
LeftJoycon = new LeftJoyconCommonConfig<Key> LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
{ {
DpadUp = Key.Up, DpadUp = PhysicalKey.Up,
DpadDown = Key.Down, DpadDown = PhysicalKey.Down,
DpadLeft = Key.Left, DpadLeft = PhysicalKey.Left,
DpadRight = Key.Right, DpadRight = PhysicalKey.Right,
ButtonMinus = Key.Minus, ButtonMinus = PhysicalKey.Minus,
ButtonL = Key.E, ButtonL = PhysicalKey.E,
ButtonZl = Key.Q, ButtonZl = PhysicalKey.Q,
ButtonSl = Key.Unbound, ButtonSl = PhysicalKey.Unbound,
ButtonSr = Key.Unbound, ButtonSr = PhysicalKey.Unbound,
}, },
LeftJoyconStick = new JoyconConfigKeyboardStick<Key> LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
{ {
StickUp = Key.W, StickUp = PhysicalKey.W,
StickDown = Key.S, StickDown = PhysicalKey.S,
StickLeft = Key.A, StickLeft = PhysicalKey.A,
StickRight = Key.D, StickRight = PhysicalKey.D,
StickButton = Key.F, StickButton = PhysicalKey.F,
}, },
RightJoycon = new RightJoyconCommonConfig<Key> RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
{ {
ButtonA = Key.Z, ButtonA = PhysicalKey.Z,
ButtonB = Key.X, ButtonB = PhysicalKey.X,
ButtonX = Key.C, ButtonX = PhysicalKey.C,
ButtonY = Key.V, ButtonY = PhysicalKey.V,
ButtonPlus = Key.Plus, ButtonPlus = PhysicalKey.Plus,
ButtonR = Key.U, ButtonR = PhysicalKey.U,
ButtonZr = Key.O, ButtonZr = PhysicalKey.O,
ButtonSl = Key.Unbound, ButtonSl = PhysicalKey.Unbound,
ButtonSr = Key.Unbound, ButtonSr = PhysicalKey.Unbound,
}, },
RightJoyconStick = new JoyconConfigKeyboardStick<Key> RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
{ {
StickUp = Key.I, StickUp = PhysicalKey.I,
StickDown = Key.K, StickDown = PhysicalKey.K,
StickLeft = Key.J, StickLeft = PhysicalKey.J,
StickRight = Key.L, StickRight = PhysicalKey.L,
StickButton = Key.H, StickButton = PhysicalKey.H,
}, },
}; };
} }

View File

@@ -6,15 +6,15 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Threading; using System.Threading;
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
using Key = Ryujinx.Input.Key; using Key = Ryujinx.Input.Key;
namespace Ryujinx.Ava.Input namespace Ryujinx.Ava.Input
{ {
internal class AvaloniaKeyboard : IKeyboard internal class AvaloniaKeyboard : IKeyboard
{ {
private readonly List<ButtonMappingEntry> _buttonsUserMapping; private readonly List<KeyboardInputMappingHelper.KeyboardButtonMapping> _buttonsUserMapping;
private readonly AvaloniaKeyboardDriver _driver; private readonly AvaloniaKeyboardDriver _driver;
private readonly KeyboardInputMode _mode;
private StandardKeyboardInputConfig _configuration; private StandardKeyboardInputConfig _configuration;
private readonly Lock _userMappingLock = new(); private readonly Lock _userMappingLock = new();
@@ -24,18 +24,12 @@ namespace Ryujinx.Ava.Input
public bool IsConnected => true; public bool IsConnected => true;
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None; public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name, KeyboardInputMode mode)
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)
{ {
_buttonsUserMapping = []; _buttonsUserMapping = [];
_driver = driver; _driver = driver;
_mode = mode;
Id = id; Id = id;
Name = name; Name = name;
} }
@@ -57,22 +51,18 @@ namespace Ryujinx.Ava.Input
return result; 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; 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 leftStickX, short leftStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.LeftJoyconStick);
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick); (short rightStickX, short rightStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.RightJoyconStick);
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY)); result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY)); result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
@@ -100,7 +90,7 @@ namespace Ryujinx.Ava.Input
{ {
try try
{ {
return _driver.IsPressed(key); return _driver.IsPressed(key, _mode);
} }
catch catch
{ {
@@ -108,6 +98,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) public void SetConfiguration(InputConfig configuration)
{ {
lock (_userMappingLock) lock (_userMappingLock)
@@ -116,37 +119,13 @@ namespace Ryujinx.Ava.Input
_buttonsUserMapping.Clear(); _buttonsUserMapping.Clear();
#pragma warning disable IDE0055 // Disable formatting _buttonsUserMapping.AddRange(KeyboardInputMappingHelper.BuildButtonMappings(_configuration));
// 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
} }
} }
public void SetLed(uint packedRgb) 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) { } public void SetTriggerThreshold(float triggerThreshold) { }
@@ -162,41 +141,9 @@ namespace Ryujinx.Ava.Input
return value * ConvertRate; 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() public void Clear()
{ {
_driver?.Clear(); _driver?.Clear(_mode);
} }
public void Dispose() { } public void Dispose() { }

View File

@@ -1,19 +1,37 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Systems.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Input; using Ryujinx.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using AvaKey = Avalonia.Input.Key; using System.Threading;
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
using Key = Ryujinx.Input.Key; using Key = Ryujinx.Input.Key;
namespace Ryujinx.Ava.Input namespace Ryujinx.Ava.Input
{ {
internal class AvaloniaKeyboardDriver : IGamepadDriver internal class AvaloniaKeyboardDriver : IKeyboardModeDriver
{ {
private enum PhysicalKeySource
{
Direct,
ObservedFallback,
Unknown,
}
private static readonly string[] _keyboardIdentifers = ["0"]; private static readonly string[] _keyboardIdentifers = ["0"];
private readonly Control _control; 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 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> KeyPressed;
public event EventHandler<KeyEventArgs> KeyRelease; public event EventHandler<KeyEventArgs> KeyRelease;
@@ -22,14 +40,30 @@ namespace Ryujinx.Ava.Input
public string DriverName => "AvaloniaKeyboardDriver"; public string DriverName => "AvaloniaKeyboardDriver";
public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers; public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
public AvaloniaKeyboardDriver(Control control) public AvaloniaKeyboardDriver(Control control, KeyboardInputMode defaultMode = KeyboardInputMode.Semantic)
{ {
_control = control; _control = control;
_pressedKeys = []; _window = control as Window ?? TopLevel.GetTopLevel(control) as Window;
_semanticPressedKeys = [];
_physicalPressedKeys = [];
_observedPhysicalKeysBySemanticKey = [];
_semanticPressedKeyQueue = [];
_physicalPressedKeyQueue = [];
_pressedKeyQueueLock = new();
_defaultMode = defaultMode;
_control.KeyDown += OnKeyPress; // Use routed handlers so keys consumed earlier in the Avalonia pipeline
_control.KeyUp += OnKeyRelease; // can still be observed by the input driver. This is needed for keys like
// Caps Lock on macOS, which may not reach the plain CLR event path.
_control.AddHandler(InputElement.KeyDownEvent, OnKeyPress, RoutingStrategies.Tunnel, true);
_control.AddHandler(InputElement.KeyUpEvent, OnKeyRelease, RoutingStrategies.Tunnel, true);
_control.TextInput += Control_TextInput; _control.TextInput += Control_TextInput;
_window?.Deactivated += Window_Deactivated;
}
private void Window_Deactivated(object sender, EventArgs e)
{
Clear();
} }
private void Control_TextInput(object sender, TextInputEventArgs e) private void Control_TextInput(object sender, TextInputEventArgs e)
@@ -50,13 +84,18 @@ namespace Ryujinx.Ava.Input
} }
public IGamepad GetGamepad(string id) public IGamepad GetGamepad(string id)
{
return GetKeyboard(id, _defaultMode);
}
public IKeyboard GetKeyboard(string id, KeyboardInputMode mode)
{ {
if (!_keyboardIdentifers[0].Equals(id)) if (!_keyboardIdentifers[0].Equals(id))
{ {
return null; 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")]; public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
@@ -65,40 +104,189 @@ namespace Ryujinx.Ava.Input
{ {
if (disposing) if (disposing)
{ {
_control.KeyUp -= OnKeyPress; _control.RemoveHandler(InputElement.KeyDownEvent, OnKeyPress);
_control.KeyDown -= OnKeyRelease; _control.RemoveHandler(InputElement.KeyUpEvent, OnKeyRelease);
_control.TextInput -= Control_TextInput;
if (_window != null)
{
_window.Deactivated -= Window_Deactivated;
}
} }
} }
protected void OnKeyPress(object sender, KeyEventArgs args) protected void OnKeyPress(object sender, KeyEventArgs args)
{ {
_pressedKeys.Add(args.Key); UpdateKeyStates(args, isPressed: true);
KeyPressed?.Invoke(this, args); KeyPressed?.Invoke(this, args);
} }
protected void OnKeyRelease(object sender, KeyEventArgs args) protected void OnKeyRelease(object sender, KeyEventArgs args)
{ {
_pressedKeys.Remove(args.Key); UpdateKeyStates(args, isPressed: false);
KeyRelease?.Invoke(this, args); KeyRelease?.Invoke(this, args);
} }
internal bool IsPressed(Key key) internal bool IsPressed(Key key, KeyboardInputMode mode)
{ {
if (key is Key.Unbound or Key.Unknown) if (key is Key.Unbound or Key.Unknown)
{ {
return false; return false;
} }
AvaloniaKeyboardMappingHelper.TryGetAvaKey(key, out AvaKey nativeKey); return mode == KeyboardInputMode.Physical
? _physicalPressedKeys.Contains((ConfigPhysicalKey)(int)key)
: _semanticPressedKeys.Contains(key);
}
return _pressedKeys.Contains(nativeKey); internal void Clear(KeyboardInputMode mode)
{
lock (_pressedKeyQueueLock)
{
if (mode == KeyboardInputMode.Physical)
{
_physicalPressedKeys.Clear();
_physicalPressedKeyQueue.Clear();
}
else
{
_semanticPressedKeys.Clear();
_semanticPressedKeyQueue.Clear();
}
}
} }
public void Clear() public void Clear()
{ {
_pressedKeys.Clear(); lock (_pressedKeyQueueLock)
{
_semanticPressedKeys.Clear();
_physicalPressedKeys.Clear();
_semanticPressedKeyQueue.Clear();
_physicalPressedKeyQueue.Clear();
}
}
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() public void Dispose()

View File

@@ -2,6 +2,7 @@ using Ryujinx.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using AvaKey = Avalonia.Input.Key; using AvaKey = Avalonia.Input.Key;
using AvaPhysicalKey = Avalonia.Input.PhysicalKey;
namespace Ryujinx.Ava.Input namespace Ryujinx.Ava.Input
{ {
@@ -132,7 +133,8 @@ namespace Ryujinx.Ava.Input
AvaKey.D8, AvaKey.D8,
AvaKey.D9, AvaKey.D9,
AvaKey.OemTilde, AvaKey.OemTilde,
AvaKey.OemTilde,AvaKey.OemMinus, AvaKey.Oem102,
AvaKey.OemMinus,
AvaKey.OemPlus, AvaKey.OemPlus,
AvaKey.OemOpenBrackets, AvaKey.OemOpenBrackets,
AvaKey.OemCloseBrackets, AvaKey.OemCloseBrackets,
@@ -147,7 +149,149 @@ namespace Ryujinx.Ava.Input
AvaKey.None 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<AvaKey, Key> _avaKeyMapping;
private static readonly Dictionary<AvaPhysicalKey, Key> _avaPhysicalKeyMapping;
static AvaloniaKeyboardMappingHelper() 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. // NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
_avaKeyMapping = new Dictionary<AvaKey, Key>(); _avaKeyMapping = new Dictionary<AvaKey, Key>();
_avaPhysicalKeyMapping = new Dictionary<AvaPhysicalKey, Key>();
foreach (Key key in inputKeys) 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) public static bool TryGetAvaKey(Key key, out AvaKey avaKey)
{ {
avaKey = AvaKey.None; avaKey = AvaKey.None;
bool keyExist = (int)key < _keyMapping.Length; bool keyExist = key < Key.Count && (int)key < _keyMapping.Length;
if (keyExist) if (keyExist)
{ {
avaKey = _keyMapping[(int)key]; avaKey = _keyMapping[(int)key];
@@ -178,9 +343,34 @@ namespace Ryujinx.Ava.Input
return keyExist; 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) public static Key ToInputKey(AvaKey key)
{ {
return _avaKeyMapping.GetValueOrDefault(key, Key.Unknown); 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);
}
} }
} }

View File

@@ -1234,9 +1234,17 @@ namespace Ryujinx.Ava.Systems
return false; return false;
} }
bool hasModalFocusLoss = _viewModel.Window is MainWindow mainWindow &&
mainWindow.SettingsWindow?.IsActive == true;
if (!_viewModel.IsActive || hasModalFocusLoss)
{
_inputManager.KeyboardDriver.Clear();
}
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
if (_viewModel.IsActive) if (_viewModel.IsActive && !hasModalFocusLoss)
{ {
bool isCursorVisible = true; bool isCursorVisible = true;
@@ -1369,7 +1377,7 @@ namespace Ryujinx.Ava.Systems
// Touchscreen. // Touchscreen.
bool hasTouch = false; bool hasTouch = false;
if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse.Value) if (_viewModel.IsActive && !hasModalFocusLoss && !ConfigurationState.Instance.Hid.EnableMouse.Value)
{ {
hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
} }

View File

@@ -13,6 +13,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Key = Ryujinx.Common.Configuration.Hid.Key;
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
using RyuLogger = Ryujinx.Common.Logging.Logger; using RyuLogger = Ryujinx.Common.Logging.Logger;
namespace Ryujinx.Ava.Systems.Configuration namespace Ryujinx.Ava.Systems.Configuration
@@ -269,45 +271,45 @@ namespace Ryujinx.Ava.Systems.Configuration
Id = "0", Id = "0",
PlayerIndex = PlayerIndex.Player1, PlayerIndex = PlayerIndex.Player1,
ControllerType = ControllerType.ProController, ControllerType = ControllerType.ProController,
LeftJoycon = new LeftJoyconCommonConfig<Key> LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
{ {
DpadUp = Key.Up, DpadUp = PhysicalKey.Up,
DpadDown = Key.Down, DpadDown = PhysicalKey.Down,
DpadLeft = Key.Left, DpadLeft = PhysicalKey.Left,
DpadRight = Key.Right, DpadRight = PhysicalKey.Right,
ButtonMinus = Key.Minus, ButtonMinus = PhysicalKey.Minus,
ButtonL = Key.E, ButtonL = PhysicalKey.E,
ButtonZl = Key.Q, ButtonZl = PhysicalKey.Q,
ButtonSl = Key.Unbound, ButtonSl = PhysicalKey.Unbound,
ButtonSr = Key.Unbound, ButtonSr = PhysicalKey.Unbound,
}, },
LeftJoyconStick = new JoyconConfigKeyboardStick<Key> LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
{ {
StickUp = Key.W, StickUp = PhysicalKey.W,
StickDown = Key.S, StickDown = PhysicalKey.S,
StickLeft = Key.A, StickLeft = PhysicalKey.A,
StickRight = Key.D, StickRight = PhysicalKey.D,
StickButton = Key.F, StickButton = PhysicalKey.F,
}, },
RightJoycon = new RightJoyconCommonConfig<Key> RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
{ {
ButtonA = Key.Z, ButtonA = PhysicalKey.Z,
ButtonB = Key.X, ButtonB = PhysicalKey.X,
ButtonX = Key.C, ButtonX = PhysicalKey.C,
ButtonY = Key.V, ButtonY = PhysicalKey.V,
ButtonPlus = Key.Plus, ButtonPlus = PhysicalKey.Plus,
ButtonR = Key.U, ButtonR = PhysicalKey.U,
ButtonZr = Key.O, ButtonZr = PhysicalKey.O,
ButtonSl = Key.Unbound, ButtonSl = PhysicalKey.Unbound,
ButtonSr = Key.Unbound, ButtonSr = PhysicalKey.Unbound,
}, },
RightJoyconStick = new JoyconConfigKeyboardStick<Key> RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
{ {
StickUp = Key.I, StickUp = PhysicalKey.I,
StickDown = Key.K, StickDown = PhysicalKey.K,
StickLeft = Key.J, StickLeft = PhysicalKey.J,
StickRight = Key.L, StickRight = PhysicalKey.L,
StickButton = Key.H, StickButton = PhysicalKey.H,
}, },
} }
]; ];

View File

@@ -8,6 +8,8 @@ using Ryujinx.Graphics.Vulkan;
using Ryujinx.HLE; using Ryujinx.HLE;
using System; using System;
using System.Linq; using System.Linq;
using Key = Ryujinx.Common.Configuration.Hid.Key;
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
namespace Ryujinx.Ava.Systems.Configuration namespace Ryujinx.Ava.Systems.Configuration
{ {
@@ -285,45 +287,45 @@ namespace Ryujinx.Ava.Systems.Configuration
Name = "Keyboard", Name = "Keyboard",
PlayerIndex = PlayerIndex.Player1, PlayerIndex = PlayerIndex.Player1,
ControllerType = ControllerType.ProController, ControllerType = ControllerType.ProController,
LeftJoycon = new LeftJoyconCommonConfig<Key> LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
{ {
DpadUp = Key.Up, DpadUp = PhysicalKey.Up,
DpadDown = Key.Down, DpadDown = PhysicalKey.Down,
DpadLeft = Key.Left, DpadLeft = PhysicalKey.Left,
DpadRight = Key.Right, DpadRight = PhysicalKey.Right,
ButtonMinus = Key.Minus, ButtonMinus = PhysicalKey.Minus,
ButtonL = Key.E, ButtonL = PhysicalKey.E,
ButtonZl = Key.Q, ButtonZl = PhysicalKey.Q,
ButtonSl = Key.Unbound, ButtonSl = PhysicalKey.Unbound,
ButtonSr = Key.Unbound, ButtonSr = PhysicalKey.Unbound,
}, },
LeftJoyconStick = new JoyconConfigKeyboardStick<Key> LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
{ {
StickUp = Key.W, StickUp = PhysicalKey.W,
StickDown = Key.S, StickDown = PhysicalKey.S,
StickLeft = Key.A, StickLeft = PhysicalKey.A,
StickRight = Key.D, StickRight = PhysicalKey.D,
StickButton = Key.F, StickButton = PhysicalKey.F,
}, },
RightJoycon = new RightJoyconCommonConfig<Key> RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
{ {
ButtonA = Key.Z, ButtonA = PhysicalKey.Z,
ButtonB = Key.X, ButtonB = PhysicalKey.X,
ButtonX = Key.C, ButtonX = PhysicalKey.C,
ButtonY = Key.V, ButtonY = PhysicalKey.V,
ButtonPlus = Key.Plus, ButtonPlus = PhysicalKey.Plus,
ButtonR = Key.U, ButtonR = PhysicalKey.U,
ButtonZr = Key.O, ButtonZr = PhysicalKey.O,
ButtonSl = Key.Unbound, ButtonSl = PhysicalKey.Unbound,
ButtonSr = Key.Unbound, ButtonSr = PhysicalKey.Unbound,
}, },
RightJoyconStick = new JoyconConfigKeyboardStick<Key> RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
{ {
StickUp = Key.I, StickUp = PhysicalKey.I,
StickDown = Key.K, StickDown = PhysicalKey.K,
StickLeft = Key.J, StickLeft = PhysicalKey.J,
StickRight = Key.L, StickRight = PhysicalKey.L,
StickButton = Key.H, StickButton = PhysicalKey.H,
}, },
} }
]; ];

View File

@@ -65,7 +65,7 @@ namespace Ryujinx.Ava.UI.Applet
private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e) 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)) if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true))
{ {
@@ -85,7 +85,7 @@ namespace Ryujinx.Ava.UI.Applet
private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e) 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)) if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true))
{ {

View File

@@ -1,5 +1,6 @@
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Threading; using Avalonia.Threading;
using Ryujinx.Ava.Input;
using Ryujinx.Input; using Ryujinx.Input;
using Ryujinx.Input.Assigner; using Ryujinx.Input.Assigner;
using System; using System;
@@ -25,6 +26,7 @@ namespace Ryujinx.Ava.UI.Helpers
private bool _isWaitingForInput; private bool _isWaitingForInput;
private bool _shouldUnbind; private bool _shouldUnbind;
private IKeyboard _keyboard;
public event EventHandler<ButtonAssignedEventArgs> ButtonAssigned; public event EventHandler<ButtonAssignedEventArgs> ButtonAssigned;
public ButtonKeyAssigner(ToggleButton toggleButton) public ButtonKeyAssigner(ToggleButton toggleButton)
@@ -34,6 +36,9 @@ namespace Ryujinx.Ava.UI.Helpers
public async void GetInputAndAssign(IButtonAssigner assigner, IKeyboard keyboard = null) public async void GetInputAndAssign(IButtonAssigner assigner, IKeyboard keyboard = null)
{ {
_keyboard = keyboard;
ClearKeyboardState(_keyboard);
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
ToggledButton.IsChecked = true; ToggledButton.IsChecked = true;
@@ -82,6 +87,7 @@ namespace Ryujinx.Ava.UI.Helpers
_isWaitingForInput = false; _isWaitingForInput = false;
ToggledButton.IsChecked = false; ToggledButton.IsChecked = false;
ClearKeyboardState(_keyboard);
if (pressedButton.HasValue && pressedButton.Value.AsHidType<Key>() == Key.BackSpace) if (pressedButton.HasValue && pressedButton.Value.AsHidType<Key>() == Key.BackSpace)
{ {
@@ -98,6 +104,15 @@ namespace Ryujinx.Ava.UI.Helpers
_isWaitingForInput = false; _isWaitingForInput = false;
ToggledButton.IsChecked = false; ToggledButton.IsChecked = false;
_shouldUnbind = shouldUnbind; _shouldUnbind = shouldUnbind;
ClearKeyboardState(_keyboard);
}
private static void ClearKeyboardState(IKeyboard keyboard)
{
if (keyboard is AvaloniaKeyboard avaloniaKeyboard)
{
avaloniaKeyboard.Clear();
}
} }
} }
} }

View File

@@ -5,6 +5,7 @@ using Ryujinx.Common.Configuration.Hid.Controller;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Key = Ryujinx.Input.Key;
namespace Ryujinx.Ava.UI.Helpers namespace Ryujinx.Ava.UI.Helpers
{ {
@@ -12,79 +13,6 @@ namespace Ryujinx.Ava.UI.Helpers
{ {
public static readonly KeyValueConverter Instance = new(); 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() private static readonly Dictionary<GamepadInputId, LocaleKeys> _gamepadInputIdMap = new()
{ {
{ GamepadInputId.LeftStick, LocaleKeys.GamepadLeftStick }, { GamepadInputId.LeftStick, LocaleKeys.GamepadLeftStick },
@@ -110,49 +38,38 @@ namespace Ryujinx.Ava.UI.Helpers
{ GamepadInputId.SingleRightTrigger0, LocaleKeys.GamepadSingleRightTrigger0}, { GamepadInputId.SingleRightTrigger0, LocaleKeys.GamepadSingleRightTrigger0},
{ GamepadInputId.SingleLeftTrigger1, LocaleKeys.GamepadSingleLeftTrigger1}, { GamepadInputId.SingleLeftTrigger1, LocaleKeys.GamepadSingleLeftTrigger1},
{ GamepadInputId.SingleRightTrigger1, LocaleKeys.GamepadSingleRightTrigger1}, { GamepadInputId.SingleRightTrigger1, LocaleKeys.GamepadSingleRightTrigger1},
{ GamepadInputId.Unbound, LocaleKeys.KeyUnbound}, { GamepadInputId.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound},
}; };
private static readonly Dictionary<StickInputId, LocaleKeys> _stickInputIdMap = new() private static readonly Dictionary<StickInputId, LocaleKeys> _stickInputIdMap = new()
{ {
{ StickInputId.Left, LocaleKeys.StickLeft}, { StickInputId.Left, LocaleKeys.StickLeft},
{ StickInputId.Right, LocaleKeys.StickRight}, { StickInputId.Right, LocaleKeys.StickRight},
{ StickInputId.Unbound, LocaleKeys.KeyUnbound}, { StickInputId.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound},
}; };
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
string keyString = string.Empty; string keyString = string.Empty;
LocaleKeys localeKey;
switch (value) switch (value)
{ {
case Key key: case Key key:
if (_keysMap.TryGetValue(key, out localeKey)) if (KeyboardLayoutLocaleHelper.TryGetSemanticLabel(key, out string localizedKeyLabel))
{ {
if (OperatingSystem.IsMacOS()) keyString = localizedKeyLabel;
{
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 else
{ {
keyString = key.ToString(); keyString = key.ToString();
} }
break;
case PhysicalKey physicalKey:
keyString = PhysicalKeyLabelHelper.GetDisplayString(physicalKey);
break; break;
case GamepadInputId gamepadInputId: case GamepadInputId gamepadInputId:
LocaleKeys localeKey;
if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey)) if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey))
{ {
keyString = LocaleManager.Instance[localeKey]; keyString = LocaleManager.Instance[localeKey];

View 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;
}
}
}

View 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];
}
}
}

View File

@@ -0,0 +1,226 @@
using Avalonia.Input;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
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;
}
try
{
string labelsPath = GetObservedLabelsPath();
if (File.Exists(labelsPath))
{
Dictionary<string, string> labels = JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(labelsPath), _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
{
}
_observedLayoutLabelsLoaded = true;
}
}
private static void SaveObservedLayoutLabels()
{
lock (_observedLayoutLabelsLock)
{
try
{
Dictionary<string, string> labels = [];
foreach ((ConfigPhysicalKey key, string value) in _observedLayoutLabels)
{
labels[key.ToString()] = value;
}
File.WriteAllText(GetObservedLabelsPath(), JsonSerializer.Serialize(labels, _serializerOptions));
}
catch
{
}
}
}
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
{
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;
}
}
}

View File

@@ -13,88 +13,88 @@ namespace Ryujinx.Ava.UI.Models.Input
public PlayerIndex PlayerIndex { get; set; } public PlayerIndex PlayerIndex { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key LeftStickUp { get; set; } public partial PhysicalKey LeftStickUp { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key LeftStickDown { get; set; } public partial PhysicalKey LeftStickDown { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key LeftStickLeft { get; set; } public partial PhysicalKey LeftStickLeft { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key LeftStickRight { get; set; } public partial PhysicalKey LeftStickRight { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key LeftStickButton { get; set; } public partial PhysicalKey LeftStickButton { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key RightStickUp { get; set; } public partial PhysicalKey RightStickUp { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key RightStickDown { get; set; } public partial PhysicalKey RightStickDown { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key RightStickLeft { get; set; } public partial PhysicalKey RightStickLeft { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key RightStickRight { get; set; } public partial PhysicalKey RightStickRight { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key RightStickButton { get; set; } public partial PhysicalKey RightStickButton { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key DpadUp { get; set; } public partial PhysicalKey DpadUp { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key DpadDown { get; set; } public partial PhysicalKey DpadDown { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key DpadLeft { get; set; } public partial PhysicalKey DpadLeft { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key DpadRight { get; set; } public partial PhysicalKey DpadRight { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key ButtonMinus { get; set; } public partial PhysicalKey ButtonMinus { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key ButtonPlus { get; set; } public partial PhysicalKey ButtonPlus { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key ButtonA { get; set; } public partial PhysicalKey ButtonA { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key ButtonB { get; set; } public partial PhysicalKey ButtonB { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key ButtonX { get; set; } public partial PhysicalKey ButtonX { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key ButtonY { get; set; } public partial PhysicalKey ButtonY { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key ButtonL { get; set; } public partial PhysicalKey ButtonL { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key ButtonR { get; set; } public partial PhysicalKey ButtonR { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key ButtonZl { get; set; } public partial PhysicalKey ButtonZl { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key ButtonZr { get; set; } public partial PhysicalKey ButtonZr { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key LeftButtonSl { get; set; } public partial PhysicalKey LeftButtonSl { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key LeftButtonSr { get; set; } public partial PhysicalKey LeftButtonSr { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key RightButtonSl { get; set; } public partial PhysicalKey RightButtonSl { get; set; }
[ObservableProperty] [ObservableProperty]
public partial Key RightButtonSr { get; set; } public partial PhysicalKey RightButtonSr { get; set; }
public KeyboardInputConfig(InputConfig config) public KeyboardInputConfig(InputConfig config)
{ {
@@ -153,7 +153,7 @@ namespace Ryujinx.Ava.UI.Models.Input
Backend = InputBackendType.WindowKeyboard, Backend = InputBackendType.WindowKeyboard,
PlayerIndex = PlayerIndex, PlayerIndex = PlayerIndex,
ControllerType = ControllerType, ControllerType = ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<Key> LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
{ {
DpadUp = DpadUp, DpadUp = DpadUp,
DpadDown = DpadDown, DpadDown = DpadDown,
@@ -165,7 +165,7 @@ namespace Ryujinx.Ava.UI.Models.Input
ButtonSl = LeftButtonSl, ButtonSl = LeftButtonSl,
ButtonSr = LeftButtonSr, ButtonSr = LeftButtonSr,
}, },
RightJoycon = new RightJoyconCommonConfig<Key> RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
{ {
ButtonA = ButtonA, ButtonA = ButtonA,
ButtonB = ButtonB, ButtonB = ButtonB,
@@ -177,7 +177,7 @@ namespace Ryujinx.Ava.UI.Models.Input
ButtonR = ButtonR, ButtonR = ButtonR,
ButtonZr = ButtonZr, ButtonZr = ButtonZr,
}, },
LeftJoyconStick = new JoyconConfigKeyboardStick<Key> LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
{ {
StickUp = LeftStickUp, StickUp = LeftStickUp,
StickDown = LeftStickDown, StickDown = LeftStickDown,
@@ -185,7 +185,7 @@ namespace Ryujinx.Ava.UI.Models.Input
StickLeft = LeftStickLeft, StickLeft = LeftStickLeft,
StickButton = LeftStickButton, StickButton = LeftStickButton,
}, },
RightJoyconStick = new JoyconConfigKeyboardStick<Key> RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
{ {
StickUp = RightStickUp, StickUp = RightStickUp,
StickDown = RightStickDown, StickDown = RightStickDown,
@@ -198,5 +198,37 @@ namespace Ryujinx.Ava.UI.Models.Input
return config; 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));
}
} }
} }

View File

@@ -154,42 +154,42 @@ namespace Ryujinx.Ava.UI.Models.Input
{ {
KeyboardStateSnapshot snapshot = keyboard.GetKeyboardStateSnapshot(); KeyboardStateSnapshot snapshot = keyboard.GetKeyboardStateSnapshot();
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickRight)) if (snapshot.IsPressed(KeyboardConfig.LeftStickRight))
{ {
leftBuffer.Item1 += 1; leftBuffer.Item1 += 1;
} }
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickLeft)) if (snapshot.IsPressed(KeyboardConfig.LeftStickLeft))
{ {
leftBuffer.Item1 -= 1; leftBuffer.Item1 -= 1;
} }
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickUp)) if (snapshot.IsPressed(KeyboardConfig.LeftStickUp))
{ {
leftBuffer.Item2 += 1; leftBuffer.Item2 += 1;
} }
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickDown)) if (snapshot.IsPressed(KeyboardConfig.LeftStickDown))
{ {
leftBuffer.Item2 -= 1; leftBuffer.Item2 -= 1;
} }
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickRight)) if (snapshot.IsPressed(KeyboardConfig.RightStickRight))
{ {
rightBuffer.Item1 += 1; rightBuffer.Item1 += 1;
} }
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickLeft)) if (snapshot.IsPressed(KeyboardConfig.RightStickLeft))
{ {
rightBuffer.Item1 -= 1; rightBuffer.Item1 -= 1;
} }
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickUp)) if (snapshot.IsPressed(KeyboardConfig.RightStickUp))
{ {
rightBuffer.Item2 += 1; rightBuffer.Item2 += 1;
} }
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickDown)) if (snapshot.IsPressed(KeyboardConfig.RightStickDown))
{ {
rightBuffer.Item2 -= 1; rightBuffer.Item2 -= 1;
} }

View File

@@ -45,7 +45,6 @@ namespace Ryujinx.Ava.UI.Renderer
Content = EmbeddedWindow; Content = EmbeddedWindow;
} }
public void Dispose() public void Dispose()
{ {
if (EmbeddedWindow != null) if (EmbeddedWindow != null)

View File

@@ -88,19 +88,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public async void ShowMotionConfig() public async void ShowMotionConfig()
{ {
await MotionInputView.Show(this); await MotionInputView.Show(this);
ParentModel.IsModified = true; ParentModel.RefreshModifiedState();
} }
public async void ShowRumbleConfig() public async void ShowRumbleConfig()
{ {
await RumbleInputView.Show(this); await RumbleInputView.Show(this);
ParentModel.IsModified = true; ParentModel.RefreshModifiedState();
} }
public async void ShowLedConfig() public async void ShowLedConfig()
{ {
await LedInputView.Show(this); await LedInputView.Show(this);
ParentModel.IsModified = true; ParentModel.RefreshModifiedState();
} }
public void OnParentModelChanged() public void OnParentModelChanged()

View File

@@ -1,6 +1,7 @@
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Svg.Skia; using Avalonia.Svg.Skia;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Gommon; using Gommon;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
@@ -28,7 +29,7 @@ using System.Linq;
using System.Text.Json; using System.Text.Json;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key; using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
namespace Ryujinx.Ava.UI.ViewModels.Input namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
@@ -42,6 +43,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private const string KeyboardString = "keyboard"; private const string KeyboardString = "keyboard";
private const string ControllerString = "controller"; private const string ControllerString = "controller";
private readonly MainWindow _mainWindow; private readonly MainWindow _mainWindow;
private Control _keyboardDriverControl;
private PlayerIndex _playerId; private PlayerIndex _playerId;
private PlayerIndex _playerIdChoose; private PlayerIndex _playerIdChoose;
@@ -65,7 +67,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public IGamepadDriver AvaloniaKeyboardDriver { get; } public IGamepadDriver AvaloniaKeyboardDriver { get; private set; }
public IGamepad SelectedGamepad public IGamepad SelectedGamepad
{ {
@@ -89,7 +91,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; } public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; }
internal ObservableCollection<ControllerModel> Controllers { get; set; } internal ObservableCollection<ControllerModel> Controllers { get; set; }
public AvaloniaList<string> ProfilesList { get; set; } public AvaloniaList<string> ProfilesList { get; set; }
public AvaloniaList<string> DeviceList { get; set; }
public bool UseGlobalConfig; public bool UseGlobalConfig;
@@ -99,7 +100,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool IsKeyboard => !IsController; public bool IsKeyboard => !IsController;
public bool IsRight { get; set; } public bool IsRight { get; set; }
public bool IsLeft { get; set; } public bool IsLeft { get; set; }
public string RevertDeviceId { get; set; }
public bool HasLed => (SelectedGamepad.Features & GamepadFeaturesFlag.Led) != 0; public bool HasLed => (SelectedGamepad.Features & GamepadFeaturesFlag.Led) != 0;
public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense"); public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");
@@ -163,7 +163,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
LoadDevice(); LoadDevice();
LoadProfiles(); LoadProfiles();
RevertDeviceId = Devices[Device].Id;
_isLoaded = true; _isLoaded = true;
_isChangeTrackingActive = true; _isChangeTrackingActive = true;
OnPropertyChanged(); OnPropertyChanged();
@@ -175,15 +174,22 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
get => _controller; get => _controller;
set set
{ {
MarkAsChanged(); int controllerIndex = value < 0 ? 0 : value;
_controller = value; if (controllerIndex == _controller)
if (_controller == -1)
{ {
_controller = 0; return;
} }
ApplyControllerSelection(controllerIndex);
RefreshModifiedState();
}
}
private void ApplyControllerSelection(int controllerIndex)
{
_controller = controllerIndex;
if (Controllers.Count > 0 && _controller < Controllers.Count && _controller > -1) if (Controllers.Count > 0 && _controller < Controllers.Count && _controller > -1)
{ {
ControllerType controller = Controllers[_controller].Type; ControllerType controller = Controllers[_controller].Type;
@@ -216,10 +222,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
LoadProfiles(); LoadProfiles();
} }
OnPropertyChanged(); OnPropertyChanged(nameof(Controller));
NotifyChanges(); NotifyChanges();
} }
}
public string ControllerImage public string ControllerImage
{ {
@@ -255,33 +260,83 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
get => _device; get => _device;
set set
{ {
MarkAsChanged(); if (value < 0 || value >= Devices.Count)
_device = value < 0 ? 0 : value;
if (_device >= Devices.Count)
{ {
return; return;
} }
_device = value;
DeviceType selected = Devices[_device].Type; DeviceType selected = Devices[_device].Type;
if (selected != DeviceType.None) if (selected != DeviceType.None)
{ {
LoadControllers();
if (_isLoaded) if (_isLoaded)
{ {
LoadConfiguration(LoadDefaultConfiguration()); LoadSelectedDeviceDefaults();
}
else
{
LoadSelectedDeviceControllers();
} }
} }
RefreshModifiedState();
FindPairedDeviceInConfigFile(); FindPairedDeviceInConfigFile();
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(SelectedDeviceItem));
NotifyChanges(); NotifyChanges();
} }
} }
public void ResetCurrentDeviceToDefaults()
{
RefreshAvailableDevices();
if (_device <= 0 || _device >= Devices.Count || Devices[_device].Type == DeviceType.None)
{
return;
}
LoadSelectedDeviceDefaults();
RefreshModifiedState();
FindPairedDeviceInConfigFile();
NotifyChanges();
}
public void RefreshInputDevices()
{
RefreshAvailableDevices();
}
public object SelectedDeviceItem
{
get => _device >= 0 && _device < Devices.Count ? Devices[_device] : null;
set
{
if (value is not ValueTuple<DeviceType, string, string> selectedDevice)
{
return;
}
int deviceIndex = Devices.ToList().FindIndex(device =>
device.Type == selectedDevice.Item1 &&
device.Id == selectedDevice.Item2);
if (deviceIndex < 0)
{
return;
}
if (deviceIndex == _device)
{
return;
}
Device = deviceIndex;
}
}
public InputConfig Config { get; set; } public InputConfig Config { get; set; }
public InputViewModel(UserControl owner, bool useGlobal = false) : this() public InputViewModel(UserControl owner, bool useGlobal = false) : this()
@@ -290,7 +345,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
_mainWindow = RyujinxApp.MainWindow; _mainWindow = RyujinxApp.MainWindow;
AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner); ReplaceKeyboardDriver(owner);
PhysicalKeyLabelHelper.LabelsChanged += OnPhysicalKeyLabelsChanged;
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
@@ -301,7 +357,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_isLoaded = false; _isLoaded = false;
LoadDevices(); RefreshAvailableDevices();
PlayerId = PlayerIndex.Player1; PlayerId = PlayerIndex.Player1;
} }
@@ -309,13 +365,22 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_isChangeTrackingActive = true; _isChangeTrackingActive = true;
} }
public void RetargetKeyboardDriver(Control owner)
{
if (!Program.PreviewerDetached)
{
return;
}
ReplaceKeyboardDriver(owner);
}
public InputViewModel() public InputViewModel()
{ {
PlayerIndexes = []; PlayerIndexes = [];
Controllers = []; Controllers = [];
Devices = []; Devices = [];
ProfilesList = []; ProfilesList = [];
DeviceList = [];
VisualStick = new StickVisualizer(this); VisualStick = new StickVisualizer(this);
ControllerImage = ProControllerResource; ControllerImage = ProControllerResource;
@@ -333,16 +398,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private void LoadConfiguration(InputConfig inputConfig = null) private InputConfig GetPersistedInputConfig()
{ {
if (UseGlobalConfig && Program.UseExtraConfig) if (UseGlobalConfig && Program.UseExtraConfig)
{ {
Config = inputConfig ?? ConfigurationState.InstanceExtra.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId); return ConfigurationState.InstanceExtra.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);
} }
else
return ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);
}
private void LoadConfiguration(InputConfig inputConfig = null)
{ {
Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId); Config = inputConfig ?? GetDisplayedInputConfig(GetPersistedInputConfig());
}
if (Config is StandardKeyboardInputConfig keyboardInputConfig) if (Config is StandardKeyboardInputConfig keyboardInputConfig)
{ {
@@ -355,6 +423,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
} }
private InputConfig GetDisplayedInputConfig(InputConfig persistedConfig)
{
if (persistedConfig is not StandardControllerInputConfig)
{
return persistedConfig;
}
InputConfig activeConfig = _mainWindow?.ViewModel.AppHost?.NpadManager?.GetPlayerInputConfigByIndex((int)_playerId);
return activeConfig is StandardKeyboardInputConfig ? activeConfig : persistedConfig;
}
private void FindPairedDeviceInConfigFile() private void FindPairedDeviceInConfigFile()
{ {
// This function allows you to output a message about the device configuration found in the file // This function allows you to output a message about the device configuration found in the file
@@ -375,16 +455,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
} }
private void MarkAsChanged()
{
//If tracking is active, then allow changing the modifier
if (!IsModified && _isChangeTrackingActive)
{
RevertDeviceId = Devices[Device].Id; // Remember the device to undo changes
IsModified = true;
}
}
public void UnlinkDevice() public void UnlinkDevice()
{ {
// "Disabled" mode is available after unbinding the device // "Disabled" mode is available after unbinding the device
@@ -395,12 +465,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public void LoadDevice() public void LoadDevice()
{ {
int deviceIndex = 0;
if (Config == null || Config.Backend == InputBackendType.Invalid) if (Config == null || Config.Backend == InputBackendType.Invalid)
{ {
Device = 0; ApplyLoadedDevice(deviceIndex);
return;
} }
else
{
DeviceType type = DeviceType.None; DeviceType type = DeviceType.None;
if (Config is StandardKeyboardInputConfig) if (Config is StandardKeyboardInputConfig)
@@ -414,15 +486,96 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
(DeviceType Type, string Id, string Name) item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id); (DeviceType Type, string Id, string Name) item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id);
if (item != default) if (item != default)
{ {
Device = Devices.ToList().FindIndex(x => x.Id == item.Id); deviceIndex = Devices.ToList().FindIndex(x => x.Id == item.Id);
} }
else
ApplyLoadedDevice(deviceIndex);
}
private void ApplyLoadedDevice(int deviceIndex)
{ {
Device = 0; _device = deviceIndex is >= 0 and < int.MaxValue ? deviceIndex : 0;
if (_device >= Devices.Count)
{
_device = 0;
}
if (_device > 0 && Devices[_device].Type != DeviceType.None)
{
LoadControllers();
}
FindPairedDeviceInConfigFile();
OnPropertyChanged(nameof(Device));
OnPropertyChanged(nameof(SelectedDeviceItem));
NotifyChanges();
}
private void LoadSelectedDeviceControllers()
{
if (_device > 0 && _device < Devices.Count && Devices[_device].Type != DeviceType.None)
{
LoadControllers();
} }
} }
private void LoadSelectedDeviceDefaults()
{
LoadSelectedDeviceControllers();
LoadConfiguration(LoadDefaultConfiguration());
}
public void RefreshModifiedState()
{
if (!_isChangeTrackingActive)
{
return;
}
IsModified = !ConfigsMatch(GetSelectedDeviceConfig(), GetDisplayedInputConfig(GetPersistedInputConfig()));
}
private static bool ConfigsMatch(InputConfig currentConfig, InputConfig otherConfig)
{
if (currentConfig == null || otherConfig == null)
{
return currentConfig == otherConfig;
}
return JsonHelper.Serialize(currentConfig, _serializerContext.InputConfig) ==
JsonHelper.Serialize(otherConfig, _serializerContext.InputConfig);
}
private InputConfig GetSelectedDeviceConfig()
{
if (_device <= 0 || _device >= Devices.Count)
{
return null;
}
(DeviceType Type, string Id, string Name) device = Devices[_device];
InputConfig config = device.Type switch
{
DeviceType.Keyboard => (ConfigViewModel as KeyboardInputViewModel)?.Config.GetConfig(),
DeviceType.Controller => (ConfigViewModel as ControllerInputViewModel)?.Config.GetConfig(),
_ => null,
};
if (config == null)
{
return null;
}
config.Id = device.Type == DeviceType.Keyboard ? device.Id : device.Id.Split(" ")[0];
config.Name = device.Name;
config.PlayerIndex = _playerId;
config.ControllerType = Controllers[_controller].Type;
return config;
} }
private void LoadInputDriver() private void LoadInputDriver()
@@ -462,11 +615,24 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
_isChangeTrackingActive = false; // Disable configuration change tracking _isChangeTrackingActive = false; // Disable configuration change tracking
LoadDevices(); bool shouldApplyKeyboardFallback = Config is StandardControllerInputConfig controllerConfig && controllerConfig.Id == id;
RefreshAvailableDevices();
if (shouldApplyKeyboardFallback)
{
LoadConfiguration();
LoadDevice();
NotificationIsVisible = false;
IsModified = false;
NotifyChanges();
}
else
{
IsModified = true; IsModified = true;
RevertChanges(); RevertChanges();
FindPairedDeviceInConfigFile(); FindPairedDeviceInConfigFile();
}
_isChangeTrackingActive = true; // Enable configuration change tracking _isChangeTrackingActive = true; // Enable configuration change tracking
@@ -476,7 +642,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
_isChangeTrackingActive = false; // Disable configuration change tracking _isChangeTrackingActive = false; // Disable configuration change tracking
LoadDevices(); RefreshAvailableDevices();
IsModified = true; IsModified = true;
RevertChanges(); RevertChanges();
@@ -502,6 +668,23 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
return device.Id.Split(" ")[0]; return device.Id.Split(" ")[0];
} }
private string GetCurrentConfigDeviceId()
{
if (_device < 0 || _device >= Devices.Count)
{
return null;
}
(DeviceType Type, string Id, string Name) device = Devices[_device];
return device.Type switch
{
DeviceType.Keyboard => device.Id,
DeviceType.Controller => device.Id.Split(" ")[0],
_ => null,
};
}
public void LoadControllers() public void LoadControllers()
{ {
Controllers.Clear(); Controllers.Clear();
@@ -510,7 +693,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld])); Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld]));
Controller = 0; ApplyControllerSelection(0);
} }
else else
{ {
@@ -528,14 +711,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
// Workaround: set the box to 1 and then 0 // Workaround: set the box to 1 and then 0
if (controllerIndex == 0) if (controllerIndex == 0)
{ {
Controller = 1; ApplyControllerSelection(1);
} }
Controller = controllerIndex; ApplyControllerSelection(controllerIndex);
} }
else else
{ {
Controller = 0; ApplyControllerSelection(0);
} }
} }
} }
@@ -561,8 +744,16 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
return str[(str.IndexOf(Hyphen) + Offset)..]; return str[(str.IndexOf(Hyphen) + Offset)..];
} }
public void LoadDevices() private void RefreshAvailableDevices()
{ {
int selectedDeviceIndex = 0;
(DeviceType Type, string Id, string Name) selectedDevice = default;
if (_device >= 0 && _device < Devices.Count)
{
selectedDevice = Devices[_device];
}
string GetGamepadName(IGamepad gamepad, int controllerNumber) string GetGamepadName(IGamepad gamepad, int controllerNumber)
{ {
return $"{GetShortGamepadName(gamepad.Name)} ({controllerNumber})"; return $"{GetShortGamepadName(gamepad.Name)} ({controllerNumber})";
@@ -583,7 +774,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
lock (Devices) lock (Devices)
{ {
Devices.Clear(); Devices.Clear();
DeviceList.Clear();
Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled])); Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled]));
@@ -609,9 +799,20 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
} }
DeviceList.AddRange(Devices.Select(x => x.Name)); if (selectedDevice != default)
Device = Math.Min(Device, DeviceList.Count); {
selectedDeviceIndex = Devices.ToList().FindIndex(device =>
device.Type == selectedDevice.Type &&
device.Id == selectedDevice.Id);
} }
if (selectedDeviceIndex < 0)
{
selectedDeviceIndex = Math.Clamp(_device, 0, Devices.Count - 1);
}
}
ApplyLoadedDevice(selectedDeviceIndex);
} }
private string GetProfileBasePath() private string GetProfileBasePath()
@@ -677,46 +878,46 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
Id = id, Id = id,
Name = name, Name = name,
ControllerType = ControllerType.ProController, ControllerType = ControllerType.ProController,
LeftJoycon = new LeftJoyconCommonConfig<Key> LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
{ {
DpadUp = Key.Up, DpadUp = PhysicalKey.Up,
DpadDown = Key.Down, DpadDown = PhysicalKey.Down,
DpadLeft = Key.Left, DpadLeft = PhysicalKey.Left,
DpadRight = Key.Right, DpadRight = PhysicalKey.Right,
ButtonMinus = Key.Minus, ButtonMinus = PhysicalKey.Minus,
ButtonL = Key.E, ButtonL = PhysicalKey.E,
ButtonZl = Key.Q, ButtonZl = PhysicalKey.Q,
ButtonSl = Key.Unbound, ButtonSl = PhysicalKey.Unbound,
ButtonSr = Key.Unbound, ButtonSr = PhysicalKey.Unbound,
}, },
LeftJoyconStick = LeftJoyconStick =
new JoyconConfigKeyboardStick<Key> new JoyconConfigKeyboardStick<PhysicalKey>
{ {
StickUp = Key.W, StickUp = PhysicalKey.W,
StickDown = Key.S, StickDown = PhysicalKey.S,
StickLeft = Key.A, StickLeft = PhysicalKey.A,
StickRight = Key.D, StickRight = PhysicalKey.D,
StickButton = Key.F, StickButton = PhysicalKey.F,
}, },
RightJoycon = new RightJoyconCommonConfig<Key> RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
{ {
ButtonA = Key.Z, ButtonA = PhysicalKey.Z,
ButtonB = Key.X, ButtonB = PhysicalKey.X,
ButtonX = Key.C, ButtonX = PhysicalKey.C,
ButtonY = Key.V, ButtonY = PhysicalKey.V,
ButtonPlus = Key.Plus, ButtonPlus = PhysicalKey.Plus,
ButtonR = Key.U, ButtonR = PhysicalKey.U,
ButtonZr = Key.O, ButtonZr = PhysicalKey.O,
ButtonSl = Key.Unbound, ButtonSl = PhysicalKey.Unbound,
ButtonSr = Key.Unbound, ButtonSr = PhysicalKey.Unbound,
}, },
RightJoyconStick = new JoyconConfigKeyboardStick<Key> RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
{ {
StickUp = Key.I, StickUp = PhysicalKey.I,
StickDown = Key.K, StickDown = PhysicalKey.K,
StickLeft = Key.J, StickLeft = PhysicalKey.J,
StickRight = Key.L, StickRight = PhysicalKey.L,
StickButton = Key.H, StickButton = PhysicalKey.H,
}, },
}; };
} }
@@ -860,7 +1061,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
_isLoaded = false; _isLoaded = false;
config.Id = Config.Id; // Set current device id instead of changing device(independent profiles) string currentDeviceId = Config?.Id ?? GetCurrentConfigDeviceId();
if (string.IsNullOrEmpty(currentDeviceId))
{
Logger.Warning?.Print(LogClass.Configuration, $"Ignoring profile load for {ProfileName} because no active input device is selected.");
return;
}
config.Id = currentDeviceId; // Set current device id instead of changing device(independent profiles)
LoadConfiguration(config); LoadConfiguration(config);
@@ -958,9 +1166,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public void RevertChanges() public void RevertChanges()
{ {
LoadConfiguration(); // configuration preload is required if the paired gamepad was disconnected but was changed to another gamepad
Device = Devices.ToList().FindIndex(d => d.Id == RevertDeviceId);
_isLoaded = false; _isLoaded = false;
LoadConfiguration(); LoadConfiguration();
LoadDevice(); LoadDevice();
@@ -980,8 +1185,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
IsModified = false; IsModified = false;
RevertDeviceId = Devices[Device].Id; // Remember selected device after saving
List<InputConfig> newConfig = []; List<InputConfig> newConfig = [];
if (UseGlobalConfig && Program.UseExtraConfig) if (UseGlobalConfig && Program.UseExtraConfig)
@@ -1001,25 +1204,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
else else
{ {
(DeviceType Type, string Id, string Name) device = Devices[Device]; InputConfig config = GetSelectedDeviceConfig();
if (device.Type == DeviceType.Keyboard)
{
KeyboardInputConfig inputConfig = (ConfigViewModel as KeyboardInputViewModel).Config;
inputConfig.Id = device.Id;
}
else
{
GamepadInputConfig inputConfig = (ConfigViewModel as ControllerInputViewModel).Config;
inputConfig.Id = device.Id.Split(" ")[0];
}
InputConfig config = !IsController
? (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig()
: (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
config.ControllerType = Controllers[_controller].Type;
config.PlayerIndex = _playerId;
config.Name = device.Name;
int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId); int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId);
if (i == -1) if (i == -1)
@@ -1060,9 +1245,46 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
NotifyChangesEvent?.Invoke(); NotifyChangesEvent?.Invoke();
} }
private void OnPhysicalKeyLabelsChanged()
{
if (ConfigViewModel is KeyboardInputViewModel keyboardInputViewModel)
{
Dispatcher.UIThread.Post(keyboardInputViewModel.Config.NotifyKeyLabelsChanged);
}
}
private void ReplaceKeyboardDriver(Control owner)
{
Control target = TopLevel.GetTopLevel(owner) as Control ?? owner;
if (ReferenceEquals(_keyboardDriverControl, target))
{
return;
}
if (AvaloniaKeyboardDriver is AvaloniaKeyboardDriver oldKeyboardDriver)
{
oldKeyboardDriver.KeyPressed -= PhysicalKeyLabelHelper.ObserveKeyPress;
oldKeyboardDriver.Dispose();
}
_keyboardDriverControl = target;
AvaloniaKeyboardDriver keyboardDriver = new(target, KeyboardInputMode.Physical);
keyboardDriver.KeyPressed += PhysicalKeyLabelHelper.ObserveKeyPress;
AvaloniaKeyboardDriver = keyboardDriver;
if (_isLoaded && Device > 0 && Device < Devices.Count && Devices[Device].Type == DeviceType.Keyboard)
{
SelectedGamepad?.Dispose();
LoadInputDriver();
}
}
public void Dispose() public void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
PhysicalKeyLabelHelper.LabelsChanged -= OnPhysicalKeyLabelsChanged;
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
@@ -1073,7 +1295,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
SelectedGamepad?.Dispose(); SelectedGamepad?.Dispose();
AvaloniaKeyboardDriver.Dispose(); AvaloniaKeyboardDriver?.Dispose();
} }
} }
} }

View File

@@ -116,7 +116,6 @@ namespace Ryujinx.Ava.UI.Views.Input
if (e.ButtonValue.HasValue) if (e.ButtonValue.HasValue)
{ {
Button buttonValue = e.ButtonValue.Value; Button buttonValue = e.ButtonValue.Value;
FlagInputConfigChanged();
switch (button.Name) switch (button.Name)
{ {
@@ -187,6 +186,8 @@ namespace Ryujinx.Ava.UI.Views.Input
viewModel.Config.RightJoystick = buttonValue.AsHidType<StickInputId>(); viewModel.Config.RightJoystick = buttonValue.AsHidType<StickInputId>();
break; break;
} }
FlagInputConfigChanged();
} }
}; };
@@ -212,7 +213,7 @@ namespace Ryujinx.Ava.UI.Views.Input
private void FlagInputConfigChanged() private void FlagInputConfigChanged()
{ {
(DataContext as ControllerInputViewModel)!.ParentModel.IsModified = true; (DataContext as ControllerInputViewModel)!.ParentModel.RefreshModifiedState();
} }
private void MouseClick(object sender, PointerPressedEventArgs e) private void MouseClick(object sender, PointerPressedEventArgs e)

View File

@@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup" 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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
@@ -148,7 +149,7 @@
<Grid <Grid
Grid.Column="0" Grid.Column="0"
Margin="2" Margin="2"
HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*,Auto"> HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*,Auto,Auto">
<TextBlock <TextBlock
Grid.Column="0" Grid.Column="0"
Margin="5,0,10,0" Margin="5,0,10,0"
@@ -161,19 +162,38 @@
Name="DeviceBox" Name="DeviceBox"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center" VerticalAlignment="Center"
ItemsSource="{Binding DeviceList}" ItemsSource="{Binding Devices}"
SelectedIndex="{Binding Device}" /> SelectedItem="{Binding SelectedDeviceItem, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ., Converter={x:Static helpers:InputDeviceNameConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button <Button
Grid.Column="2" Grid.Column="2"
MinWidth="0" MinWidth="0"
Margin="5,0,0,0" Margin="5,0,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Command="{Binding LoadDevice}"> ToolTip.Tip="{ext:Locale ControllerSettingsRefresh}"
Command="{Binding RefreshInputDevices}">
<ui:SymbolIcon <ui:SymbolIcon
Symbol="Refresh" Symbol="Refresh"
FontSize="15" FontSize="15"
Height="20"/> Height="20"/>
</Button> </Button>
<Button
Grid.Column="3"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{ext:Locale ControllerSettingsResetKeybindsToDefault}"
Command="{Binding ResetCurrentDeviceToDefaults}">
<ui:SymbolIcon
Symbol="Undo"
FontSize="15"
Height="20"/>
</Button>
</Grid> </Grid>
<!-- Controller Type --> <!-- Controller Type -->
<Grid <Grid

View File

@@ -1,4 +1,5 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Systems.Configuration; using Ryujinx.Ava.Systems.Configuration;
@@ -15,9 +16,14 @@ namespace Ryujinx.Ava.UI.Views.Input
public InputView() 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() public void SaveCurrentProfile()
@@ -28,8 +34,18 @@ namespace Ryujinx.Ava.UI.Views.Input
public void ToggleLocalGlobalInput(bool enableConfigGlobal) public void ToggleLocalGlobalInput(bool enableConfigGlobal)
{ {
Dispose(); 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(); InitializeComponent();
if (VisualRoot is not null)
{
ViewModel.RetargetKeyboardDriver(this);
}
} }
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)

View File

@@ -12,7 +12,7 @@ using Ryujinx.Input.Assigner;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Button = Ryujinx.Input.Button; 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 namespace Ryujinx.Ava.UI.Views.Input
{ {
@@ -73,95 +73,96 @@ namespace Ryujinx.Ava.UI.Views.Input
if (be.ButtonValue.HasValue) if (be.ButtonValue.HasValue)
{ {
Button buttonValue = be.ButtonValue.Value; Button buttonValue = be.ButtonValue.Value;
ViewModel.ParentModel.IsModified = true;
switch (button.Name) switch (button.Name)
{ {
case "ButtonZl": case "ButtonZl":
ViewModel.Config.ButtonZl = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonZl = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "ButtonL": case "ButtonL":
ViewModel.Config.ButtonL = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonL = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "ButtonMinus": case "ButtonMinus":
ViewModel.Config.ButtonMinus = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonMinus = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "LeftStickButton": case "LeftStickButton":
ViewModel.Config.LeftStickButton = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftStickButton = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "LeftStickUp": case "LeftStickUp":
ViewModel.Config.LeftStickUp = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftStickUp = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "LeftStickDown": case "LeftStickDown":
ViewModel.Config.LeftStickDown = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftStickDown = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "LeftStickRight": case "LeftStickRight":
ViewModel.Config.LeftStickRight = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftStickRight = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "LeftStickLeft": case "LeftStickLeft":
ViewModel.Config.LeftStickLeft = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftStickLeft = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "DpadUp": case "DpadUp":
ViewModel.Config.DpadUp = buttonValue.AsHidType<Key>(); ViewModel.Config.DpadUp = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "DpadDown": case "DpadDown":
ViewModel.Config.DpadDown = buttonValue.AsHidType<Key>(); ViewModel.Config.DpadDown = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "DpadLeft": case "DpadLeft":
ViewModel.Config.DpadLeft = buttonValue.AsHidType<Key>(); ViewModel.Config.DpadLeft = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "DpadRight": case "DpadRight":
ViewModel.Config.DpadRight = buttonValue.AsHidType<Key>(); ViewModel.Config.DpadRight = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "LeftButtonSr": case "LeftButtonSr":
ViewModel.Config.LeftButtonSr = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftButtonSr = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "LeftButtonSl": case "LeftButtonSl":
ViewModel.Config.LeftButtonSl = buttonValue.AsHidType<Key>(); ViewModel.Config.LeftButtonSl = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "RightButtonSr": case "RightButtonSr":
ViewModel.Config.RightButtonSr = buttonValue.AsHidType<Key>(); ViewModel.Config.RightButtonSr = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "RightButtonSl": case "RightButtonSl":
ViewModel.Config.RightButtonSl = buttonValue.AsHidType<Key>(); ViewModel.Config.RightButtonSl = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "ButtonZr": case "ButtonZr":
ViewModel.Config.ButtonZr = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonZr = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "ButtonR": case "ButtonR":
ViewModel.Config.ButtonR = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonR = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "ButtonPlus": case "ButtonPlus":
ViewModel.Config.ButtonPlus = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonPlus = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "ButtonA": case "ButtonA":
ViewModel.Config.ButtonA = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonA = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "ButtonB": case "ButtonB":
ViewModel.Config.ButtonB = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonB = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "ButtonX": case "ButtonX":
ViewModel.Config.ButtonX = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonX = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "ButtonY": case "ButtonY":
ViewModel.Config.ButtonY = buttonValue.AsHidType<Key>(); ViewModel.Config.ButtonY = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "RightStickButton": case "RightStickButton":
ViewModel.Config.RightStickButton = buttonValue.AsHidType<Key>(); ViewModel.Config.RightStickButton = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "RightStickUp": case "RightStickUp":
ViewModel.Config.RightStickUp = buttonValue.AsHidType<Key>(); ViewModel.Config.RightStickUp = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "RightStickDown": case "RightStickDown":
ViewModel.Config.RightStickDown = buttonValue.AsHidType<Key>(); ViewModel.Config.RightStickDown = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "RightStickRight": case "RightStickRight":
ViewModel.Config.RightStickRight = buttonValue.AsHidType<Key>(); ViewModel.Config.RightStickRight = buttonValue.AsHidType<PhysicalKey>();
break; break;
case "RightStickLeft": case "RightStickLeft":
ViewModel.Config.RightStickLeft = buttonValue.AsHidType<Key>(); ViewModel.Config.RightStickLeft = buttonValue.AsHidType<PhysicalKey>();
break; break;
} }
ViewModel.ParentModel.RefreshModifiedState();
} }
}; };
@@ -207,40 +208,40 @@ namespace Ryujinx.Ava.UI.Views.Input
{ {
Dictionary<string, Action> buttonActions = new() Dictionary<string, Action> buttonActions = new()
{ {
{ "ButtonZl", () => ViewModel.Config.ButtonZl = Key.Unbound }, { "ButtonZl", () => ViewModel.Config.ButtonZl = PhysicalKey.Unbound },
{ "ButtonL", () => ViewModel.Config.ButtonL = Key.Unbound }, { "ButtonL", () => ViewModel.Config.ButtonL = PhysicalKey.Unbound },
{ "ButtonMinus", () => ViewModel.Config.ButtonMinus = Key.Unbound }, { "ButtonMinus", () => ViewModel.Config.ButtonMinus = PhysicalKey.Unbound },
{ "LeftStickButton", () => ViewModel.Config.LeftStickButton = Key.Unbound }, { "LeftStickButton", () => ViewModel.Config.LeftStickButton = PhysicalKey.Unbound },
{ "LeftStickUp", () => ViewModel.Config.LeftStickUp = Key.Unbound }, { "LeftStickUp", () => ViewModel.Config.LeftStickUp = PhysicalKey.Unbound },
{ "LeftStickDown", () => ViewModel.Config.LeftStickDown = Key.Unbound }, { "LeftStickDown", () => ViewModel.Config.LeftStickDown = PhysicalKey.Unbound },
{ "LeftStickRight", () => ViewModel.Config.LeftStickRight = Key.Unbound }, { "LeftStickRight", () => ViewModel.Config.LeftStickRight = PhysicalKey.Unbound },
{ "LeftStickLeft", () => ViewModel.Config.LeftStickLeft = Key.Unbound }, { "LeftStickLeft", () => ViewModel.Config.LeftStickLeft = PhysicalKey.Unbound },
{ "DpadUp", () => ViewModel.Config.DpadUp = Key.Unbound }, { "DpadUp", () => ViewModel.Config.DpadUp = PhysicalKey.Unbound },
{ "DpadDown", () => ViewModel.Config.DpadDown = Key.Unbound }, { "DpadDown", () => ViewModel.Config.DpadDown = PhysicalKey.Unbound },
{ "DpadLeft", () => ViewModel.Config.DpadLeft = Key.Unbound }, { "DpadLeft", () => ViewModel.Config.DpadLeft = PhysicalKey.Unbound },
{ "DpadRight", () => ViewModel.Config.DpadRight = Key.Unbound }, { "DpadRight", () => ViewModel.Config.DpadRight = PhysicalKey.Unbound },
{ "LeftButtonSr", () => ViewModel.Config.LeftButtonSr = Key.Unbound }, { "LeftButtonSr", () => ViewModel.Config.LeftButtonSr = PhysicalKey.Unbound },
{ "LeftButtonSl", () => ViewModel.Config.LeftButtonSl = Key.Unbound }, { "LeftButtonSl", () => ViewModel.Config.LeftButtonSl = PhysicalKey.Unbound },
{ "RightButtonSr", () => ViewModel.Config.RightButtonSr = Key.Unbound }, { "RightButtonSr", () => ViewModel.Config.RightButtonSr = PhysicalKey.Unbound },
{ "RightButtonSl", () => ViewModel.Config.RightButtonSl = Key.Unbound }, { "RightButtonSl", () => ViewModel.Config.RightButtonSl = PhysicalKey.Unbound },
{ "ButtonZr", () => ViewModel.Config.ButtonZr = Key.Unbound }, { "ButtonZr", () => ViewModel.Config.ButtonZr = PhysicalKey.Unbound },
{ "ButtonR", () => ViewModel.Config.ButtonR = Key.Unbound }, { "ButtonR", () => ViewModel.Config.ButtonR = PhysicalKey.Unbound },
{ "ButtonPlus", () => ViewModel.Config.ButtonPlus = Key.Unbound }, { "ButtonPlus", () => ViewModel.Config.ButtonPlus = PhysicalKey.Unbound },
{ "ButtonA", () => ViewModel.Config.ButtonA = Key.Unbound }, { "ButtonA", () => ViewModel.Config.ButtonA = PhysicalKey.Unbound },
{ "ButtonB", () => ViewModel.Config.ButtonB = Key.Unbound }, { "ButtonB", () => ViewModel.Config.ButtonB = PhysicalKey.Unbound },
{ "ButtonX", () => ViewModel.Config.ButtonX = Key.Unbound }, { "ButtonX", () => ViewModel.Config.ButtonX = PhysicalKey.Unbound },
{ "ButtonY", () => ViewModel.Config.ButtonY = Key.Unbound }, { "ButtonY", () => ViewModel.Config.ButtonY = PhysicalKey.Unbound },
{ "RightStickButton", () => ViewModel.Config.RightStickButton = Key.Unbound }, { "RightStickButton", () => ViewModel.Config.RightStickButton = PhysicalKey.Unbound },
{ "RightStickUp", () => ViewModel.Config.RightStickUp = Key.Unbound }, { "RightStickUp", () => ViewModel.Config.RightStickUp = PhysicalKey.Unbound },
{ "RightStickDown", () => ViewModel.Config.RightStickDown = Key.Unbound }, { "RightStickDown", () => ViewModel.Config.RightStickDown = PhysicalKey.Unbound },
{ "RightStickRight", () => ViewModel.Config.RightStickRight = Key.Unbound }, { "RightStickRight", () => ViewModel.Config.RightStickRight = PhysicalKey.Unbound },
{ "RightStickLeft", () => ViewModel.Config.RightStickLeft = Key.Unbound } { "RightStickLeft", () => ViewModel.Config.RightStickLeft = PhysicalKey.Unbound }
}; };
if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action)) if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action))
{ {
action(); action();
ViewModel.ParentModel.IsModified = true; ViewModel.ParentModel.RefreshModifiedState();
} }
} }
} }

View File

@@ -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) protected override void OnPointerReleased(PointerReleasedEventArgs e)

View File

@@ -30,6 +30,7 @@ using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input.HLE; using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL3; using Ryujinx.Input.SDL3;
using Ryujinx.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -105,7 +106,9 @@ namespace Ryujinx.Ava.UI.Windows
if (Program.PreviewerDetached) 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.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it);
this.ScalingChanged += OnScalingChanged; this.ScalingChanged += OnScalingChanged;