Pr 434 #523
@ -253,11 +253,23 @@ namespace Ryujinx.Input.SDL2
|
|||||||
return IGamepad.GetStateSnapshot(this);
|
return IGamepad.GetStateSnapshot(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool hotButtonMinus = false;
|
||||||
|
private static bool hotExit = false;
|
||||||
|
|
||||||
|
public bool SpecialExit()
|
||||||
|
{
|
||||||
|
if (hotButtonMinus)
|
||||||
|
{
|
||||||
|
hotButtonMinus = false;
|
||||||
|
return hotExit;
|
||||||
|
}
|
||||||
|
return hotExit = false;
|
||||||
|
}
|
||||||
|
|
||||||
public GamepadStateSnapshot GetMappedStateSnapshot()
|
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||||
{
|
{
|
||||||
GamepadStateSnapshot rawState = GetStateSnapshot();
|
GamepadStateSnapshot rawState = GetStateSnapshot();
|
||||||
GamepadStateSnapshot result = default;
|
GamepadStateSnapshot result = default;
|
||||||
|
|
||||||
lock (_userMappingLock)
|
lock (_userMappingLock)
|
||||||
{
|
{
|
||||||
if (_buttonsUserMapping.Count == 0)
|
if (_buttonsUserMapping.Count == 0)
|
||||||
@ -270,6 +282,28 @@ namespace Ryujinx.Input.SDL2
|
|||||||
if (!entry.IsValid)
|
if (!entry.IsValid)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (GamepadButtonInputId.Minus == entry.To)
|
||||||
|
{
|
||||||
|
if (rawState.IsPressed(entry.From) && !hotButtonMinus)
|
||||||
|
{
|
||||||
|
hotButtonMinus = true;
|
||||||
|
}
|
||||||
|
else if (!result.IsPressed(entry.From) && hotButtonMinus)
|
||||||
|
{
|
||||||
|
hotButtonMinus = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GamepadButtonInputId.Plus == entry.To)
|
||||||
|
{
|
||||||
|
if (rawState.IsPressed(entry.To) && hotButtonMinus)
|
||||||
|
{
|
||||||
|
|
||||||
|
hotExit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Do not touch state of button already pressed
|
// Do not touch state of button already pressed
|
||||||
if (!result.IsPressed(entry.To))
|
if (!result.IsPressed(entry.To))
|
||||||
{
|
{
|
||||||
@ -376,5 +410,7 @@ namespace Ryujinx.Input.SDL2
|
|||||||
|
|
||||||
return SDL_GameControllerGetButton(_gamepadHandle, _buttonsDriverMapping[(int)inputId]) == 1;
|
return SDL_GameControllerGetButton(_gamepadHandle, _buttonsDriverMapping[(int)inputId]) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.SDL2.Common;
|
using Ryujinx.SDL2.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -36,6 +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;
|
||||||
|
SDL2Driver.Instance.OnJoyBatteryUpdated += HandleJoyBatteryUpdated;
|
||||||
|
|
||||||
// Add already connected gamepads
|
// Add already connected gamepads
|
||||||
int numJoysticks = SDL_NumJoysticks();
|
int numJoysticks = SDL_NumJoysticks();
|
||||||
@ -83,19 +85,30 @@ namespace Ryujinx.Input.SDL2
|
|||||||
|
|
||||||
private void HandleJoyStickDisconnected(int joystickInstanceId)
|
private void HandleJoyStickDisconnected(int joystickInstanceId)
|
||||||
{
|
{
|
||||||
|
bool joyConPairDisconnected = false;
|
||||||
if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id))
|
if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
_gamepadsIds.Remove(id);
|
_gamepadsIds.Remove(id);
|
||||||
|
if (!SDL2JoyConPair.IsCombinable(_gamepadsIds))
|
||||||
|
{
|
||||||
|
_gamepadsIds.Remove(SDL2JoyConPair.Id);
|
||||||
|
joyConPairDisconnected = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OnGamepadDisconnected?.Invoke(id);
|
OnGamepadDisconnected?.Invoke(id);
|
||||||
|
if (joyConPairDisconnected)
|
||||||
|
{
|
||||||
|
OnGamepadDisconnected?.Invoke(SDL2JoyConPair.Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId)
|
private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId)
|
||||||
{
|
{
|
||||||
|
bool joyConPairConnected = false;
|
||||||
if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
|
if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
|
||||||
{
|
{
|
||||||
if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId))
|
if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId))
|
||||||
@ -120,12 +133,28 @@ namespace Ryujinx.Input.SDL2
|
|||||||
_gamepadsIds.Insert(joystickDeviceId, id);
|
_gamepadsIds.Insert(joystickDeviceId, id);
|
||||||
else
|
else
|
||||||
_gamepadsIds.Add(id);
|
_gamepadsIds.Add(id);
|
||||||
|
if (SDL2JoyConPair.IsCombinable(_gamepadsIds))
|
||||||
|
{
|
||||||
|
_gamepadsIds.Remove(SDL2JoyConPair.Id);
|
||||||
|
_gamepadsIds.Add(SDL2JoyConPair.Id);
|
||||||
|
joyConPairConnected = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OnGamepadConnected?.Invoke(id);
|
OnGamepadConnected?.Invoke(id);
|
||||||
|
if (joyConPairConnected)
|
||||||
|
{
|
||||||
|
OnGamepadConnected?.Invoke(SDL2JoyConPair.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleJoyBatteryUpdated(int joystickDeviceId, SDL_JoystickPowerLevel powerLevel)
|
||||||
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Hid,
|
||||||
|
$"{SDL_GameControllerNameForIndex(joystickDeviceId)} power level: {powerLevel}");
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
@ -157,6 +186,14 @@ namespace Ryujinx.Input.SDL2
|
|||||||
|
|
||||||
public IGamepad GetGamepad(string id)
|
public IGamepad GetGamepad(string id)
|
||||||
{
|
{
|
||||||
|
if (id == SDL2JoyConPair.Id)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return SDL2JoyConPair.GetGamepad(_gamepadsIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int joystickIndex = GetJoystickIndexByGamepadId(id);
|
int joystickIndex = GetJoystickIndexByGamepadId(id);
|
||||||
|
|
||||||
if (joystickIndex == -1)
|
if (joystickIndex == -1)
|
||||||
@ -165,12 +202,16 @@ namespace Ryujinx.Input.SDL2
|
|||||||
}
|
}
|
||||||
|
|
||||||
nint gamepadHandle = SDL_GameControllerOpen(joystickIndex);
|
nint gamepadHandle = SDL_GameControllerOpen(joystickIndex);
|
||||||
|
|
||||||
if (gamepadHandle == nint.Zero)
|
if (gamepadHandle == nint.Zero)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SDL_GameControllerName(gamepadHandle).StartsWith(SDL2JoyCon.Prefix))
|
||||||
|
{
|
||||||
|
return new SDL2JoyCon(gamepadHandle, id);
|
||||||
|
}
|
||||||
|
|
||||||
return new SDL2Gamepad(gamepadHandle, id);
|
return new SDL2Gamepad(gamepadHandle, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
409
src/Ryujinx.Input.SDL2/SDL2JoyCon.cs
Normal file
409
src/Ryujinx.Input.SDL2/SDL2JoyCon.cs
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
using static SDL2.SDL;
|
||||||
|
|
||||||
|
namespace Ryujinx.Input.SDL2
|
||||||
|
{
|
||||||
|
internal class SDL2JoyCon : IGamepad
|
||||||
|
{
|
||||||
|
private bool HasConfiguration => _configuration != null;
|
||||||
|
|
||||||
|
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From)
|
||||||
|
{
|
||||||
|
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound;
|
||||||
|
}
|
||||||
|
|
||||||
|
private StandardControllerInputConfig _configuration;
|
||||||
|
|
||||||
|
private readonly Dictionary<GamepadButtonInputId,SDL_GameControllerButton> _leftButtonsDriverMapping = new()
|
||||||
|
{
|
||||||
|
{ GamepadButtonInputId.LeftStick , SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK },
|
||||||
|
{GamepadButtonInputId.DpadUp ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y},
|
||||||
|
{GamepadButtonInputId.DpadDown ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A},
|
||||||
|
{GamepadButtonInputId.DpadLeft ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B},
|
||||||
|
{GamepadButtonInputId.DpadRight ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X},
|
||||||
|
{GamepadButtonInputId.Minus ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START},
|
||||||
|
{GamepadButtonInputId.LeftShoulder,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2},
|
||||||
|
{GamepadButtonInputId.LeftTrigger,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4},
|
||||||
|
{GamepadButtonInputId.SingleRightTrigger0,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||||
|
{GamepadButtonInputId.SingleLeftTrigger0,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||||
|
};
|
||||||
|
private readonly Dictionary<GamepadButtonInputId,SDL_GameControllerButton> _rightButtonsDriverMapping = new()
|
||||||
|
{
|
||||||
|
{GamepadButtonInputId.RightStick,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK},
|
||||||
|
{GamepadButtonInputId.A,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B},
|
||||||
|
{GamepadButtonInputId.B,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y},
|
||||||
|
{GamepadButtonInputId.X,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A},
|
||||||
|
{GamepadButtonInputId.Y,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X},
|
||||||
|
{GamepadButtonInputId.Plus,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START},
|
||||||
|
{GamepadButtonInputId.RightShoulder,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1},
|
||||||
|
{GamepadButtonInputId.RightTrigger,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3},
|
||||||
|
{GamepadButtonInputId.SingleRightTrigger1,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||||
|
{GamepadButtonInputId.SingleLeftTrigger1,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER}
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly Dictionary<GamepadButtonInputId, SDL_GameControllerButton> _buttonsDriverMapping;
|
||||||
|
private readonly Lock _userMappingLock = new();
|
||||||
|
|
||||||
|
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
|
||||||
|
|
||||||
|
private readonly StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count]
|
||||||
|
{
|
||||||
|
StickInputId.Unbound, StickInputId.Left, StickInputId.Right,
|
||||||
|
};
|
||||||
|
|
||||||
|
public GamepadFeaturesFlag Features { get; }
|
||||||
|
|
||||||
|
private nint _gamepadHandle;
|
||||||
|
|
||||||
|
private enum JoyConType
|
||||||
|
{
|
||||||
|
Left, Right
|
||||||
|
}
|
||||||
|
|
||||||
|
public const string Prefix = "Nintendo Switch Joy-Con";
|
||||||
|
public const string LeftName = "Nintendo Switch Joy-Con (L)";
|
||||||
|
public const string RightName = "Nintendo Switch Joy-Con (R)";
|
||||||
|
|
||||||
|
private readonly JoyConType _joyConType;
|
||||||
|
|
||||||
|
public SDL2JoyCon(nint gamepadHandle, string driverId)
|
||||||
|
{
|
||||||
|
_gamepadHandle = gamepadHandle;
|
||||||
|
_buttonsUserMapping = new List<ButtonMappingEntry>(10);
|
||||||
|
|
||||||
|
Name = SDL_GameControllerName(_gamepadHandle);
|
||||||
|
Id = driverId;
|
||||||
|
Features = GetFeaturesFlag();
|
||||||
|
|
||||||
|
// Enable motion tracking
|
||||||
|
if (Features.HasFlag(GamepadFeaturesFlag.Motion))
|
||||||
|
{
|
||||||
|
if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL,
|
||||||
|
SDL_bool.SDL_TRUE) != 0)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Hid,
|
||||||
|
$"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO,
|
||||||
|
SDL_bool.SDL_TRUE) != 0)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Hid,
|
||||||
|
$"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Name)
|
||||||
|
{
|
||||||
|
case LeftName:
|
||||||
|
{
|
||||||
|
_buttonsDriverMapping = _leftButtonsDriverMapping;
|
||||||
|
_joyConType = JoyConType.Left;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RightName:
|
||||||
|
{
|
||||||
|
_buttonsDriverMapping = _rightButtonsDriverMapping;
|
||||||
|
_joyConType = JoyConType.Right;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private GamepadFeaturesFlag GetFeaturesFlag()
|
||||||
|
{
|
||||||
|
GamepadFeaturesFlag result = GamepadFeaturesFlag.None;
|
||||||
|
|
||||||
|
if (SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) == SDL_bool.SDL_TRUE &&
|
||||||
|
SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO) == SDL_bool.SDL_TRUE)
|
||||||
|
{
|
||||||
|
result |= GamepadFeaturesFlag.Motion;
|
||||||
|
}
|
||||||
|
|
||||||
|
int error = SDL_GameControllerRumble(_gamepadHandle, 0, 0, 100);
|
||||||
|
|
||||||
|
if (error == 0)
|
||||||
|
{
|
||||||
|
result |= GamepadFeaturesFlag.Rumble;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Id { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
public bool IsConnected => SDL_GameControllerGetAttached(_gamepadHandle) == SDL_bool.SDL_TRUE;
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && _gamepadHandle != nint.Zero)
|
||||||
|
{
|
||||||
|
SDL_GameControllerClose(_gamepadHandle);
|
||||||
|
|
||||||
|
_gamepadHandle = nint.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void SetTriggerThreshold(float triggerThreshold)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
|
{
|
||||||
|
if (!Features.HasFlag(GamepadFeaturesFlag.Rumble))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
|
||||||
|
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
|
||||||
|
|
||||||
|
if (durationMs == uint.MaxValue)
|
||||||
|
{
|
||||||
|
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) !=
|
||||||
|
0)
|
||||||
|
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
|
||||||
|
}
|
||||||
|
else if (durationMs > SDL_HAPTIC_INFINITY)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0)
|
||||||
|
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetMotionData(MotionInputId inputId)
|
||||||
|
{
|
||||||
|
SDL_SensorType sensorType = inputId switch
|
||||||
|
{
|
||||||
|
MotionInputId.Accelerometer => SDL_SensorType.SDL_SENSOR_ACCEL,
|
||||||
|
MotionInputId.Gyroscope => SDL_SensorType.SDL_SENSOR_GYRO,
|
||||||
|
_ => SDL_SensorType.SDL_SENSOR_INVALID
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!Features.HasFlag(GamepadFeaturesFlag.Motion) || sensorType is SDL_SensorType.SDL_SENSOR_INVALID)
|
||||||
|
return Vector3.Zero;
|
||||||
|
|
||||||
|
const int ElementCount = 3;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
float* values = stackalloc float[ElementCount];
|
||||||
|
|
||||||
|
int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (nint)values, ElementCount);
|
||||||
|
|
||||||
|
if (result != 0)
|
||||||
|
return Vector3.Zero;
|
||||||
|
|
||||||
|
Vector3 value = _joyConType switch
|
||||||
|
{
|
||||||
|
JoyConType.Left => new Vector3(-values[2], values[1], values[0]),
|
||||||
|
JoyConType.Right => new Vector3(values[2], values[1], -values[0])
|
||||||
|
};
|
||||||
|
|
||||||
|
return inputId switch
|
||||||
|
{
|
||||||
|
MotionInputId.Gyroscope => RadToDegree(value),
|
||||||
|
MotionInputId.Accelerometer => GsToMs2(value),
|
||||||
|
_ => value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 RadToDegree(Vector3 rad) => rad * (180 / MathF.PI);
|
||||||
|
|
||||||
|
private static Vector3 GsToMs2(Vector3 gs) => gs / SDL_STANDARD_GRAVITY;
|
||||||
|
|
||||||
|
public void SetConfiguration(InputConfig configuration)
|
||||||
|
{
|
||||||
|
lock (_userMappingLock)
|
||||||
|
{
|
||||||
|
_configuration = (StandardControllerInputConfig)configuration;
|
||||||
|
|
||||||
|
_buttonsUserMapping.Clear();
|
||||||
|
|
||||||
|
// First update sticks
|
||||||
|
_stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick;
|
||||||
|
_stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick;
|
||||||
|
|
||||||
|
|
||||||
|
switch (_joyConType)
|
||||||
|
{
|
||||||
|
case JoyConType.Left:
|
||||||
|
_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));
|
||||||
|
break;
|
||||||
|
case JoyConType.Right:
|
||||||
|
_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));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
SetTriggerThreshold(_configuration.TriggerThreshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GamepadStateSnapshot GetStateSnapshot()
|
||||||
|
{
|
||||||
|
return IGamepad.GetStateSnapshot(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static float ConvertRawStickValue(short value)
|
||||||
|
{
|
||||||
|
const float ConvertRate = 1.0f / (short.MaxValue + 0.5f);
|
||||||
|
|
||||||
|
return value * ConvertRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JoyconConfigControllerStick<GamepadInputId, Common.Configuration.Hid.Controller.StickInputId>
|
||||||
|
GetLogicalJoyStickConfig(StickInputId inputId)
|
||||||
|
{
|
||||||
|
switch (inputId)
|
||||||
|
{
|
||||||
|
case StickInputId.Left:
|
||||||
|
if (_configuration.RightJoyconStick.Joystick ==
|
||||||
|
Common.Configuration.Hid.Controller.StickInputId.Left)
|
||||||
|
return _configuration.RightJoyconStick;
|
||||||
|
else
|
||||||
|
return _configuration.LeftJoyconStick;
|
||||||
|
case StickInputId.Right:
|
||||||
|
if (_configuration.LeftJoyconStick.Joystick ==
|
||||||
|
Common.Configuration.Hid.Controller.StickInputId.Right)
|
||||||
|
return _configuration.LeftJoyconStick;
|
||||||
|
else
|
||||||
|
return _configuration.RightJoyconStick;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public (float, float) GetStick(StickInputId inputId)
|
||||||
|
{
|
||||||
|
if (inputId == StickInputId.Unbound)
|
||||||
|
return (0.0f, 0.0f);
|
||||||
|
|
||||||
|
if (inputId == StickInputId.Left && _joyConType == JoyConType.Right || inputId == StickInputId.Right && _joyConType == JoyConType.Left)
|
||||||
|
{
|
||||||
|
return (0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
(short stickX, short stickY) = GetStickXY();
|
||||||
|
|
||||||
|
float resultX = ConvertRawStickValue(stickX);
|
||||||
|
float resultY = -ConvertRawStickValue(stickY);
|
||||||
|
|
||||||
|
if (HasConfiguration)
|
||||||
|
{
|
||||||
|
var joyconStickConfig = GetLogicalJoyStickConfig(inputId);
|
||||||
|
|
||||||
|
if (joyconStickConfig != null)
|
||||||
|
{
|
||||||
|
if (joyconStickConfig.InvertStickX)
|
||||||
|
resultX = -resultX;
|
||||||
|
|
||||||
|
if (joyconStickConfig.InvertStickY)
|
||||||
|
resultY = -resultY;
|
||||||
|
|
||||||
|
if (joyconStickConfig.Rotate90CW)
|
||||||
|
{
|
||||||
|
float temp = resultX;
|
||||||
|
resultX = resultY;
|
||||||
|
resultY = -temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputId switch
|
||||||
|
{
|
||||||
|
StickInputId.Left when _joyConType == JoyConType.Left => (resultY, -resultX),
|
||||||
|
StickInputId.Right when _joyConType == JoyConType.Right => (-resultY, resultX),
|
||||||
|
_ => (0.0f, 0.0f)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private (short, short) GetStickXY()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX),
|
||||||
|
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPressed(GamepadButtonInputId inputId)
|
||||||
|
{
|
||||||
|
if (!_buttonsDriverMapping.TryGetValue(inputId, out var button))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SDL_GameControllerGetButton(_gamepadHandle, button) == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
142
src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs
Normal file
142
src/Ryujinx.Input.SDL2/SDL2JoyConPair.cs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using static SDL2.SDL;
|
||||||
|
|
||||||
|
namespace Ryujinx.Input.SDL2
|
||||||
|
{
|
||||||
|
internal class SDL2JoyConPair(IGamepad left, IGamepad right) : IGamepad
|
||||||
|
{
|
||||||
|
private StandardControllerInputConfig _configuration;
|
||||||
|
|
||||||
|
private readonly StickInputId[] _stickUserMapping =
|
||||||
|
[
|
||||||
|
StickInputId.Unbound,
|
||||||
|
StickInputId.Left,
|
||||||
|
StickInputId.Right
|
||||||
|
];
|
||||||
|
|
||||||
|
public GamepadFeaturesFlag Features => (left?.Features ?? GamepadFeaturesFlag.None) |
|
||||||
|
(right?.Features ?? GamepadFeaturesFlag.None);
|
||||||
|
|
||||||
|
public const string Id = "JoyConPair";
|
||||||
|
string IGamepad.Id => Id;
|
||||||
|
|
||||||
|
public string Name => "* Nintendo Switch Joy-Con (L/R)";
|
||||||
|
public bool IsConnected => left is { IsConnected: true } && right is { IsConnected: true };
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
left?.Dispose();
|
||||||
|
right?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||||
|
{
|
||||||
|
return GetStateSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetMotionData(MotionInputId inputId)
|
||||||
|
{
|
||||||
|
return inputId switch
|
||||||
|
{
|
||||||
|
MotionInputId.Accelerometer or
|
||||||
|
MotionInputId.Gyroscope => left.GetMotionData(inputId),
|
||||||
|
MotionInputId.SecondAccelerometer => right.GetMotionData(MotionInputId.Accelerometer),
|
||||||
|
MotionInputId.SecondGyroscope => right.GetMotionData(MotionInputId.Gyroscope),
|
||||||
|
_ => Vector3.Zero
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public GamepadStateSnapshot GetStateSnapshot()
|
||||||
|
{
|
||||||
|
return IGamepad.GetStateSnapshot(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (float, float) GetStick(StickInputId inputId)
|
||||||
|
{
|
||||||
|
return inputId switch
|
||||||
|
{
|
||||||
|
StickInputId.Left => left.GetStick(StickInputId.Left),
|
||||||
|
StickInputId.Right => right.GetStick(StickInputId.Right),
|
||||||
|
_ => (0, 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPressed(GamepadButtonInputId inputId)
|
||||||
|
{
|
||||||
|
return left.IsPressed(inputId) || right.IsPressed(inputId);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
left.SetConfiguration(configuration);
|
||||||
|
right.SetConfiguration(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTriggerThreshold(float triggerThreshold)
|
||||||
|
{
|
||||||
|
left.SetTriggerThreshold(triggerThreshold);
|
||||||
|
right.SetTriggerThreshold(triggerThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsCombinable(List<string> gamepadsIds)
|
||||||
|
{
|
||||||
|
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
|
||||||
|
return leftIndex >= 0 && rightIndex >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int leftIndex, int rightIndex) DetectJoyConPair(List<string> gamepadsIds)
|
||||||
|
{
|
||||||
|
var gamepadNames = gamepadsIds.Where(gamepadId => gamepadId != Id)
|
||||||
|
.Select((_, index) => SDL_GameControllerNameForIndex(index)).ToList();
|
||||||
|
int leftIndex = gamepadNames.IndexOf(SDL2JoyCon.LeftName);
|
||||||
|
int rightIndex = gamepadNames.IndexOf(SDL2JoyCon.RightName);
|
||||||
|
|
||||||
|
return (leftIndex, rightIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IGamepad GetGamepad(List<string> gamepadsIds)
|
||||||
|
{
|
||||||
|
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return new SDL2JoyConPair(new SDL2JoyCon(leftGamepadHandle, gamepadsIds[leftIndex]),
|
||||||
|
new SDL2JoyCon(rightGamepadHandle, gamepadsIds[rightIndex]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -329,6 +329,11 @@ namespace Ryujinx.Input.SDL2
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SpecialExit()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public GamepadStateSnapshot GetStateSnapshot()
|
public GamepadStateSnapshot GetStateSnapshot()
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
|
@ -25,6 +25,10 @@ namespace Ryujinx.Input.SDL2
|
|||||||
{
|
{
|
||||||
_driver = driver;
|
_driver = driver;
|
||||||
}
|
}
|
||||||
|
public bool SpecialExit()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public Vector2 GetPosition()
|
public Vector2 GetPosition()
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@ 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.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Graphics.Gpu;
|
||||||
using Ryujinx.HLE.HOS.Services.Hid;
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
@ -266,6 +267,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
|
||||||
{
|
{
|
||||||
@ -273,8 +275,9 @@ namespace Ryujinx.Input.HLE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update()
|
public bool Update()
|
||||||
{
|
{
|
||||||
|
|
||||||
// _gamepad may be altered by other threads
|
// _gamepad may be altered by other threads
|
||||||
var gamepad = _gamepad;
|
var gamepad = _gamepad;
|
||||||
|
|
||||||
@ -282,6 +285,11 @@ namespace Ryujinx.Input.HLE
|
|||||||
{
|
{
|
||||||
State = gamepad.GetMappedStateSnapshot();
|
State = gamepad.GetMappedStateSnapshot();
|
||||||
|
|
||||||
|
if (gamepad.SpecialExit())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion)
|
if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion)
|
||||||
{
|
{
|
||||||
if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver)
|
if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver)
|
||||||
@ -297,11 +305,24 @@ namespace Ryujinx.Input.HLE
|
|||||||
_leftMotionInput.Update(accelerometer, gyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);
|
_leftMotionInput.Update(accelerometer, gyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);
|
||||||
|
|
||||||
if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
|
if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
|
||||||
|
{
|
||||||
|
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;
|
_rightMotionInput = _leftMotionInput;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook && controllerConfig.Motion is CemuHookMotionConfigController cemuControllerConfig)
|
else if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook && controllerConfig.Motion is CemuHookMotionConfigController cemuControllerConfig)
|
||||||
{
|
{
|
||||||
int clientId = (int)controllerConfig.PlayerIndex;
|
int clientId = (int)controllerConfig.PlayerIndex;
|
||||||
@ -333,7 +354,9 @@ namespace Ryujinx.Input.HLE
|
|||||||
// Reset states
|
// Reset states
|
||||||
State = default;
|
State = default;
|
||||||
_leftMotionInput = null;
|
_leftMotionInput = null;
|
||||||
|
_rightMotionInput = null;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GamepadInput GetHLEInputState()
|
public GamepadInput GetHLEInputState()
|
||||||
|
@ -200,8 +200,10 @@ namespace Ryujinx.Input.HLE
|
|||||||
ReloadConfiguration(inputConfig, enableKeyboard, enableMouse);
|
ReloadConfiguration(inputConfig, enableKeyboard, enableMouse);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(float aspectRatio = 1)
|
public bool Update(float aspectRatio = 1)
|
||||||
{
|
{
|
||||||
|
bool specialExit = false;
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
List<GamepadInput> hleInputStates = new();
|
List<GamepadInput> hleInputStates = new();
|
||||||
@ -225,7 +227,8 @@ namespace Ryujinx.Input.HLE
|
|||||||
DriverConfigurationUpdate(ref controller, inputConfig);
|
DriverConfigurationUpdate(ref controller, inputConfig);
|
||||||
|
|
||||||
controller.UpdateUserConfiguration(inputConfig);
|
controller.UpdateUserConfiguration(inputConfig);
|
||||||
controller.Update();
|
|
||||||
|
specialExit = controller.Update(); //hotkey press check
|
||||||
controller.UpdateRumble(_device.Hid.Npads.GetRumbleQueue(playerIndex));
|
controller.UpdateRumble(_device.Hid.Npads.GetRumbleQueue(playerIndex));
|
||||||
|
|
||||||
inputState = controller.GetHLEInputState();
|
inputState = controller.GetHLEInputState();
|
||||||
@ -315,6 +318,8 @@ namespace Ryujinx.Input.HLE
|
|||||||
|
|
||||||
_device.TamperMachine.UpdateInput(hleInputStates);
|
_device.TamperMachine.UpdateInput(hleInputStates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return specialExit;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal InputConfig GetPlayerInputConfigByIndex(int index)
|
internal InputConfig GetPlayerInputConfigByIndex(int index)
|
||||||
|
@ -79,6 +79,12 @@ namespace Ryujinx.Input
|
|||||||
/// <returns>A remapped snaphost of the state of the gamepad.</returns>
|
/// <returns>A remapped snaphost of the state of the gamepad.</returns>
|
||||||
GamepadStateSnapshot GetMappedStateSnapshot();
|
GamepadStateSnapshot GetMappedStateSnapshot();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the state if the minus and plus buttons were pressed on the gamepad.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>returns true if the buttons were pressed.</returns>
|
||||||
|
bool SpecialExit();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a snaphost of the state of the gamepad.
|
/// Get a snaphost of the state of the gamepad.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,14 +25,17 @@ namespace Ryujinx.SDL2.Common
|
|||||||
|
|
||||||
public static Action<Action> MainThreadDispatcher { get; set; }
|
public static Action<Action> MainThreadDispatcher { get; set; }
|
||||||
|
|
||||||
private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO;
|
private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK |
|
||||||
|
SDL_INIT_AUDIO | SDL_INIT_VIDEO;
|
||||||
|
|
||||||
private bool _isRunning;
|
private bool _isRunning;
|
||||||
private uint _refereceCount;
|
private uint _refereceCount;
|
||||||
private Thread _worker;
|
private Thread _worker;
|
||||||
|
|
||||||
|
private const uint SDL_JOYBATTERYUPDATED = 1543;
|
||||||
public event Action<int, int> OnJoyStickConnected;
|
public event Action<int, int> OnJoyStickConnected;
|
||||||
public event Action<int> OnJoystickDisconnected;
|
public event Action<int> OnJoystickDisconnected;
|
||||||
|
public event Action<int, SDL_JoystickPowerLevel> OnJoyBatteryUpdated;
|
||||||
|
|
||||||
private ConcurrentDictionary<uint, Action<SDL_Event>> _registeredWindowHandlers;
|
private ConcurrentDictionary<uint, Action<SDL_Event>> _registeredWindowHandlers;
|
||||||
|
|
||||||
@ -78,12 +81,14 @@ namespace Ryujinx.SDL2.Common
|
|||||||
// First ensure that we only enable joystick events (for connected/disconnected).
|
// First ensure that we only enable joystick events (for connected/disconnected).
|
||||||
if (SDL_GameControllerEventState(SDL_IGNORE) != SDL_IGNORE)
|
if (SDL_GameControllerEventState(SDL_IGNORE) != SDL_IGNORE)
|
||||||
{
|
{
|
||||||
Logger.Error?.PrintMsg(LogClass.Application, "Couldn't change the state of game controller events.");
|
Logger.Error?.PrintMsg(LogClass.Application,
|
||||||
|
"Couldn't change the state of game controller events.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SDL_JoystickEventState(SDL_ENABLE) < 0)
|
if (SDL_JoystickEventState(SDL_ENABLE) < 0)
|
||||||
{
|
{
|
||||||
Logger.Error?.PrintMsg(LogClass.Application, $"Failed to enable joystick event polling: {SDL_GetError()}");
|
Logger.Error?.PrintMsg(LogClass.Application,
|
||||||
|
$"Failed to enable joystick event polling: {SDL_GetError()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable all joysticks information, we don't need them no need to flood the event queue for that.
|
// Disable all joysticks information, we don't need them no need to flood the event queue for that.
|
||||||
@ -143,7 +148,12 @@ namespace Ryujinx.SDL2.Common
|
|||||||
|
|
||||||
OnJoystickDisconnected?.Invoke(evnt.cbutton.which);
|
OnJoystickDisconnected?.Invoke(evnt.cbutton.which);
|
||||||
}
|
}
|
||||||
else if (evnt.type is SDL_EventType.SDL_WINDOWEVENT or SDL_EventType.SDL_MOUSEBUTTONDOWN or SDL_EventType.SDL_MOUSEBUTTONUP)
|
else if ((uint)evnt.type == SDL_JOYBATTERYUPDATED)
|
||||||
|
{
|
||||||
|
OnJoyBatteryUpdated?.Invoke(evnt.cbutton.which, (SDL_JoystickPowerLevel)evnt.user.code);
|
||||||
|
}
|
||||||
|
else if (evnt.type is SDL_EventType.SDL_WINDOWEVENT or SDL_EventType.SDL_MOUSEBUTTONDOWN
|
||||||
|
or SDL_EventType.SDL_MOUSEBUTTONUP)
|
||||||
{
|
{
|
||||||
if (_registeredWindowHandlers.TryGetValue(evnt.window.windowID, out Action<SDL_Event> handler))
|
if (_registeredWindowHandlers.TryGetValue(evnt.window.windowID, out Action<SDL_Event> handler))
|
||||||
{
|
{
|
||||||
|
@ -18,6 +18,7 @@ using Ryujinx.Ava.UI.Helpers;
|
|||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Ava.UI.Renderer;
|
using Ryujinx.Ava.UI.Renderer;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
|
using Ryujinx.Ava.UI.Views.Main;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Ava.Utilities;
|
using Ryujinx.Ava.Utilities;
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
@ -70,6 +71,7 @@ namespace Ryujinx.Ava
|
|||||||
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
|
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
|
||||||
private const int TargetFps = 60;
|
private const int TargetFps = 60;
|
||||||
private const float VolumeDelta = 0.05f;
|
private const float VolumeDelta = 0.05f;
|
||||||
|
static bool SpecialExit = false;
|
||||||
|
|
||||||
private static readonly Cursor _invisibleCursor = new(StandardCursorType.None);
|
private static readonly Cursor _invisibleCursor = new(StandardCursorType.None);
|
||||||
private readonly nint _invisibleCursorWin;
|
private readonly nint _invisibleCursorWin;
|
||||||
@ -96,6 +98,7 @@ namespace Ryujinx.Ava
|
|||||||
private bool _isCursorInRenderer = true;
|
private bool _isCursorInRenderer = true;
|
||||||
private bool _ignoreCursorState = false;
|
private bool _ignoreCursorState = false;
|
||||||
|
|
||||||
|
|
||||||
private enum CursorStates
|
private enum CursorStates
|
||||||
{
|
{
|
||||||
CursorIsHidden,
|
CursorIsHidden,
|
||||||
@ -507,6 +510,11 @@ namespace Ryujinx.Ava
|
|||||||
Exit();
|
Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsSpecialExit()
|
||||||
|
{
|
||||||
|
return SpecialExit;
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs<bool> args)
|
private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs<bool> args)
|
||||||
{
|
{
|
||||||
if (Device != null)
|
if (Device != null)
|
||||||
@ -589,6 +597,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_isStopped = true;
|
_isStopped = true;
|
||||||
Stop();
|
Stop();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisposeContext()
|
public void DisposeContext()
|
||||||
@ -1135,6 +1144,7 @@ namespace Ryujinx.Ava
|
|||||||
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld];
|
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld];
|
||||||
string vSyncMode = Device.VSyncMode.ToString();
|
string vSyncMode = Device.VSyncMode.ToString();
|
||||||
|
|
||||||
|
|
||||||
UpdateShaderCount();
|
UpdateShaderCount();
|
||||||
|
|
||||||
if (GraphicsConfig.ResScale != 1)
|
if (GraphicsConfig.ResScale != 1)
|
||||||
@ -1200,7 +1210,17 @@ namespace Ryujinx.Ava
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
|
if (NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()))
|
||||||
|
{
|
||||||
|
if (ConfigurationState.Instance.Hid.SpecialExitEmulator.Value == 1)
|
||||||
|
{
|
||||||
|
SpecialExit = true; // close App
|
||||||
|
}
|
||||||
|
if (ConfigurationState.Instance.Hid.SpecialExitEmulator.Value > 0)
|
||||||
|
{
|
||||||
|
_isActive = false; //close game
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_viewModel.IsActive)
|
if (_viewModel.IsActive)
|
||||||
{
|
{
|
||||||
@ -1335,6 +1355,8 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
Device.Hid.DebugPad.Update();
|
Device.Hid.DebugPad.Update();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,6 +147,31 @@
|
|||||||
"zh_TW": "滑鼠直接存取"
|
"zh_TW": "滑鼠直接存取"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ID": "SettingsExtraCloseApp",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "خروج سريع من التطبيق",
|
||||||
|
"de_DE": "Schneller Ausstieg aus der Anwendung",
|
||||||
|
"el_GR": "Γρήγορη έξοδος από την εφαρμογή",
|
||||||
|
"en_US": "Quick Exit from Application",
|
||||||
|
"es_ES": "Salida rápida de la aplicación",
|
||||||
|
"fr_FR": "Sortie rapide de l'application",
|
||||||
|
"he_IL": "יציאה מהירה מהאפליקציה",
|
||||||
|
"it_IT": "Uscita rapida dall'applicazione",
|
||||||
|
"ja_JP": "アプリケーションからの迅速な終了",
|
||||||
|
"ko_KR": "애플리케이션에서 빠른 종료",
|
||||||
|
"no_NO": "Rask avslutning av applikasjonen",
|
||||||
|
"pl_PL": "Szybkie wyjście z aplikacji",
|
||||||
|
"pt_BR": "Saída rápida do aplicativo",
|
||||||
|
"ru_RU": "Быстрый выход из приложения",
|
||||||
|
"sv_SE": "Snabb avslutning från applikationen",
|
||||||
|
"th_TH": "ออกจากแอปพลิเคชันอย่างรวดเร็ว",
|
||||||
|
"tr_TR": "Uygulamadan Hızlı Çıkış",
|
||||||
|
"uk_UA": "Швидкий вихід з програми",
|
||||||
|
"zh_CN": "快速退出应用程序",
|
||||||
|
"zh_TW": "快速退出應用程式"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ID": "SettingsTabSystemMemoryManagerMode",
|
"ID": "SettingsTabSystemMemoryManagerMode",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
@ -11047,6 +11072,81 @@
|
|||||||
"zh_TW": "淺色"
|
"zh_TW": "淺色"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ID": "SettingsTabInputDisableExitHotKey",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "الخروج السريع معطل",
|
||||||
|
"de_DE": "Schneller Ausstieg deaktiviert",
|
||||||
|
"el_GR": "Η γρήγορη έξοδος είναι απενεργοποιημένη",
|
||||||
|
"en_US": "Quick exit disabled",
|
||||||
|
"es_ES": "Salida rápida desactivada",
|
||||||
|
"fr_FR": "Sortie rapide désactivée",
|
||||||
|
"he_IL": "יציאה מהירה מושבתת",
|
||||||
|
"it_IT": "Uscita rapida disabilitata",
|
||||||
|
"ja_JP": "クイック終了が無効です",
|
||||||
|
"ko_KR": "빠른 종료 비활성화됨",
|
||||||
|
"no_NO": "Rask avslutning er deaktivert",
|
||||||
|
"pl_PL": "Szybkie wyjście wyłączone",
|
||||||
|
"pt_BR": "Saída rápida desativada",
|
||||||
|
"ru_RU": "Быстрый выход выключен",
|
||||||
|
"sv_SE": "Snabb avslutning inaktiverad",
|
||||||
|
"th_TH": "ปิดใช้งานออกอย่างรวดเร็ว",
|
||||||
|
"tr_TR": "Hızlı çıkış devre dışı bırakıldı",
|
||||||
|
"uk_UA": "Швидкий вихід вимкнено",
|
||||||
|
"zh_CN": "快速退出已禁用",
|
||||||
|
"zh_TW": "快速退出已停用"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "SettingsTabInputHotkeyIsCloseApp",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "إغلاق التطبيق بالضغط على الزرين '+' و '-'.",
|
||||||
|
"de_DE": "App schließen mit den '+' und '-' Tasten.",
|
||||||
|
"el_GR": "Κλείσιμο της εφαρμογής με τα κουμπιά '+' και '-'.",
|
||||||
|
"en_US": "Close app by '+' and '-' buttons.",
|
||||||
|
"es_ES": "Cerrar la aplicación pulsando los botones '+' y '-'.",
|
||||||
|
"fr_FR": "Fermer l'application avec les boutons '+' et '-'.",
|
||||||
|
"he_IL": "סגור את האפליקציה בלחיצה על '+' ו-'-'.",
|
||||||
|
"it_IT": "Chiudi l'app premendo i pulsanti '+' e '-'.",
|
||||||
|
"ja_JP": "「+」と「-」ボタンを押してアプリを終了します。",
|
||||||
|
"ko_KR": "'+' 및 '-' 버튼을 눌러 앱을 종료합니다.",
|
||||||
|
"no_NO": "Lukk appen med '+' og '-' knappene.",
|
||||||
|
"pl_PL": "Zamknij aplikację przyciskiem '+' i '-'.",
|
||||||
|
"pt_BR": "Fechar o aplicativo pressionando os botões '+' e '-'.",
|
||||||
|
"ru_RU": "Закрыть приложение нажатием '+' и '-'.",
|
||||||
|
"sv_SE": "Stäng appen med '+' och '-' knapparna.",
|
||||||
|
"th_TH": "ปิดแอปโดยกดปุ่ม '+' และ '-'.",
|
||||||
|
"tr_TR": "'+' ve '-' düğmelerine basarak uygulamayı kapatın.",
|
||||||
|
"uk_UA": "Закрити додаток натисканням '+' та '-'.",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "SettingsTabInputHotkeyIsCloseGame",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "الخروج من اللعبة بالضغط على الزرين '+' و '-'.",
|
||||||
|
"de_DE": "Spiel beenden mit den '+' und '-' Tasten.",
|
||||||
|
"el_GR": "Έξοδος από το παιχνίδι με τα κουμπιά '+' και '-'.",
|
||||||
|
"en_US": "Exit game by '+' and '-' buttons.",
|
||||||
|
"es_ES": "Salir del juego pulsando los botones '+' y '-'.",
|
||||||
|
"fr_FR": "Quitter le jeu avec les boutons '+' et '-'.",
|
||||||
|
"he_IL": "יציאה מהמשחק בלחיצה על '+' ו-'-'.",
|
||||||
|
"it_IT": "Esci dal gioco premendo i pulsanti '+' e '-'.",
|
||||||
|
"ja_JP": "「+」と「-」ボタンを押してゲームを終了します。",
|
||||||
|
"ko_KR": "'+' 및 '-' 버튼을 눌러 게임을 종료합니다.",
|
||||||
|
"no_NO": "Avslutt spillet med '+' og '-' knappene.",
|
||||||
|
"pl_PL": "Wyjście z gry przyciskiem '+' i '-'.",
|
||||||
|
"pt_BR": "Sair do jogo pressionando os botões '+' e '-'.",
|
||||||
|
"ru_RU": "Выйти из игры нажатием '+' и '-'.",
|
||||||
|
"sv_SE": "Avsluta spelet med '+' och '-' knapparna.",
|
||||||
|
"th_TH": "ออกจากเกมโดยกดปุ่ม '+' และ '-'.",
|
||||||
|
"tr_TR": "'+' ve '-' düğmelerine basarak oyundan çıkın.",
|
||||||
|
"uk_UA": "Вийти з гри натисканням '+' та '-'.",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ID": "ControllerSettingsConfigureGeneral",
|
"ID": "ControllerSettingsConfigureGeneral",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
@ -15147,6 +15247,31 @@
|
|||||||
"zh_TW": "支援滑鼠直接存取 (HID)。遊戲可將滑鼠作為指向裝置使用。\n\n僅適用於在 Switch 硬體上原生支援滑鼠控制的遊戲,這類遊戲很少。\n\n啟用後,觸控螢幕功能可能無法使用。\n\n如果不確定,請保持關閉狀態。"
|
"zh_TW": "支援滑鼠直接存取 (HID)。遊戲可將滑鼠作為指向裝置使用。\n\n僅適用於在 Switch 硬體上原生支援滑鼠控制的遊戲,這類遊戲很少。\n\n啟用後,觸控螢幕功能可能無法使用。\n\n如果不確定,請保持關閉狀態。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ID": "SpecialExitTooltip",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "يقوم بتفعيل مفاتيح الاختصار 'زائد' و 'ناقص'.\nاضغط على زرّي زائد وناقص في نفس الوقت لتنفيذ إحدى العمليات:\n\n1) إغلاق التطبيق باستخدام مفاتيح الاختصار.\n2) الخروج من اللعبة دون إغلاق التطبيق.\n\nيعمل فقط مع أذرع التحكم.",
|
||||||
|
"de_DE": "Aktiviert die Hotkeys 'Plus' und 'Minus'.\nDrücken Sie gleichzeitig die Plus- und Minus-Tasten, um eine der folgenden Aktionen auszuführen:\n\n1) Schließt die Anwendung durch Drücken der Hotkeys.\n2) Beendet das Spiel, ohne die Anwendung zu schließen.\n\nFunktioniert nur mit Gamepads.",
|
||||||
|
"el_GR": "Ενεργοποιεί τα πλήκτρα πρόσβασης 'συν' και 'πλην'.\nΠατήστε ταυτόχρονα τα κουμπιά συν και πλην για μία από τις ενέργειες:\n\n1) Κλείνει την εφαρμογή πατώντας τα πλήκτρα πρόσβασης.\n2) Εξέρχεται από το παιχνίδι χωρίς να κλείσει η εφαρμογή.\n\nΛειτουργεί μόνο με gamepads.",
|
||||||
|
"en_US": "Activates the hot keys 'plus' and 'minus'.\nPress buttons plus and minus at the same time to get one of the actions:\n\n1) Closes the application by pressing the hot keys.\n2) Exits the game without closing the application.\n\nWorks only with gamepads.",
|
||||||
|
"es_ES": "Activa las teclas rápidas 'más' y 'menos'.\nPresiona los botones más y menos al mismo tiempo para realizar una de las siguientes acciones:\n\n1) Cierra la aplicación presionando las teclas rápidas.\n2) Salir del juego sin cerrar la aplicación.\n\nFunciona solo con mandos.",
|
||||||
|
"fr_FR": "Active les raccourcis 'plus' et 'moins'.\nAppuyez simultanément sur les boutons plus et moins pour effectuer l'une des actions suivantes :\n\n1) Ferme l'application en appuyant sur les raccourcis.\n2) Quitte le jeu sans fermer l'application.\n\nFonctionne uniquement avec les manettes.",
|
||||||
|
"he_IL": "מפעיל את המקשים הקצרים 'פלוס' ו-'מינוס'.\nלחץ על הכפתורים פלוס ומינוס בו זמנית כדי לבצע אחת מהפעולות:\n\n1) סוגר את היישום באמצעות המקשים הקצרים.\n2) יוצא מהמשחק מבלי לסגור את היישום.\n\nפועל רק עם בקרי משחק.",
|
||||||
|
"it_IT": "Attiva i tasti rapidi 'più' e 'meno'.\nPremere i pulsanti più e meno contemporaneamente per eseguire una delle seguenti azioni:\n\n1) Chiude l'applicazione premendo i tasti rapidi.\n2) Esce dal gioco senza chiudere l'applicazione.\n\nFunziona solo con i gamepad.",
|
||||||
|
"ja_JP": "ホットキー「プラス」と「マイナス」を有効化します。\nプラスとマイナスのボタンを同時に押して、次のいずれかの操作を実行します:\n\n1) ホットキーを押すことでアプリを閉じます。\n2) アプリを閉じずにゲームを終了します。\n\nゲームパッドでのみ動作します。",
|
||||||
|
"ko_KR": "'플러스' 및 '마이너스' 단축키를 활성화합니다.\n플러스 및 마이너스 버튼을 동시에 눌러 다음 작업 중 하나를 수행합니다:\n\n1) 단축키를 눌러 애플리케이션을 닫습니다.\n2) 애플리케이션을 닫지 않고 게임을 종료합니다.\n\n게임패드에서만 작동합니다.",
|
||||||
|
"no_NO": "Aktiverer hurtigtastene 'pluss' og 'minus'.\nTrykk på knappene pluss og minus samtidig for å utføre en av følgende handlinger:\n\n1) Lukker applikasjonen ved å trykke på hurtigtastene.\n2) Avslutter spillet uten å lukke applikasjonen.\n\nFungerer kun med spillkontroller.",
|
||||||
|
"pl_PL": "Aktywuje klawisze skrótu 'plus' i 'minus'.\nNaciśnij jednocześnie przyciski plus i minus, aby wykonać jedną z akcji:\n\n1) Zamknij aplikację, naciskając klawisze skrótu.\n2) Wyjdź z gry bez zamykania aplikacji.\n\nDziała tylko z gamepadami.",
|
||||||
|
"pt_BR": "Ativa as teclas de atalho 'mais' e 'menos'.\nPressione os botões mais e menos ao mesmo tempo para realizar uma das ações:\n\n1) Fecha o aplicativo pressionando as teclas de atalho.\n2) Sai do jogo sem fechar o aplicativo.\n\nFunciona apenas com gamepads.",
|
||||||
|
"ru_RU": "Активирует горячие клавиши 'плюс' и 'минус'.\nНажмите одновременно кнопки плюс и минус чтобы получить одно из действий:\n\n1) Закрывает приложение по нажатию горячих кнопок.\n2) Выходит из игры без закрытия приложения.\n\nРаботает только с геймпадами.",
|
||||||
|
"sv_SE": "Aktiverar snabbtangenterna 'plus' och 'minus'.\nTryck samtidigt på plus- och minusknapparna för att utföra en av följande åtgärder:\n\n1) Stänger applikationen med snabbtangenterna.\n2) Avslutar spelet utan att stänga applikationen.\n\nFungerar bara med spelkontroller.",
|
||||||
|
"th_TH": "เปิดใช้งานปุ่มลัด '+' และ '-'.\nกดปุ่ม '+' และ '-' พร้อมกันเพื่อทำการอย่างใดอย่างหนึ่ง:\n\n1) ปิดแอปพลิเคชันด้วยการกดปุ่มลัด\n2) ออกจากเกมโดยไม่ปิดแอปพลิเคชัน\n\nใช้งานได้เฉพาะกับจอยเกม",
|
||||||
|
"tr_TR": "'Artı' ve 'eksi' kısayol tuşlarını etkinleştirir.\nArtı ve eksi tuşlarına aynı anda basarak aşağıdaki işlemlerden birini gerçekleştirin:\n\n1) Kısayol tuşlarına basarak uygulamayı kapatır.\n2) Uygulamayı kapatmadan oyundan çıkar.\n\nYalnızca gamepad'lerle çalışır.",
|
||||||
|
"uk_UA": "Активує гарячі клавіші '+' та '-'.\nНатисніть одночасно кнопки плюс та мінус для виконання однієї з дій:\n\n1) Закриває додаток за допомогою гарячих клавіш.\n2) Виходить із гри без закриття додатка.\n\nПрацює лише з геймпадами.",
|
||||||
|
"zh_CN": "激活快捷键“加号”和“减号”。\n同时按下加号和减号按钮以执行以下操作之一:\n\n1) 按快捷键关闭应用程序。\n2) 退出游戏而不关闭应用程序。\n\n仅适用于游戏手柄。",
|
||||||
|
"zh_TW": "啟用快捷鍵「加號」和「減號」。\n同時按下加號和減號按鈕以執行以下其中一項操作:\n\n1) 按快捷鍵關閉應用程式。\n2) 離開遊戲而不關閉應用程式。\n\n僅適用於遊戲手柄。"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ID": "RegionTooltip",
|
"ID": "RegionTooltip",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
|
@ -350,11 +350,11 @@ namespace Ryujinx.Headless
|
|||||||
{
|
{
|
||||||
return options.GraphicsBackend switch
|
return options.GraphicsBackend switch
|
||||||
{
|
{
|
||||||
GraphicsBackend.Vulkan => new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet),
|
GraphicsBackend.Vulkan => new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet, options.SpecialExit),
|
||||||
GraphicsBackend.Metal => OperatingSystem.IsMacOS() ?
|
GraphicsBackend.Metal => OperatingSystem.IsMacOS() ?
|
||||||
new MetalWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableKeyboard, options.HideCursorMode, options.IgnoreControllerApplet) :
|
new MetalWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableKeyboard, options.HideCursorMode, options.IgnoreControllerApplet, options.SpecialExit) :
|
||||||
throw new Exception("Attempted to use Metal renderer on non-macOS platform!"),
|
throw new Exception("Attempted to use Metal renderer on non-macOS platform!"),
|
||||||
_ => new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet)
|
_ => new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet, options.SpecialExit)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +23,9 @@ namespace Ryujinx.Headless
|
|||||||
AspectRatio aspectRatio,
|
AspectRatio aspectRatio,
|
||||||
bool enableMouse,
|
bool enableMouse,
|
||||||
HideCursorMode hideCursorMode,
|
HideCursorMode hideCursorMode,
|
||||||
bool ignoreControllerApplet)
|
bool ignoreControllerApplet,
|
||||||
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet) { }
|
int specialExitEmulator)
|
||||||
|
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet, specialExitEmulator) { }
|
||||||
|
|
||||||
public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_METAL;
|
public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_METAL;
|
||||||
|
|
||||||
|
@ -118,8 +118,9 @@ namespace Ryujinx.Headless
|
|||||||
AspectRatio aspectRatio,
|
AspectRatio aspectRatio,
|
||||||
bool enableMouse,
|
bool enableMouse,
|
||||||
HideCursorMode hideCursorMode,
|
HideCursorMode hideCursorMode,
|
||||||
bool ignoreControllerApplet)
|
bool ignoreControllerApplet,
|
||||||
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet)
|
int specialExitEmulator)
|
||||||
|
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet, specialExitEmulator)
|
||||||
{
|
{
|
||||||
_glLogLevel = glLogLevel;
|
_glLogLevel = glLogLevel;
|
||||||
}
|
}
|
||||||
|
@ -151,6 +151,9 @@ namespace Ryujinx.Headless
|
|||||||
if (NeedsOverride(nameof(IgnoreControllerApplet)))
|
if (NeedsOverride(nameof(IgnoreControllerApplet)))
|
||||||
IgnoreControllerApplet = configurationState.IgnoreApplet;
|
IgnoreControllerApplet = configurationState.IgnoreApplet;
|
||||||
|
|
||||||
|
if (NeedsOverride(nameof(SpecialExit)))
|
||||||
|
SpecialExit = configurationState.Hid.SpecialExitEmulator;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey)));
|
bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey)));
|
||||||
@ -274,6 +277,9 @@ namespace Ryujinx.Headless
|
|||||||
[Option("enable-mouse", Required = false, Default = false, HelpText = "Enable or disable mouse support.")]
|
[Option("enable-mouse", Required = false, Default = false, HelpText = "Enable or disable mouse support.")]
|
||||||
public bool EnableMouse { get; set; }
|
public bool EnableMouse { get; set; }
|
||||||
|
|
||||||
|
[Option("enable-press-hotkeys-to-exit", Required = false, Default = 0, HelpText = "press the minus and plus buttons to: 0 -disable, 1 - exit app, 2 - exit game.")]
|
||||||
|
public int SpecialExit { get; set; }
|
||||||
|
|
||||||
[Option("hide-cursor", Required = false, Default = HideCursorMode.OnIdle, HelpText = "Change when the cursor gets hidden.")]
|
[Option("hide-cursor", Required = false, Default = HideCursorMode.OnIdle, HelpText = "Change when the cursor gets hidden.")]
|
||||||
public HideCursorMode HideCursorMode { get; set; }
|
public HideCursorMode HideCursorMode { get; set; }
|
||||||
|
|
||||||
@ -414,6 +420,7 @@ namespace Ryujinx.Headless
|
|||||||
[Option("ignore-controller-applet", Required = false, Default = false, HelpText = "Enable ignoring the controller applet when your game loses connection to your controller.")]
|
[Option("ignore-controller-applet", Required = false, Default = false, HelpText = "Enable ignoring the controller applet when your game loses connection to your controller.")]
|
||||||
public bool IgnoreControllerApplet { get; set; }
|
public bool IgnoreControllerApplet { get; set; }
|
||||||
|
|
||||||
|
|
||||||
// Values
|
// Values
|
||||||
|
|
||||||
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
|
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
|
||||||
|
@ -18,8 +18,9 @@ namespace Ryujinx.Headless
|
|||||||
AspectRatio aspectRatio,
|
AspectRatio aspectRatio,
|
||||||
bool enableMouse,
|
bool enableMouse,
|
||||||
HideCursorMode hideCursorMode,
|
HideCursorMode hideCursorMode,
|
||||||
bool ignoreControllerApplet)
|
bool ignoreControllerApplet,
|
||||||
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet)
|
int specialExitEmulator)
|
||||||
|
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet, specialExitEmulator)
|
||||||
{
|
{
|
||||||
_glLogLevel = glLogLevel;
|
_glLogLevel = glLogLevel;
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,7 @@ namespace Ryujinx.Headless
|
|||||||
|
|
||||||
private readonly AspectRatio _aspectRatio;
|
private readonly AspectRatio _aspectRatio;
|
||||||
private readonly bool _enableMouse;
|
private readonly bool _enableMouse;
|
||||||
|
private readonly int _specialExitEmulator;
|
||||||
private readonly bool _ignoreControllerApplet;
|
private readonly bool _ignoreControllerApplet;
|
||||||
|
|
||||||
public WindowBase(
|
public WindowBase(
|
||||||
@ -96,7 +97,8 @@ namespace Ryujinx.Headless
|
|||||||
AspectRatio aspectRatio,
|
AspectRatio aspectRatio,
|
||||||
bool enableMouse,
|
bool enableMouse,
|
||||||
HideCursorMode hideCursorMode,
|
HideCursorMode hideCursorMode,
|
||||||
bool ignoreControllerApplet)
|
bool ignoreControllerApplet,
|
||||||
|
int specialExitEmulator)
|
||||||
{
|
{
|
||||||
MouseDriver = new SDL2MouseDriver(hideCursorMode);
|
MouseDriver = new SDL2MouseDriver(hideCursorMode);
|
||||||
_inputManager = inputManager;
|
_inputManager = inputManager;
|
||||||
@ -112,6 +114,7 @@ namespace Ryujinx.Headless
|
|||||||
_gpuDoneEvent = new ManualResetEvent(false);
|
_gpuDoneEvent = new ManualResetEvent(false);
|
||||||
_aspectRatio = aspectRatio;
|
_aspectRatio = aspectRatio;
|
||||||
_enableMouse = enableMouse;
|
_enableMouse = enableMouse;
|
||||||
|
_specialExitEmulator = specialExitEmulator;
|
||||||
_ignoreControllerApplet = ignoreControllerApplet;
|
_ignoreControllerApplet = ignoreControllerApplet;
|
||||||
HostUITheme = new HeadlessHostUiTheme();
|
HostUITheme = new HeadlessHostUiTheme();
|
||||||
|
|
||||||
|
@ -30,6 +30,11 @@ namespace Ryujinx.Ava.Input
|
|||||||
public readonly Key From = from;
|
public readonly Key From = from;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SpecialExit()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
|
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
|
||||||
{
|
{
|
||||||
_buttonsUserMapping = [];
|
_buttonsUserMapping = [];
|
||||||
|
@ -13,6 +13,11 @@ namespace Ryujinx.Ava.Input
|
|||||||
public string Id => "0";
|
public string Id => "0";
|
||||||
public string Name => "AvaloniaMouse";
|
public string Name => "AvaloniaMouse";
|
||||||
|
|
||||||
|
public bool SpecialExit()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsConnected => true;
|
public bool IsConnected => true;
|
||||||
public GamepadFeaturesFlag Features => throw new NotImplementedException();
|
public GamepadFeaturesFlag Features => throw new NotImplementedException();
|
||||||
public bool[] Buttons => _driver.PressedButtons;
|
public bool[] Buttons => _driver.PressedButtons;
|
||||||
|
@ -1049,6 +1049,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void InitializeGame()
|
private void InitializeGame()
|
||||||
{
|
{
|
||||||
|
|
||||||
RendererHostControl.WindowCreated += RendererHost_Created;
|
RendererHostControl.WindowCreated += RendererHost_Created;
|
||||||
|
|
||||||
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
||||||
@ -1058,7 +1059,13 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
AppHost?.Start();
|
AppHost?.Start();
|
||||||
|
|
||||||
|
if (AppHost?.IsSpecialExit() == true)
|
||||||
|
{
|
||||||
|
Window.ForceExit();
|
||||||
|
}
|
||||||
|
|
||||||
AppHost?.DisposeContext();
|
AppHost?.DisposeContext();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleRelaunch()
|
private async Task HandleRelaunch()
|
||||||
|
@ -128,6 +128,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool EnableDockedMode { get; set; }
|
public bool EnableDockedMode { get; set; }
|
||||||
public bool EnableKeyboard { get; set; }
|
public bool EnableKeyboard { get; set; }
|
||||||
public bool EnableMouse { get; set; }
|
public bool EnableMouse { get; set; }
|
||||||
|
public int EnableSpecialExit { get; set; }
|
||||||
public VSyncMode VSyncMode
|
public VSyncMode VSyncMode
|
||||||
{
|
{
|
||||||
get => _vSyncMode;
|
get => _vSyncMode;
|
||||||
@ -259,6 +260,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public int OpenglDebugLevel { get; set; }
|
public int OpenglDebugLevel { get; set; }
|
||||||
public int MemoryMode { get; set; }
|
public int MemoryMode { get; set; }
|
||||||
public int BaseStyleIndex { get; set; }
|
public int BaseStyleIndex { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public int GraphicsBackendIndex
|
public int GraphicsBackendIndex
|
||||||
{
|
{
|
||||||
get => _graphicsBackendIndex;
|
get => _graphicsBackendIndex;
|
||||||
@ -511,6 +514,13 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
EnableDockedMode = config.System.EnableDockedMode;
|
EnableDockedMode = config.System.EnableDockedMode;
|
||||||
EnableKeyboard = config.Hid.EnableKeyboard;
|
EnableKeyboard = config.Hid.EnableKeyboard;
|
||||||
EnableMouse = config.Hid.EnableMouse;
|
EnableMouse = config.Hid.EnableMouse;
|
||||||
|
EnableSpecialExit = config.Hid.SpecialExitEmulator.Value switch
|
||||||
|
{
|
||||||
|
0 => 0, // "Hotkey 'Exit' is Disabled"
|
||||||
|
1 => 1, // "Close app. by hotkey"
|
||||||
|
2 => 2, // "Close game by hotkey"
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
|
||||||
// Keyboard Hotkeys
|
// Keyboard Hotkeys
|
||||||
KeyboardHotkey = new HotkeyConfig(config.Hid.Hotkeys.Value);
|
KeyboardHotkey = new HotkeyConfig(config.Hid.Hotkeys.Value);
|
||||||
@ -618,6 +628,13 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.System.EnableDockedMode.Value = EnableDockedMode;
|
config.System.EnableDockedMode.Value = EnableDockedMode;
|
||||||
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
||||||
config.Hid.EnableMouse.Value = EnableMouse;
|
config.Hid.EnableMouse.Value = EnableMouse;
|
||||||
|
config.Hid.SpecialExitEmulator.Value = EnableSpecialExit switch
|
||||||
|
{
|
||||||
|
0 => 0, // "Hotkey 'Exit' is Disabled",
|
||||||
|
1 => 1, // "Close app. by hotkey",
|
||||||
|
2 => 2, // "Close game by hotkey",
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
|
||||||
// Keyboard Hotkeys
|
// Keyboard Hotkeys
|
||||||
config.Hid.Hotkeys.Value = KeyboardHotkey.GetConfig();
|
config.Hid.Hotkeys.Value = KeyboardHotkey.GetConfig();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView"
|
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
@ -58,6 +58,20 @@
|
|||||||
<TextBlock
|
<TextBlock
|
||||||
Text="{ext:Locale SettingsTabInputDirectMouseAccess}" />
|
Text="{ext:Locale SettingsTabInputDirectMouseAccess}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
|
<ComboBox SelectedIndex="{Binding EnableSpecialExit}"
|
||||||
|
ToolTip.Tip="{ext:Locale SpecialExitTooltip}"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
MinWidth="160">
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabInputDisableExitHotKey}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabInputHotkeyIsCloseApp}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabInputHotkeyIsCloseGame}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -45,6 +45,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
internal readonly AvaHostUIHandler UiHandler;
|
internal readonly AvaHostUIHandler UiHandler;
|
||||||
|
|
||||||
private bool _isLoading;
|
private bool _isLoading;
|
||||||
|
private bool _isExitWithoutConfirm = false;
|
||||||
private bool _applicationsLoadedOnce;
|
private bool _applicationsLoadedOnce;
|
||||||
|
|
||||||
private UserChannelPersistence _userChannelPersistence;
|
private UserChannelPersistence _userChannelPersistence;
|
||||||
@ -571,7 +572,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
protected override void OnClosing(WindowClosingEventArgs e)
|
protected override void OnClosing(WindowClosingEventArgs e)
|
||||||
{
|
{
|
||||||
if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit)
|
if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit && !_isExitWithoutConfirm)
|
||||||
{
|
{
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
|
|
||||||
@ -616,6 +617,12 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
base.OnClosing(e);
|
base.OnClosing(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ForceExit()
|
||||||
|
{
|
||||||
|
_isExitWithoutConfirm = true;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
private void ConfirmExit()
|
private void ConfirmExit()
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
|
@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current version of the file format
|
/// The current version of the file format
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int CurrentVersion = 59;
|
public const int CurrentVersion = 60;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version of the configuration file format
|
/// Version of the configuration file format
|
||||||
@ -366,6 +366,12 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnableMouse { get; set; }
|
public bool EnableMouse { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows you to choose one of several behaviors when pressing hotkeys:
|
||||||
|
/// 0 - Do nothing, 1 - Close the emulator application, 2 - Exit the game.
|
||||||
|
/// </summary>
|
||||||
|
public int SpecialExitEmulator { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hotkey Keyboard Bindings
|
/// Hotkey Keyboard Bindings
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -136,6 +136,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
|
|
||||||
Hid.EnableKeyboard.Value = cff.EnableKeyboard;
|
Hid.EnableKeyboard.Value = cff.EnableKeyboard;
|
||||||
Hid.EnableMouse.Value = cff.EnableMouse;
|
Hid.EnableMouse.Value = cff.EnableMouse;
|
||||||
|
Hid.SpecialExitEmulator.Value = cff.SpecialExitEmulator;
|
||||||
Hid.Hotkeys.Value = cff.Hotkeys;
|
Hid.Hotkeys.Value = cff.Hotkeys;
|
||||||
Hid.InputConfig.Value = cff.InputConfig ?? [];
|
Hid.InputConfig.Value = cff.InputConfig ?? [];
|
||||||
|
|
||||||
@ -414,6 +415,10 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
// This was accidentally enabled by default when it was PRed. That is not what we want,
|
// This was accidentally enabled by default when it was PRed. That is not what we want,
|
||||||
// so as a compromise users who want to use it will simply need to re-enable it once after updating.
|
// so as a compromise users who want to use it will simply need to re-enable it once after updating.
|
||||||
cff.IgnoreApplet = false;
|
cff.IgnoreApplet = false;
|
||||||
|
}),
|
||||||
|
(60, static cff =>
|
||||||
|
{
|
||||||
|
cff.SpecialExitEmulator = 0;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -420,6 +420,13 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<bool> EnableMouse { get; private set; }
|
public ReactiveObject<bool> EnableMouse { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows you to choose one of several behaviors when pressing hotkeys:
|
||||||
|
/// 0 - Do nothing, 1 - Close the emulator application, 2 - Exit the game.
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<int> SpecialExitEmulator { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hotkey Keyboard Bindings
|
/// Hotkey Keyboard Bindings
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -436,6 +443,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
{
|
{
|
||||||
EnableKeyboard = new ReactiveObject<bool>();
|
EnableKeyboard = new ReactiveObject<bool>();
|
||||||
EnableMouse = new ReactiveObject<bool>();
|
EnableMouse = new ReactiveObject<bool>();
|
||||||
|
SpecialExitEmulator = new ReactiveObject<int>();
|
||||||
Hotkeys = new ReactiveObject<KeyboardHotkeys>();
|
Hotkeys = new ReactiveObject<KeyboardHotkeys>();
|
||||||
InputConfig = new ReactiveObject<List<InputConfig>>();
|
InputConfig = new ReactiveObject<List<InputConfig>>();
|
||||||
}
|
}
|
||||||
|
@ -128,6 +128,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
ShowConsole = UI.ShowConsole,
|
ShowConsole = UI.ShowConsole,
|
||||||
EnableKeyboard = Hid.EnableKeyboard,
|
EnableKeyboard = Hid.EnableKeyboard,
|
||||||
EnableMouse = Hid.EnableMouse,
|
EnableMouse = Hid.EnableMouse,
|
||||||
|
SpecialExitEmulator = Hid.SpecialExitEmulator,
|
||||||
Hotkeys = Hid.Hotkeys,
|
Hotkeys = Hid.Hotkeys,
|
||||||
KeyboardConfig = [],
|
KeyboardConfig = [],
|
||||||
ControllerConfig = [],
|
ControllerConfig = [],
|
||||||
@ -241,6 +242,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
UI.WindowStartup.WindowMaximized.Value = false;
|
UI.WindowStartup.WindowMaximized.Value = false;
|
||||||
Hid.EnableKeyboard.Value = false;
|
Hid.EnableKeyboard.Value = false;
|
||||||
Hid.EnableMouse.Value = false;
|
Hid.EnableMouse.Value = false;
|
||||||
|
Hid.SpecialExitEmulator.Value = 0;
|
||||||
Hid.Hotkeys.Value = new KeyboardHotkeys
|
Hid.Hotkeys.Value = new KeyboardHotkeys
|
||||||
{
|
{
|
||||||
ToggleVSyncMode = Key.F1,
|
ToggleVSyncMode = Key.F1,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user