Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e0b6a01e9d | |||
| e509ffa716 | |||
| 412d4065b8 | |||
| 714c68b548 | |||
| fec197d9ec | |||
| a4b2feef79 | |||
| ad7d9d1ce0 | |||
| 86f9544910 | |||
| e9ecbd44fc |
@@ -11,6 +11,7 @@ namespace Ryujinx.Input.SDL2
|
|||||||
private readonly Dictionary<int, string> _gamepadsInstanceIdsMapping;
|
private readonly Dictionary<int, string> _gamepadsInstanceIdsMapping;
|
||||||
private readonly List<string> _gamepadsIds;
|
private readonly List<string> _gamepadsIds;
|
||||||
private readonly Lock _lock = new();
|
private readonly Lock _lock = new();
|
||||||
|
private readonly SDL2JoyConPair joyConPair;
|
||||||
|
|
||||||
public ReadOnlySpan<string> GamepadsIds
|
public ReadOnlySpan<string> GamepadsIds
|
||||||
{
|
{
|
||||||
@@ -36,7 +37,7 @@ namespace Ryujinx.Input.SDL2
|
|||||||
SDL2Driver.Instance.Initialize();
|
SDL2Driver.Instance.Initialize();
|
||||||
SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
|
SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
|
||||||
SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected;
|
SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected;
|
||||||
|
joyConPair = new SDL2JoyConPair();
|
||||||
// Add already connected gamepads
|
// Add already connected gamepads
|
||||||
int numJoysticks = SDL_NumJoysticks();
|
int numJoysticks = SDL_NumJoysticks();
|
||||||
|
|
||||||
@@ -89,6 +90,10 @@ namespace Ryujinx.Input.SDL2
|
|||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
_gamepadsIds.Remove(id);
|
_gamepadsIds.Remove(id);
|
||||||
|
if (joyConPair.GetGamepad(_gamepadsIds) == null)
|
||||||
|
{
|
||||||
|
_gamepadsIds.Remove(joyConPair.Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OnGamepadDisconnected?.Invoke(id);
|
OnGamepadDisconnected?.Invoke(id);
|
||||||
@@ -120,6 +125,11 @@ namespace Ryujinx.Input.SDL2
|
|||||||
_gamepadsIds.Insert(joystickDeviceId, id);
|
_gamepadsIds.Insert(joystickDeviceId, id);
|
||||||
else
|
else
|
||||||
_gamepadsIds.Add(id);
|
_gamepadsIds.Add(id);
|
||||||
|
if (joyConPair.GetGamepad(_gamepadsIds) != null)
|
||||||
|
{
|
||||||
|
_gamepadsIds.Remove(joyConPair.Id);
|
||||||
|
_gamepadsIds.Add(joyConPair.Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OnGamepadConnected?.Invoke(id);
|
OnGamepadConnected?.Invoke(id);
|
||||||
@@ -157,6 +167,14 @@ namespace Ryujinx.Input.SDL2
|
|||||||
|
|
||||||
public IGamepad GetGamepad(string id)
|
public IGamepad GetGamepad(string id)
|
||||||
{
|
{
|
||||||
|
if (id == joyConPair.Id)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return joyConPair.GetGamepad(_gamepadsIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int joystickIndex = GetJoystickIndexByGamepadId(id);
|
int joystickIndex = GetJoystickIndexByGamepadId(id);
|
||||||
|
|
||||||
if (joystickIndex == -1)
|
if (joystickIndex == -1)
|
||||||
@@ -171,6 +189,7 @@ namespace Ryujinx.Input.SDL2
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Game controller opened" + SDL_GameControllerName(gamepadHandle));
|
||||||
return new SDL2Gamepad(gamepadHandle, id);
|
return new SDL2Gamepad(gamepadHandle, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,281 @@
|
|||||||
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
using static SDL2.SDL;
|
||||||
|
using Timer = System.Timers.Timer;
|
||||||
|
|
||||||
|
namespace Ryujinx.Input.SDL2
|
||||||
|
{
|
||||||
|
internal class SDL2JoyConPair : IGamepad
|
||||||
|
{
|
||||||
|
private IGamepad _left;
|
||||||
|
private IGamepad _right;
|
||||||
|
private Timer timer;
|
||||||
|
private StandardControllerInputConfig _configuration;
|
||||||
|
|
||||||
|
private readonly StickInputId[] _stickUserMapping =
|
||||||
|
[
|
||||||
|
StickInputId.Unbound,
|
||||||
|
StickInputId.Left,
|
||||||
|
StickInputId.Right
|
||||||
|
];
|
||||||
|
|
||||||
|
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From)
|
||||||
|
{
|
||||||
|
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<ButtonMappingEntry> _buttonsUserMapping = new(20);
|
||||||
|
|
||||||
|
private readonly Lock _userMappingLock = new();
|
||||||
|
|
||||||
|
public GamepadFeaturesFlag Features => (_left?.Features ?? GamepadFeaturesFlag.None) |
|
||||||
|
(_right?.Features ?? GamepadFeaturesFlag.None);
|
||||||
|
|
||||||
|
public string Id => "JoyConPair";
|
||||||
|
|
||||||
|
public string Name => "Nintendo Switch Joy-Con (L/R)";
|
||||||
|
private const string LeftName = "Nintendo Switch Joy-Con (L)";
|
||||||
|
private const string RightName = "Nintendo Switch Joy-Con (R)";
|
||||||
|
public bool IsConnected => _left is { IsConnected: true } && _right is { IsConnected: true };
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_left?.Dispose();
|
||||||
|
_right?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||||
|
{
|
||||||
|
GamepadStateSnapshot rawState = GetStateSnapshot();
|
||||||
|
GamepadStateSnapshot result = default;
|
||||||
|
|
||||||
|
lock (_userMappingLock)
|
||||||
|
{
|
||||||
|
if (_buttonsUserMapping.Count == 0)
|
||||||
|
return rawState;
|
||||||
|
|
||||||
|
|
||||||
|
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
|
||||||
|
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
|
||||||
|
{
|
||||||
|
if (!entry.IsValid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Do not touch state of button already pressed
|
||||||
|
if (!result.IsPressed(entry.To))
|
||||||
|
{
|
||||||
|
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(float leftStickX, float leftStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Left]);
|
||||||
|
(float rightStickX, float rightStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Right]);
|
||||||
|
|
||||||
|
result.SetStick(StickInputId.Left, leftStickX, leftStickY);
|
||||||
|
result.SetStick(StickInputId.Right, rightStickX, rightStickY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetMotionData(MotionInputId inputId)
|
||||||
|
{
|
||||||
|
return inputId switch
|
||||||
|
{
|
||||||
|
MotionInputId.SecondAccelerometer => _right.GetMotionData(MotionInputId.Accelerometer),
|
||||||
|
MotionInputId.SecondGyroscope => _right.GetMotionData(MotionInputId.Gyroscope),
|
||||||
|
_ => _left.GetMotionData(inputId)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public GamepadStateSnapshot GetStateSnapshot()
|
||||||
|
{
|
||||||
|
return IGamepad.GetStateSnapshot(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (float, float) GetStick(StickInputId inputId)
|
||||||
|
{
|
||||||
|
switch (inputId)
|
||||||
|
{
|
||||||
|
case StickInputId.Left:
|
||||||
|
{
|
||||||
|
(float x, float y) = _left.GetStick(StickInputId.Left);
|
||||||
|
return (y, -x);
|
||||||
|
}
|
||||||
|
case StickInputId.Right:
|
||||||
|
{
|
||||||
|
(float x, float y) = _right.GetStick(StickInputId.Left);
|
||||||
|
return (-y, x);
|
||||||
|
}
|
||||||
|
case StickInputId.Unbound:
|
||||||
|
case StickInputId.Count:
|
||||||
|
default:
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPressed(GamepadButtonInputId inputId)
|
||||||
|
{
|
||||||
|
return inputId switch
|
||||||
|
{
|
||||||
|
GamepadButtonInputId.LeftStick => _left.IsPressed(GamepadButtonInputId.LeftStick),
|
||||||
|
GamepadButtonInputId.DpadUp => _left.IsPressed(GamepadButtonInputId.Y),
|
||||||
|
GamepadButtonInputId.DpadDown => _left.IsPressed(GamepadButtonInputId.A),
|
||||||
|
GamepadButtonInputId.DpadLeft => _left.IsPressed(GamepadButtonInputId.B),
|
||||||
|
GamepadButtonInputId.DpadRight => _left.IsPressed(GamepadButtonInputId.X),
|
||||||
|
GamepadButtonInputId.Minus => _left.IsPressed(GamepadButtonInputId.Start),
|
||||||
|
GamepadButtonInputId.LeftShoulder => _left.IsPressed(GamepadButtonInputId.Paddle2),
|
||||||
|
GamepadButtonInputId.LeftTrigger => _left.IsPressed(GamepadButtonInputId.Paddle4),
|
||||||
|
GamepadButtonInputId.SingleRightTrigger0 => _left.IsPressed(GamepadButtonInputId.LeftShoulder),
|
||||||
|
GamepadButtonInputId.SingleLeftTrigger0 => _left.IsPressed(GamepadButtonInputId.RightShoulder),
|
||||||
|
|
||||||
|
GamepadButtonInputId.RightStick => _right.IsPressed(GamepadButtonInputId.LeftStick),
|
||||||
|
GamepadButtonInputId.A => _right.IsPressed(GamepadButtonInputId.B),
|
||||||
|
GamepadButtonInputId.B => _right.IsPressed(GamepadButtonInputId.Y),
|
||||||
|
GamepadButtonInputId.X => _right.IsPressed(GamepadButtonInputId.A),
|
||||||
|
GamepadButtonInputId.Y => _right.IsPressed(GamepadButtonInputId.X),
|
||||||
|
GamepadButtonInputId.Plus => _right.IsPressed(GamepadButtonInputId.Start),
|
||||||
|
GamepadButtonInputId.RightShoulder => _right.IsPressed(GamepadButtonInputId.Paddle1),
|
||||||
|
GamepadButtonInputId.RightTrigger => _right.IsPressed(GamepadButtonInputId.Paddle3),
|
||||||
|
GamepadButtonInputId.SingleRightTrigger1 => _right.IsPressed(GamepadButtonInputId.LeftShoulder),
|
||||||
|
GamepadButtonInputId.SingleLeftTrigger1 => _right.IsPressed(GamepadButtonInputId.RightShoulder),
|
||||||
|
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
|
{
|
||||||
|
if (lowFrequency != 0)
|
||||||
|
{
|
||||||
|
_right.Rumble(lowFrequency, lowFrequency, durationMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (highFrequency != 0)
|
||||||
|
{
|
||||||
|
_left.Rumble(highFrequency, highFrequency, durationMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lowFrequency == 0 && highFrequency == 0)
|
||||||
|
{
|
||||||
|
_left.Rumble(0, 0, durationMs);
|
||||||
|
_right.Rumble(0, 0, durationMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetConfiguration(InputConfig configuration)
|
||||||
|
{
|
||||||
|
lock (_userMappingLock)
|
||||||
|
{
|
||||||
|
_configuration = (StandardControllerInputConfig)configuration;
|
||||||
|
_left.SetConfiguration(configuration);
|
||||||
|
_right.SetConfiguration(configuration);
|
||||||
|
|
||||||
|
_buttonsUserMapping.Clear();
|
||||||
|
|
||||||
|
// First update sticks
|
||||||
|
_stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick;
|
||||||
|
_stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick;
|
||||||
|
|
||||||
|
// Then left joycon
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick,
|
||||||
|
(GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp,
|
||||||
|
(GamepadButtonInputId)_configuration.LeftJoycon.DpadUp));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown,
|
||||||
|
(GamepadButtonInputId)_configuration.LeftJoycon.DpadDown));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft,
|
||||||
|
(GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight,
|
||||||
|
(GamepadButtonInputId)_configuration.LeftJoycon.DpadRight));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus,
|
||||||
|
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder,
|
||||||
|
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonL));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger,
|
||||||
|
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0,
|
||||||
|
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0,
|
||||||
|
(GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl));
|
||||||
|
|
||||||
|
// Finally right joycon
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick,
|
||||||
|
(GamepadButtonInputId)_configuration.RightJoyconStick.StickButton));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A,
|
||||||
|
(GamepadButtonInputId)_configuration.RightJoycon.ButtonA));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B,
|
||||||
|
(GamepadButtonInputId)_configuration.RightJoycon.ButtonB));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X,
|
||||||
|
(GamepadButtonInputId)_configuration.RightJoycon.ButtonX));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y,
|
||||||
|
(GamepadButtonInputId)_configuration.RightJoycon.ButtonY));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus,
|
||||||
|
(GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder,
|
||||||
|
(GamepadButtonInputId)_configuration.RightJoycon.ButtonR));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger,
|
||||||
|
(GamepadButtonInputId)_configuration.RightJoycon.ButtonZr));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1,
|
||||||
|
(GamepadButtonInputId)_configuration.RightJoycon.ButtonSr));
|
||||||
|
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1,
|
||||||
|
(GamepadButtonInputId)_configuration.RightJoycon.ButtonSl));
|
||||||
|
|
||||||
|
SetTriggerThreshold(_configuration.TriggerThreshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTriggerThreshold(float triggerThreshold)
|
||||||
|
{
|
||||||
|
_left.SetTriggerThreshold(triggerThreshold);
|
||||||
|
_right.SetTriggerThreshold(triggerThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IGamepad GetGamepad(List<string> gamepadsIds)
|
||||||
|
{
|
||||||
|
this.Dispose();
|
||||||
|
var gamepadNames = gamepadsIds.Where(gamepadId => gamepadId != Id)
|
||||||
|
.Select((_, index) => SDL_GameControllerNameForIndex(index)).ToList();
|
||||||
|
int leftIndex = gamepadNames.IndexOf(LeftName);
|
||||||
|
int rightIndex = gamepadNames.IndexOf(RightName);
|
||||||
|
|
||||||
|
if (leftIndex == -1 || rightIndex == -1)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
nint leftGamepadHandle = SDL_GameControllerOpen(leftIndex);
|
||||||
|
nint rightGamepadHandle = SDL_GameControllerOpen(rightIndex);
|
||||||
|
|
||||||
|
if (leftGamepadHandle == nint.Zero || rightGamepadHandle == nint.Zero)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_left = new SDL2Gamepad(leftGamepadHandle, gamepadsIds[leftIndex]);
|
||||||
|
_right = new SDL2Gamepad(rightGamepadHandle, gamepadsIds[rightIndex]);
|
||||||
|
ShowPowerLevel(leftGamepadHandle, rightGamepadHandle);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowPowerLevel(nint leftGamepadHandle, nint rightGamepadHandle)
|
||||||
|
{
|
||||||
|
timer?.Stop();
|
||||||
|
timer = new Timer(2000);
|
||||||
|
timer.Elapsed += (_, _) =>
|
||||||
|
{
|
||||||
|
timer.Stop();
|
||||||
|
var leftLevel = SDL_JoystickCurrentPowerLevel(SDL_GameControllerGetJoystick(leftGamepadHandle));
|
||||||
|
var rightLevel = SDL_JoystickCurrentPowerLevel(SDL_GameControllerGetJoystick(rightGamepadHandle));
|
||||||
|
Logger.Info?.Print(LogClass.Hid, $"Left power level: {leftLevel}, Right power level: {rightLevel}");
|
||||||
|
};
|
||||||
|
timer.AutoReset = false;
|
||||||
|
timer.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -266,6 +266,7 @@ namespace Ryujinx.Input.HLE
|
|||||||
if (motionConfig.MotionBackend != MotionInputBackendType.CemuHook)
|
if (motionConfig.MotionBackend != MotionInputBackendType.CemuHook)
|
||||||
{
|
{
|
||||||
_leftMotionInput = new MotionInput();
|
_leftMotionInput = new MotionInput();
|
||||||
|
_rightMotionInput = new MotionInput();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -298,7 +299,20 @@ namespace Ryujinx.Input.HLE
|
|||||||
|
|
||||||
if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
|
if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
|
||||||
{
|
{
|
||||||
_rightMotionInput = _leftMotionInput;
|
if (gamepad.Id== "JoyConPair")
|
||||||
|
{
|
||||||
|
Vector3 rightAccelerometer = gamepad.GetMotionData(MotionInputId.SecondAccelerometer);
|
||||||
|
Vector3 rightGyroscope = gamepad.GetMotionData(MotionInputId.SecondGyroscope);
|
||||||
|
|
||||||
|
rightAccelerometer = new Vector3(rightAccelerometer.X, -rightAccelerometer.Z, rightAccelerometer.Y);
|
||||||
|
rightGyroscope = new Vector3(rightGyroscope.X, -rightGyroscope.Z, rightGyroscope.Y);
|
||||||
|
|
||||||
|
_rightMotionInput.Update(rightAccelerometer, rightGyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_rightMotionInput = _leftMotionInput;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,6 +347,7 @@ namespace Ryujinx.Input.HLE
|
|||||||
// Reset states
|
// Reset states
|
||||||
State = default;
|
State = default;
|
||||||
_leftMotionInput = null;
|
_leftMotionInput = null;
|
||||||
|
_rightMotionInput = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,5 +21,17 @@ namespace Ryujinx.Input
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Values are in degrees</remarks>
|
/// <remarks>Values are in degrees</remarks>
|
||||||
Gyroscope,
|
Gyroscope,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Second accelerometer.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Values are in m/s^2</remarks>
|
||||||
|
SecondAccelerometer,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Second gyroscope.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Values are in degrees</remarks>
|
||||||
|
SecondGyroscope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Ncm;
|
||||||
|
using LibHac.Ns;
|
||||||
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using Ryujinx.HLE;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.UI.App.Common;
|
||||||
|
|
||||||
|
namespace Ryujinx.UI.Common.Helper
|
||||||
|
{
|
||||||
|
public readonly struct AppletMetadata
|
||||||
|
{
|
||||||
|
private readonly ContentManager _contentManager;
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
public ulong ProgramId { get; }
|
||||||
|
|
||||||
|
public string Version { get; }
|
||||||
|
|
||||||
|
public AppletMetadata(ContentManager contentManager, string name, ulong programId, string version = "1.0.0")
|
||||||
|
: this(name, programId, version)
|
||||||
|
{
|
||||||
|
_contentManager = contentManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppletMetadata(string name, ulong programId, string version = "1.0.0")
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
ProgramId = programId;
|
||||||
|
Version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetContentPath(ContentManager contentManager)
|
||||||
|
=> (contentManager ?? _contentManager)
|
||||||
|
.GetInstalledContentPath(ProgramId, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||||
|
|
||||||
|
public bool CanStart(ContentManager contentManager, out ApplicationData appData, out BlitStruct<ApplicationControlProperty> appControl)
|
||||||
|
{
|
||||||
|
contentManager ??= _contentManager;
|
||||||
|
if (contentManager == null)
|
||||||
|
{
|
||||||
|
appData = null;
|
||||||
|
appControl = new BlitStruct<ApplicationControlProperty>(0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
appData = new()
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
Id = ProgramId,
|
||||||
|
Path = GetContentPath(contentManager)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(appData.Path))
|
||||||
|
{
|
||||||
|
appControl = new BlitStruct<ApplicationControlProperty>(0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
appControl = StructHelpers.CreateCustomNacpData(Name, Version);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using Gommon;
|
using Gommon;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using System;
|
using System;
|
||||||
@@ -17,7 +16,6 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
private const string DefaultLanguageCode = "en_US";
|
private const string DefaultLanguageCode = "en_US";
|
||||||
|
|
||||||
private readonly Dictionary<LocaleKeys, string> _localeStrings;
|
private readonly Dictionary<LocaleKeys, string> _localeStrings;
|
||||||
private Dictionary<LocaleKeys, string> _localeDefaultStrings;
|
|
||||||
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
|
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
|
||||||
private string _localeLanguageCode;
|
private string _localeLanguageCode;
|
||||||
|
|
||||||
@@ -27,7 +25,6 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
public LocaleManager()
|
public LocaleManager()
|
||||||
{
|
{
|
||||||
_localeStrings = new Dictionary<LocaleKeys, string>();
|
_localeStrings = new Dictionary<LocaleKeys, string>();
|
||||||
_localeDefaultStrings = new Dictionary<LocaleKeys, string>();
|
|
||||||
_dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>();
|
_dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>();
|
||||||
|
|
||||||
Load();
|
Load();
|
||||||
@@ -37,9 +34,7 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
{
|
{
|
||||||
var localeLanguageCode = !string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value) ?
|
var localeLanguageCode = !string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value) ?
|
||||||
ConfigurationState.Instance.UI.LanguageCode.Value : CultureInfo.CurrentCulture.Name.Replace('-', '_');
|
ConfigurationState.Instance.UI.LanguageCode.Value : CultureInfo.CurrentCulture.Name.Replace('-', '_');
|
||||||
|
|
||||||
// Load en_US as default, if the target language translation is missing or incomplete.
|
|
||||||
LoadDefaultLanguage();
|
|
||||||
LoadLanguage(localeLanguageCode);
|
LoadLanguage(localeLanguageCode);
|
||||||
|
|
||||||
// Save whatever we ended up with.
|
// Save whatever we ended up with.
|
||||||
@@ -66,26 +61,14 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// If formatting failed use the default text instead.
|
// If formatting the text failed,
|
||||||
if (_localeDefaultStrings.TryGetValue(key, out value))
|
// continue to the below line & return the text without formatting.
|
||||||
try
|
|
||||||
{
|
|
||||||
return string.Format(value, dynamicValue);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// If formatting the default text failed return the key.
|
|
||||||
return key.ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the locale doesn't contain the key return the default one.
|
return key.ToString(); // If the locale text doesn't exist return the key.
|
||||||
return _localeDefaultStrings.TryGetValue(key, out string defaultValue)
|
|
||||||
? defaultValue
|
|
||||||
: key.ToString(); // If the locale text doesn't exist return the key.
|
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
@@ -114,11 +97,6 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
return this[key];
|
return this[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadDefaultLanguage()
|
|
||||||
{
|
|
||||||
_localeDefaultStrings = LoadJsonLanguage(DefaultLanguageCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadLanguage(string languageCode)
|
public void LoadLanguage(string languageCode)
|
||||||
{
|
{
|
||||||
var locale = LoadJsonLanguage(languageCode);
|
var locale = LoadJsonLanguage(languageCode);
|
||||||
@@ -126,7 +104,7 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
if (locale == null)
|
if (locale == null)
|
||||||
{
|
{
|
||||||
_localeLanguageCode = DefaultLanguageCode;
|
_localeLanguageCode = DefaultLanguageCode;
|
||||||
locale = _localeDefaultStrings;
|
locale = LoadJsonLanguage(_localeLanguageCode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -167,15 +145,16 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
if (!Enum.TryParse<LocaleKeys>(locale.ID, out var localeKey))
|
if (!Enum.TryParse<LocaleKeys>(locale.ID, out var localeKey))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
localeStrings[localeKey] =
|
var str = locale.Translations.TryGetValue(languageCode, out string val) && !string.IsNullOrEmpty(val)
|
||||||
locale.Translations.TryGetValue(languageCode, out string val) && !string.IsNullOrEmpty(val)
|
? val
|
||||||
? val
|
: locale.Translations[DefaultLanguageCode];
|
||||||
: locale.Translations[DefaultLanguageCode];
|
|
||||||
|
if (string.IsNullOrEmpty(str))
|
||||||
if (string.IsNullOrEmpty(localeStrings[localeKey]))
|
|
||||||
{
|
{
|
||||||
throw new Exception($"Locale key '{locale.ID}' has no valid translations for desired language {languageCode}! {DefaultLanguageCode} is an empty string or null");
|
throw new Exception($"Locale key '{locale.ID}' has no valid translations for desired language {languageCode}! {DefaultLanguageCode} is an empty string or null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localeStrings[localeKey] = str;
|
||||||
}
|
}
|
||||||
|
|
||||||
return localeStrings;
|
return localeStrings;
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Gommon;
|
using Gommon;
|
||||||
using LibHac.Ncm;
|
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
@@ -12,8 +10,6 @@ using Ryujinx.Ava.UI.Windows;
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
|
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
|
||||||
using Ryujinx.HLE;
|
|
||||||
using Ryujinx.UI.App.Common;
|
|
||||||
using Ryujinx.UI.Common;
|
using Ryujinx.UI.Common;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
@@ -124,26 +120,13 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
ViewModel.LoadConfigurableHotKeys();
|
ViewModel.LoadConfigurableHotKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly AppletMetadata MiiApplet = new("miiEdit", 0x0100000000001009);
|
||||||
|
|
||||||
public async void OpenMiiApplet(object sender, RoutedEventArgs e)
|
public async void OpenMiiApplet(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
const string AppletName = "miiEdit";
|
if (MiiApplet.CanStart(ViewModel.ContentManager, out var appData, out var nacpData))
|
||||||
const ulong AppletProgramId = 0x0100000000001009;
|
|
||||||
const string AppletVersion = "1.0.0";
|
|
||||||
|
|
||||||
string contentPath = ViewModel.ContentManager.GetInstalledContentPath(AppletProgramId, StorageId.BuiltInSystem, NcaContentType.Program);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(contentPath))
|
|
||||||
{
|
{
|
||||||
ApplicationData applicationData = new()
|
await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
|
||||||
{
|
|
||||||
Name = AppletName,
|
|
||||||
Id = AppletProgramId,
|
|
||||||
Path = contentPath
|
|
||||||
};
|
|
||||||
|
|
||||||
var nacpData = StructHelpers.CreateCustomNacpData(AppletName, AppletVersion);
|
|
||||||
|
|
||||||
await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user