mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-12-23 11:21:21 -08:00
Improve multi-controller support in HID and Controller Applet (#1453)
* Initial commit Enable proper LED patterns Toggle Hotkeys only on focus Ignore Handheld on Docked mode Remove PrimaryController Validate NpadIdType Rewrite NpadDevices to process config in update loop Cleanup * Notify in log periodically when no matched controllers * Remove duplicate StructArrayHelpers in favor of Common.Memory Fix struct padding CS0169 warns in Touchscreen * Remove GTK markup from Controller Applet Use IList instead of List Explicit list capacity in 1ms loop Fix formatting * Restrict ControllerWindow to show valid controller types Add selected player name to ControllerWindow title * ControllerWindow: Fix controller type initial value NpadDevices: Simplify default battery charge * Address AcK's comments Use explicit types and fix formatting * Remove HashSet for SupportedPlayers Fixes potential exceptions due to race * Fix ControllerSupportArg struct packing Also comes with two revisions of struct for 4/8 players max.
This commit is contained in:
parent
01ff648bdf
commit
27179d0218
@ -32,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
byte[] controllerSupportArgPrivate = _normalSession.Pop();
|
byte[] controllerSupportArgPrivate = _normalSession.Pop();
|
||||||
ControllerSupportArgPrivate privateArg = IApplet.ReadStruct<ControllerSupportArgPrivate>(controllerSupportArgPrivate);
|
ControllerSupportArgPrivate privateArg = IApplet.ReadStruct<ControllerSupportArgPrivate>(controllerSupportArgPrivate);
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet ArgPriv {privateArg.PrivateSize} {privateArg.ArgSize} {privateArg.Mode}" +
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet ArgPriv {privateArg.PrivateSize} {privateArg.ArgSize} {privateArg.Mode} " +
|
||||||
$"HoldType:{(NpadJoyHoldType)privateArg.NpadJoyHoldType} StyleSets:{(ControllerType)privateArg.NpadStyleSet}");
|
$"HoldType:{(NpadJoyHoldType)privateArg.NpadJoyHoldType} StyleSets:{(ControllerType)privateArg.NpadStyleSet}");
|
||||||
|
|
||||||
if (privateArg.Mode != ControllerSupportMode.ShowControllerSupport)
|
if (privateArg.Mode != ControllerSupportMode.ShowControllerSupport)
|
||||||
@ -47,33 +47,57 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
|
|
||||||
ControllerSupportArgHeader argHeader;
|
ControllerSupportArgHeader argHeader;
|
||||||
|
|
||||||
if (privateArg.ArgSize == Marshal.SizeOf<ControllerSupportArg>())
|
if (privateArg.ArgSize == Marshal.SizeOf<ControllerSupportArgV7>())
|
||||||
{
|
{
|
||||||
ControllerSupportArg arg = IApplet.ReadStruct<ControllerSupportArg>(controllerSupportArg);
|
ControllerSupportArgV7 arg = IApplet.ReadStruct<ControllerSupportArgV7>(controllerSupportArg);
|
||||||
argHeader = arg.Header;
|
argHeader = arg.Header;
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerSupportArg Version 7 EnableExplainText={arg.EnableExplainText != 0}");
|
||||||
|
// Read enable text here?
|
||||||
|
}
|
||||||
|
else if (privateArg.ArgSize == Marshal.SizeOf<ControllerSupportArgVPre7>())
|
||||||
|
{
|
||||||
|
ControllerSupportArgVPre7 arg = IApplet.ReadStruct<ControllerSupportArgVPre7>(controllerSupportArg);
|
||||||
|
argHeader = arg.Header;
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerSupportArg Version Pre-7 EnableExplainText={arg.EnableExplainText != 0}");
|
||||||
// Read enable text here?
|
// Read enable text here?
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"Unknown revision of ControllerSupportArg.");
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerSupportArg Version Unknown");
|
||||||
|
|
||||||
argHeader = IApplet.ReadStruct<ControllerSupportArgHeader>(controllerSupportArg); // Read just the header
|
argHeader = IApplet.ReadStruct<ControllerSupportArgHeader>(controllerSupportArg); // Read just the header
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet Arg {argHeader.PlayerCountMin} {argHeader.PlayerCountMax} {argHeader.EnableTakeOverConnection} {argHeader.EnableSingleMode}");
|
int playerMin = argHeader.PlayerCountMin;
|
||||||
|
int playerMax = argHeader.PlayerCountMax;
|
||||||
|
|
||||||
// Currently, the only purpose of this applet is to help
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet Arg {playerMin} {playerMax} {argHeader.EnableTakeOverConnection} {argHeader.EnableSingleMode}");
|
||||||
// choose the primary input controller for the game
|
|
||||||
// TODO: Ideally should hook back to HID.Controller. When applet is called, can choose appropriate controller and attach to appropriate id.
|
int configuredCount = 0;
|
||||||
if (argHeader.PlayerCountMin > 1)
|
PlayerIndex primaryIndex = PlayerIndex.Unknown;
|
||||||
|
while (!_system.Device.Hid.Npads.Validate(playerMin, playerMax, (ControllerType)privateArg.NpadStyleSet, out configuredCount, out primaryIndex))
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.ServiceHid, "More than one controller was requested.");
|
ControllerAppletUiArgs uiArgs = new ControllerAppletUiArgs
|
||||||
|
{
|
||||||
|
PlayerCountMin = playerMin,
|
||||||
|
PlayerCountMax = playerMax,
|
||||||
|
SupportedStyles = (ControllerType)privateArg.NpadStyleSet,
|
||||||
|
SupportedPlayers = _system.Device.Hid.Npads.GetSupportedPlayers(),
|
||||||
|
IsDocked = _system.State.DockedMode
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!_system.Device.UiHandler.DisplayMessageDialog(uiArgs))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ControllerSupportResultInfo result = new ControllerSupportResultInfo
|
ControllerSupportResultInfo result = new ControllerSupportResultInfo
|
||||||
{
|
{
|
||||||
PlayerCount = 1,
|
PlayerCount = (sbyte)configuredCount,
|
||||||
SelectedId = (uint)GetNpadIdTypeFromIndex(_system.Device.Hid.Npads.PrimaryController)
|
SelectedId = (uint)GetNpadIdTypeFromIndex(primaryIndex)
|
||||||
};
|
};
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet ReturnResult {result.PlayerCount} {result.SelectedId}");
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet ReturnResult {result.PlayerCount} {result.SelectedId}");
|
||||||
|
14
Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs
Normal file
14
Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Applets
|
||||||
|
{
|
||||||
|
public struct ControllerAppletUiArgs
|
||||||
|
{
|
||||||
|
public int PlayerCountMin;
|
||||||
|
public int PlayerCountMax;
|
||||||
|
public ControllerType SupportedStyles;
|
||||||
|
public IEnumerable<PlayerIndex> SupportedPlayers;
|
||||||
|
public bool IsDocked;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Applets
|
namespace Ryujinx.HLE.HOS.Applets
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0649
|
#pragma warning disable CS0649
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack=1)]
|
||||||
struct ControllerSupportArgHeader
|
struct ControllerSupportArgHeader
|
||||||
{
|
{
|
||||||
public sbyte PlayerCountMin;
|
public sbyte PlayerCountMin;
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Applets
|
namespace Ryujinx.HLE.HOS.Applets
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0649
|
#pragma warning disable CS0649
|
||||||
// (8.0.0+ version)
|
// (8.0.0+ version)
|
||||||
unsafe struct ControllerSupportArg
|
[StructLayout(LayoutKind.Sequential, Pack=1)]
|
||||||
|
unsafe struct ControllerSupportArgV7
|
||||||
{
|
{
|
||||||
public ControllerSupportArgHeader Header;
|
public ControllerSupportArgHeader Header;
|
||||||
public fixed uint IdentificationColor[8];
|
public fixed uint IdentificationColor[8];
|
@ -0,0 +1,16 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Applets
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0649
|
||||||
|
// (1.0.0+ version)
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack=1)]
|
||||||
|
unsafe struct ControllerSupportArgVPre7
|
||||||
|
{
|
||||||
|
public ControllerSupportArgHeader Header;
|
||||||
|
public fixed uint IdentificationColor[4];
|
||||||
|
public byte EnableExplainText;
|
||||||
|
public fixed byte ExplainText[4 * 0x81];
|
||||||
|
}
|
||||||
|
#pragma warning restore CS0649
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Applets
|
namespace Ryujinx.HLE.HOS.Applets
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0649
|
#pragma warning disable CS0649
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack=1)]
|
||||||
unsafe struct ControllerSupportResultInfo
|
unsafe struct ControllerSupportResultInfo
|
||||||
{
|
{
|
||||||
public sbyte PlayerCount;
|
public sbyte PlayerCount;
|
||||||
|
@ -249,6 +249,9 @@ namespace Ryujinx.HLE.HOS
|
|||||||
AppletState.EnqueueMessage(MessageInfo.OperationModeChanged);
|
AppletState.EnqueueMessage(MessageInfo.OperationModeChanged);
|
||||||
AppletState.EnqueueMessage(MessageInfo.PerformanceModeChanged);
|
AppletState.EnqueueMessage(MessageInfo.PerformanceModeChanged);
|
||||||
SignalDisplayResolutionChange();
|
SignalDisplayResolutionChange();
|
||||||
|
|
||||||
|
// Reconfigure controllers
|
||||||
|
Device.Hid.RefreshInputConfig(ConfigurationState.Instance.Hid.InputConfig.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.HLE.Exceptions;
|
using Ryujinx.HLE.Exceptions;
|
||||||
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
@ -65,6 +67,24 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
Npads = new NpadDevices(_device, true);
|
Npads = new NpadDevices(_device, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void RefreshInputConfig(List<InputConfig> inputConfig)
|
||||||
|
{
|
||||||
|
ControllerConfig[] npadConfig = new ControllerConfig[inputConfig.Count];
|
||||||
|
|
||||||
|
for (int i = 0; i < npadConfig.Length; ++i)
|
||||||
|
{
|
||||||
|
npadConfig[i].Player = (PlayerIndex)inputConfig[i].PlayerIndex;
|
||||||
|
npadConfig[i].Type = (ControllerType)inputConfig[i].ControllerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
_device.Hid.Npads.Configure(npadConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RefreshInputConfigEvent(object _, ReactiveEventArgs<List<InputConfig>> args)
|
||||||
|
{
|
||||||
|
RefreshInputConfig(args.NewValue);
|
||||||
|
}
|
||||||
|
|
||||||
public ControllerKeys UpdateStickButtons(JoystickPosition leftStick, JoystickPosition rightStick)
|
public ControllerKeys UpdateStickButtons(JoystickPosition leftStick, JoystickPosition rightStick)
|
||||||
{
|
{
|
||||||
ControllerKeys result = 0;
|
ControllerKeys result = 0;
|
||||||
|
@ -1,74 +1,118 @@
|
|||||||
using System;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
{
|
{
|
||||||
public class NpadDevices : BaseDevice
|
public class NpadDevices : BaseDevice
|
||||||
{
|
{
|
||||||
internal NpadJoyHoldType JoyHold = NpadJoyHoldType.Vertical;
|
private const BatteryCharge DefaultBatteryCharge = BatteryCharge.Percent100;
|
||||||
internal bool SixAxisActive = false; // TODO: link to hidserver when implemented
|
|
||||||
|
|
||||||
private enum FilterState
|
private const int NoMatchNotifyFrequencyMs = 2000;
|
||||||
{
|
private int _activeCount;
|
||||||
Unconfigured = 0,
|
private long _lastNotifyTimestamp;
|
||||||
Configured = 1,
|
|
||||||
Accepted = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct NpadConfig
|
|
||||||
{
|
|
||||||
public ControllerType ConfiguredType;
|
|
||||||
public FilterState State;
|
|
||||||
}
|
|
||||||
|
|
||||||
private const int _maxControllers = 9; // Players1-8 and Handheld
|
|
||||||
private NpadConfig[] _configuredNpads;
|
|
||||||
|
|
||||||
private ControllerType _supportedStyleSets = ControllerType.ProController |
|
|
||||||
ControllerType.JoyconPair |
|
|
||||||
ControllerType.JoyconLeft |
|
|
||||||
ControllerType.JoyconRight |
|
|
||||||
ControllerType.Handheld;
|
|
||||||
|
|
||||||
public ControllerType SupportedStyleSets
|
|
||||||
{
|
|
||||||
get => _supportedStyleSets;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_supportedStyleSets != value) // Deal with spamming
|
|
||||||
{
|
|
||||||
_supportedStyleSets = value;
|
|
||||||
MatchControllers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerIndex PrimaryController { get; set; } = PlayerIndex.Unknown;
|
|
||||||
|
|
||||||
|
public const int MaxControllers = 9; // Players 1-8 and Handheld
|
||||||
|
private ControllerType[] _configuredTypes;
|
||||||
private KEvent[] _styleSetUpdateEvents;
|
private KEvent[] _styleSetUpdateEvents;
|
||||||
|
private bool[] _supportedPlayers;
|
||||||
|
|
||||||
private static readonly Array3<BatteryCharge> _fullBattery;
|
internal NpadJoyHoldType JoyHold { get; set; }
|
||||||
|
internal bool SixAxisActive = false; // TODO: link to hidserver when implemented
|
||||||
|
internal ControllerType SupportedStyleSets { get; set; }
|
||||||
|
|
||||||
public NpadDevices(Switch device, bool active = true) : base(device, active)
|
public NpadDevices(Switch device, bool active = true) : base(device, active)
|
||||||
{
|
{
|
||||||
_configuredNpads = new NpadConfig[_maxControllers];
|
_configuredTypes = new ControllerType[MaxControllers];
|
||||||
|
|
||||||
_styleSetUpdateEvents = new KEvent[_maxControllers];
|
SupportedStyleSets = ControllerType.Handheld | ControllerType.JoyconPair |
|
||||||
|
ControllerType.JoyconLeft | ControllerType.JoyconRight |
|
||||||
|
ControllerType.ProController;
|
||||||
|
|
||||||
|
_supportedPlayers = new bool[MaxControllers];
|
||||||
|
_supportedPlayers.AsSpan().Fill(true);
|
||||||
|
|
||||||
|
_styleSetUpdateEvents = new KEvent[MaxControllers];
|
||||||
for (int i = 0; i < _styleSetUpdateEvents.Length; ++i)
|
for (int i = 0; i < _styleSetUpdateEvents.Length; ++i)
|
||||||
{
|
{
|
||||||
_styleSetUpdateEvents[i] = new KEvent(_device.System.KernelContext);
|
_styleSetUpdateEvents[i] = new KEvent(_device.System.KernelContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
_fullBattery[0] = _fullBattery[1] = _fullBattery[2] = BatteryCharge.Percent100;
|
_activeCount = 0;
|
||||||
|
|
||||||
|
JoyHold = NpadJoyHoldType.Vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddControllers(params ControllerConfig[] configs)
|
internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
|
||||||
{
|
{
|
||||||
|
return ref _styleSetUpdateEvents[(int)player];
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ClearSupportedPlayers()
|
||||||
|
{
|
||||||
|
_supportedPlayers.AsSpan().Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetSupportedPlayer(PlayerIndex player, bool supported = true)
|
||||||
|
{
|
||||||
|
_supportedPlayers[(int)player] = supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IEnumerable<PlayerIndex> GetSupportedPlayers()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _supportedPlayers.Length; ++i)
|
||||||
|
{
|
||||||
|
if (_supportedPlayers[i])
|
||||||
|
{
|
||||||
|
yield return (PlayerIndex)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Validate(int playerMin, int playerMax, ControllerType acceptedTypes, out int configuredCount, out PlayerIndex primaryIndex)
|
||||||
|
{
|
||||||
|
primaryIndex = PlayerIndex.Unknown;
|
||||||
|
configuredCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < MaxControllers; ++i)
|
||||||
|
{
|
||||||
|
ControllerType npad = _configuredTypes[i];
|
||||||
|
|
||||||
|
if (npad == ControllerType.Handheld && _device.System.State.DockedMode)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ControllerType currentType = _device.Hid.SharedMemory.Npads[i].Header.Type;
|
||||||
|
|
||||||
|
if (currentType != ControllerType.None && (npad & acceptedTypes) != 0 && _supportedPlayers[i])
|
||||||
|
{
|
||||||
|
configuredCount++;
|
||||||
|
if (primaryIndex == PlayerIndex.Unknown)
|
||||||
|
{
|
||||||
|
primaryIndex = (PlayerIndex)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configuredCount < playerMin || configuredCount > playerMax || primaryIndex == PlayerIndex.Unknown)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(params ControllerConfig[] configs)
|
||||||
|
{
|
||||||
|
_configuredTypes = new ControllerType[MaxControllers];
|
||||||
|
|
||||||
for (int i = 0; i < configs.Length; ++i)
|
for (int i = 0; i < configs.Length; ++i)
|
||||||
{
|
{
|
||||||
PlayerIndex player = configs[i].Player;
|
PlayerIndex player = configs[i].Player;
|
||||||
ControllerType controllerType = configs[i].Type;
|
ControllerType controllerType = configs[i].Type;
|
||||||
|
|
||||||
if (player > PlayerIndex.Handheld)
|
if (player > PlayerIndex.Handheld)
|
||||||
@ -81,77 +125,87 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
player = PlayerIndex.Handheld;
|
player = PlayerIndex.Handheld;
|
||||||
}
|
}
|
||||||
|
|
||||||
_configuredNpads[(int)player] = new NpadConfig { ConfiguredType = controllerType, State = FilterState.Configured };
|
_configuredTypes[(int)player] = controllerType;
|
||||||
}
|
|
||||||
|
|
||||||
MatchControllers();
|
Logger.Info?.Print(LogClass.Hid, $"Configured Controller {controllerType} to {player}");
|
||||||
}
|
|
||||||
|
|
||||||
private void MatchControllers()
|
|
||||||
{
|
|
||||||
PrimaryController = PlayerIndex.Unknown;
|
|
||||||
|
|
||||||
for (int i = 0; i < _configuredNpads.Length; ++i)
|
|
||||||
{
|
|
||||||
ref NpadConfig config = ref _configuredNpads[i];
|
|
||||||
|
|
||||||
if (config.State == FilterState.Unconfigured)
|
|
||||||
{
|
|
||||||
continue; // Ignore unconfigured
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((config.ConfiguredType & _supportedStyleSets) == 0)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Hid, $"ControllerType {config.ConfiguredType} (connected to {(PlayerIndex)i}) not supported by game. Removing...");
|
|
||||||
|
|
||||||
config.State = FilterState.Configured;
|
|
||||||
_device.Hid.SharedMemory.Npads[i] = new ShMemNpad(); // Zero it
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
InitController((PlayerIndex)i, config.ConfiguredType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Couldn't find any matching configuration. Reassign to something that works.
|
|
||||||
if (PrimaryController == PlayerIndex.Unknown)
|
|
||||||
{
|
|
||||||
ControllerType[] npadsTypeList = (ControllerType[])Enum.GetValues(typeof(ControllerType));
|
|
||||||
|
|
||||||
// Skip None Type
|
|
||||||
for (int i = 1; i < npadsTypeList.Length; ++i)
|
|
||||||
{
|
|
||||||
ControllerType controllerType = npadsTypeList[i];
|
|
||||||
if ((controllerType & _supportedStyleSets) != 0)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Hid, $"No matching controllers found. Reassigning input as ControllerType {controllerType}...");
|
|
||||||
|
|
||||||
InitController(controllerType == ControllerType.Handheld ? PlayerIndex.Handheld : PlayerIndex.Player1, controllerType);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Error?.Print(LogClass.Hid, "Couldn't find any appropriate controller.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
|
public void Update(IList<GamepadInput> states)
|
||||||
{
|
{
|
||||||
return ref _styleSetUpdateEvents[(int)player];
|
Remap();
|
||||||
|
|
||||||
|
UpdateAllEntries();
|
||||||
|
|
||||||
|
// Update configured inputs
|
||||||
|
for (int i = 0; i < states.Count; ++i)
|
||||||
|
{
|
||||||
|
UpdateInput(states[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitController(PlayerIndex player, ControllerType type)
|
private void Remap()
|
||||||
{
|
{
|
||||||
if (type == ControllerType.Handheld)
|
// Remap/Init if necessary
|
||||||
|
for (int i = 0; i < MaxControllers; ++i)
|
||||||
{
|
{
|
||||||
player = PlayerIndex.Handheld;
|
ControllerType config = _configuredTypes[i];
|
||||||
|
|
||||||
|
// Remove Handheld config when Docked
|
||||||
|
if (config == ControllerType.Handheld && _device.System.State.DockedMode)
|
||||||
|
{
|
||||||
|
config = ControllerType.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-remap ProController and JoyconPair
|
||||||
|
if (config == ControllerType.JoyconPair && (SupportedStyleSets & ControllerType.JoyconPair) == 0 && (SupportedStyleSets & ControllerType.ProController) != 0)
|
||||||
|
{
|
||||||
|
config = ControllerType.ProController;
|
||||||
|
}
|
||||||
|
else if (config == ControllerType.ProController && (SupportedStyleSets & ControllerType.ProController) == 0 && (SupportedStyleSets & ControllerType.JoyconPair) != 0)
|
||||||
|
{
|
||||||
|
config = ControllerType.JoyconPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check StyleSet and PlayerSet
|
||||||
|
if ((config & SupportedStyleSets) == 0 || !_supportedPlayers[i])
|
||||||
|
{
|
||||||
|
config = ControllerType.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetupNpad((PlayerIndex)i, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_activeCount == 0 && PerformanceCounter.ElapsedMilliseconds > _lastNotifyTimestamp + NoMatchNotifyFrequencyMs)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Hid, $"No matching controllers found. Application requests '{SupportedStyleSets}' on '{string.Join(", ", GetSupportedPlayers())}'");
|
||||||
|
_lastNotifyTimestamp = PerformanceCounter.ElapsedMilliseconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupNpad(PlayerIndex player, ControllerType type)
|
||||||
|
{
|
||||||
ref ShMemNpad controller = ref _device.Hid.SharedMemory.Npads[(int)player];
|
ref ShMemNpad controller = ref _device.Hid.SharedMemory.Npads[(int)player];
|
||||||
|
|
||||||
|
ControllerType oldType = controller.Header.Type;
|
||||||
|
|
||||||
|
if (oldType == type)
|
||||||
|
{
|
||||||
|
return; // Already configured
|
||||||
|
}
|
||||||
|
|
||||||
controller = new ShMemNpad(); // Zero it
|
controller = new ShMemNpad(); // Zero it
|
||||||
|
|
||||||
|
if (type == ControllerType.None)
|
||||||
|
{
|
||||||
|
_styleSetUpdateEvents[(int)player].ReadableEvent.Signal(); // Signal disconnect
|
||||||
|
_activeCount--;
|
||||||
|
|
||||||
|
Logger.Info?.Print(LogClass.Hid, $"Disconnected Controller {oldType} from {player}");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Allow customizing colors at config
|
// TODO: Allow customizing colors at config
|
||||||
NpadStateHeader defaultHeader = new NpadStateHeader
|
NpadStateHeader defaultHeader = new NpadStateHeader
|
||||||
{
|
{
|
||||||
@ -168,7 +222,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
NpadSystemProperties.PowerInfo1Connected |
|
NpadSystemProperties.PowerInfo1Connected |
|
||||||
NpadSystemProperties.PowerInfo2Connected;
|
NpadSystemProperties.PowerInfo2Connected;
|
||||||
|
|
||||||
controller.BatteryState = _fullBattery;
|
controller.BatteryState.ToSpan().Fill(DefaultBatteryCharge);
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
@ -217,19 +271,13 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
|
|
||||||
controller.Header = defaultHeader;
|
controller.Header = defaultHeader;
|
||||||
|
|
||||||
if (PrimaryController == PlayerIndex.Unknown)
|
|
||||||
{
|
|
||||||
PrimaryController = player;
|
|
||||||
}
|
|
||||||
|
|
||||||
_configuredNpads[(int)player].State = FilterState.Accepted;
|
|
||||||
|
|
||||||
_styleSetUpdateEvents[(int)player].ReadableEvent.Signal();
|
_styleSetUpdateEvents[(int)player].ReadableEvent.Signal();
|
||||||
|
_activeCount++;
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Hid, $"Connected ControllerType {type} to PlayerIndex {player}");
|
Logger.Info?.Print(LogClass.Hid, $"Connected Controller {type} to {player}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static NpadLayoutsIndex ControllerTypeToLayout(ControllerType controllerType)
|
private static NpadLayoutsIndex ControllerTypeToNpadLayout(ControllerType controllerType)
|
||||||
=> controllerType switch
|
=> controllerType switch
|
||||||
{
|
{
|
||||||
ControllerType.ProController => NpadLayoutsIndex.ProController,
|
ControllerType.ProController => NpadLayoutsIndex.ProController,
|
||||||
@ -241,43 +289,28 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
_ => NpadLayoutsIndex.SystemExternal
|
_ => NpadLayoutsIndex.SystemExternal
|
||||||
};
|
};
|
||||||
|
|
||||||
public void SetGamepadsInput(params GamepadInput[] states)
|
private void UpdateInput(GamepadInput state)
|
||||||
{
|
{
|
||||||
UpdateAllEntries();
|
if (state.PlayerId == PlayerIndex.Unknown)
|
||||||
|
|
||||||
for (int i = 0; i < states.Length; ++i)
|
|
||||||
{
|
|
||||||
SetGamepadState(states[i].PlayerId, states[i].Buttons, states[i].LStick, states[i].RStick);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetGamepadState(PlayerIndex player, ControllerKeys buttons,
|
|
||||||
JoystickPosition leftJoystick, JoystickPosition rightJoystick)
|
|
||||||
{
|
|
||||||
if (player == PlayerIndex.Auto)
|
|
||||||
{
|
|
||||||
player = PrimaryController;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (player == PlayerIndex.Unknown)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_configuredNpads[(int)player].State != FilterState.Accepted)
|
ref ShMemNpad currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId];
|
||||||
|
|
||||||
|
if (currentNpad.Header.Type == ControllerType.None)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref ShMemNpad currentNpad = ref _device.Hid.SharedMemory.Npads[(int)player];
|
ref NpadLayout currentLayout = ref currentNpad.Layouts[(int)ControllerTypeToNpadLayout(currentNpad.Header.Type)];
|
||||||
ref NpadLayout currentLayout = ref currentNpad.Layouts[(int)ControllerTypeToLayout(currentNpad.Header.Type)];
|
|
||||||
ref NpadState currentEntry = ref currentLayout.Entries[(int)currentLayout.Header.LatestEntry];
|
ref NpadState currentEntry = ref currentLayout.Entries[(int)currentLayout.Header.LatestEntry];
|
||||||
|
|
||||||
currentEntry.Buttons = buttons;
|
currentEntry.Buttons = state.Buttons;
|
||||||
currentEntry.LStickX = leftJoystick.Dx;
|
currentEntry.LStickX = state.LStick.Dx;
|
||||||
currentEntry.LStickY = leftJoystick.Dy;
|
currentEntry.LStickY = state.LStick.Dy;
|
||||||
currentEntry.RStickX = rightJoystick.Dx;
|
currentEntry.RStickX = state.RStick.Dx;
|
||||||
currentEntry.RStickY = rightJoystick.Dy;
|
currentEntry.RStickY = state.RStick.Dy;
|
||||||
|
|
||||||
// Mirror data to Default layout just in case
|
// Mirror data to Default layout just in case
|
||||||
ref NpadLayout mainLayout = ref currentNpad.Layouts[(int)NpadLayoutsIndex.SystemExternal];
|
ref NpadLayout mainLayout = ref currentNpad.Layouts[(int)NpadLayoutsIndex.SystemExternal];
|
||||||
|
@ -35,5 +35,19 @@ namespace Ryujinx.HLE.HOS.Services.Hid.HidServer
|
|||||||
PlayerIndex.Unknown => NpadIdType.Unknown,
|
PlayerIndex.Unknown => NpadIdType.Unknown,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(index))
|
_ => throw new ArgumentOutOfRangeException(nameof(index))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static long GetLedPatternFromNpadId(NpadIdType npadIdType)
|
||||||
|
=> npadIdType switch
|
||||||
|
{
|
||||||
|
NpadIdType.Player1 => 0b0001,
|
||||||
|
NpadIdType.Player2 => 0b0011,
|
||||||
|
NpadIdType.Player3 => 0b0111,
|
||||||
|
NpadIdType.Player4 => 0b1111,
|
||||||
|
NpadIdType.Player5 => 0b1001,
|
||||||
|
NpadIdType.Player6 => 0b0101,
|
||||||
|
NpadIdType.Player7 => 0b1101,
|
||||||
|
NpadIdType.Player8 => 0b0110,
|
||||||
|
_ => 0b0000
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -594,9 +594,18 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
|
|
||||||
NpadIdType[] supportedPlayerIds = new NpadIdType[arraySize];
|
NpadIdType[] supportedPlayerIds = new NpadIdType[arraySize];
|
||||||
|
|
||||||
|
context.Device.Hid.Npads.ClearSupportedPlayers();
|
||||||
|
|
||||||
for (int i = 0; i < arraySize; ++i)
|
for (int i = 0; i < arraySize; ++i)
|
||||||
{
|
{
|
||||||
supportedPlayerIds[i] = context.Memory.Read<NpadIdType>((ulong)(context.Request.PtrBuff[0].Position + i * 4));
|
NpadIdType id = context.Memory.Read<NpadIdType>((ulong)(context.Request.PtrBuff[0].Position + i * 4));
|
||||||
|
|
||||||
|
if (id >= 0)
|
||||||
|
{
|
||||||
|
context.Device.Hid.Npads.SetSupportedPlayer(HidUtils.GetIndexFromNpadIdType(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
supportedPlayerIds[i] = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"{arraySize} " + string.Join(",", supportedPlayerIds));
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, $"{arraySize} " + string.Join(",", supportedPlayerIds));
|
||||||
@ -665,9 +674,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
// GetPlayerLedPattern(uint NpadId) -> ulong LedPattern
|
// GetPlayerLedPattern(uint NpadId) -> ulong LedPattern
|
||||||
public ResultCode GetPlayerLedPattern(ServiceCtx context)
|
public ResultCode GetPlayerLedPattern(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int npadId = context.RequestData.ReadInt32();
|
NpadIdType npadId = (NpadIdType)context.RequestData.ReadInt32();
|
||||||
|
|
||||||
long ledPattern = 0;
|
long ledPattern = HidUtils.GetLedPatternFromNpadId(npadId);
|
||||||
|
|
||||||
context.ResponseData.Write(ledPattern);
|
context.ResponseData.Write(ledPattern);
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
{
|
{
|
||||||
unsafe struct ShMemDebugPad
|
unsafe struct ShMemDebugPad
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using Ryujinx.Common.Memory;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
{
|
{
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
{
|
{
|
||||||
unsafe struct ShMemKeyboard
|
unsafe struct ShMemKeyboard
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
{
|
{
|
||||||
unsafe struct ShMemMouse
|
unsafe struct ShMemMouse
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
{
|
{
|
||||||
// TODO: Add missing structs
|
// TODO: Add missing structs
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
{
|
{
|
||||||
struct NpadLayout
|
struct NpadLayout
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
{
|
{
|
||||||
struct NpadSixAxis
|
struct NpadSixAxis
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
|
||||||
{
|
|
||||||
#pragma warning disable CS0169
|
|
||||||
struct Array2<T> where T : unmanaged
|
|
||||||
{
|
|
||||||
T e0, e1;
|
|
||||||
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 2)[index];
|
|
||||||
public int Length => 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Array3<T> where T : unmanaged
|
|
||||||
{
|
|
||||||
T e0, e1, e2;
|
|
||||||
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 3)[index];
|
|
||||||
public int Length => 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Array6<T> where T : unmanaged
|
|
||||||
{
|
|
||||||
T e0, e1, e2, e3, e4, e5;
|
|
||||||
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 6)[index];
|
|
||||||
public int Length => 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Array7<T> where T : unmanaged
|
|
||||||
{
|
|
||||||
T e0, e1, e2, e3, e4, e5, e6;
|
|
||||||
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 7)[index];
|
|
||||||
public int Length => 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Array10<T> where T : unmanaged
|
|
||||||
{
|
|
||||||
T e0, e1, e2, e3, e4, e5, e6, e7, e8, e9;
|
|
||||||
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 10)[index];
|
|
||||||
public int Length => 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Array16<T> where T : unmanaged
|
|
||||||
{
|
|
||||||
T e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15;
|
|
||||||
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 16)[index];
|
|
||||||
public int Length => 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Array17<T> where T : unmanaged
|
|
||||||
{
|
|
||||||
T e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16;
|
|
||||||
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 17)[index];
|
|
||||||
public int Length => 17;
|
|
||||||
}
|
|
||||||
#pragma warning restore CS0169
|
|
||||||
}
|
|
@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
{
|
{
|
||||||
unsafe struct ShMemTouchScreen
|
unsafe struct ShMemTouchScreen
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
{
|
{
|
||||||
struct TouchScreenState
|
struct TouchScreenState
|
||||||
|
@ -3,13 +3,17 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
struct TouchScreenStateData
|
struct TouchScreenStateData
|
||||||
{
|
{
|
||||||
public ulong SampleTimestamp;
|
public ulong SampleTimestamp;
|
||||||
|
#pragma warning disable CS0169
|
||||||
uint _padding;
|
uint _padding;
|
||||||
|
#pragma warning restore CS0169
|
||||||
public uint TouchIndex;
|
public uint TouchIndex;
|
||||||
public uint X;
|
public uint X;
|
||||||
public uint Y;
|
public uint Y;
|
||||||
public uint DiameterX;
|
public uint DiameterX;
|
||||||
public uint DiameterY;
|
public uint DiameterY;
|
||||||
public uint Angle;
|
public uint Angle;
|
||||||
|
#pragma warning disable CS0169
|
||||||
uint _padding2;
|
uint _padding2;
|
||||||
|
#pragma warning restore CS0169
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,5 +10,17 @@ namespace Ryujinx.HLE
|
|||||||
/// <param name="userText">Text that the user entered. Set to `null` on internal errors</param>
|
/// <param name="userText">Text that the user entered. Set to `null` on internal errors</param>
|
||||||
/// <returns>True when OK is pressed, False otherwise. Also returns True on internal errors</returns>
|
/// <returns>True when OK is pressed, False otherwise. Also returns True on internal errors</returns>
|
||||||
bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText);
|
bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a Message Dialog box to the user and blocks until it is closed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True when OK is pressed, False otherwise.</returns>
|
||||||
|
bool DisplayMessageDialog(string title, string message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a Message Dialog box specific to Controller Applet and blocks until it is closed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True when OK is pressed, False otherwise.</returns>
|
||||||
|
bool DisplayMessageDialog(ControllerAppletUiArgs args);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -114,6 +114,10 @@ namespace Ryujinx.HLE
|
|||||||
System.GlobalAccessLogMode = ConfigurationState.Instance.System.FsGlobalAccessLogMode;
|
System.GlobalAccessLogMode = ConfigurationState.Instance.System.FsGlobalAccessLogMode;
|
||||||
|
|
||||||
ServiceConfiguration.IgnoreMissingServices = ConfigurationState.Instance.System.IgnoreMissingServices;
|
ServiceConfiguration.IgnoreMissingServices = ConfigurationState.Instance.System.IgnoreMissingServices;
|
||||||
|
|
||||||
|
// Configure controllers
|
||||||
|
Hid.RefreshInputConfig(ConfigurationState.Instance.Hid.InputConfig.Value);
|
||||||
|
ConfigurationState.Instance.Hid.InputConfig.Event += Hid.RefreshInputConfigEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IntegrityCheckLevel GetIntegrityCheckLevel()
|
public static IntegrityCheckLevel GetIntegrityCheckLevel()
|
||||||
@ -177,6 +181,8 @@ namespace Ryujinx.HLE
|
|||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
|
ConfigurationState.Instance.Hid.InputConfig.Event -= Hid.RefreshInputConfigEvent;
|
||||||
|
|
||||||
System.Dispose();
|
System.Dispose();
|
||||||
Host1x.Dispose();
|
Host1x.Dispose();
|
||||||
AudioOut.Dispose();
|
AudioOut.Dispose();
|
||||||
|
@ -5,6 +5,7 @@ using Ryujinx.Common.Utilities;
|
|||||||
using Ryujinx.Configuration;
|
using Ryujinx.Configuration;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@ -91,6 +92,23 @@ namespace Ryujinx.Ui
|
|||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_inputConfig = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerIndex);
|
_inputConfig = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerIndex);
|
||||||
|
|
||||||
|
Title = $"Ryujinx - Controller Settings - {_playerIndex}";
|
||||||
|
|
||||||
|
if (_playerIndex == PlayerIndex.Handheld)
|
||||||
|
{
|
||||||
|
_controllerType.Append(ControllerType.Handheld.ToString(), "Handheld");
|
||||||
|
_controllerType.Sensitive = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_controllerType.Append(ControllerType.ProController.ToString(), "Pro Controller");
|
||||||
|
_controllerType.Append(ControllerType.JoyconPair.ToString(), "Joycon Pair");
|
||||||
|
_controllerType.Append(ControllerType.JoyconLeft.ToString(), "Joycon Left");
|
||||||
|
_controllerType.Append(ControllerType.JoyconRight.ToString(), "Joycon Right");
|
||||||
|
}
|
||||||
|
|
||||||
|
_controllerType.Active = 0; // Set initial value to first in list.
|
||||||
|
|
||||||
//Bind Events
|
//Bind Events
|
||||||
_lStickX.Clicked += Button_Pressed;
|
_lStickX.Clicked += Button_Pressed;
|
||||||
_lStickY.Clicked += Button_Pressed;
|
_lStickY.Clicked += Button_Pressed;
|
||||||
@ -278,7 +296,12 @@ namespace Ryujinx.Ui
|
|||||||
switch (config)
|
switch (config)
|
||||||
{
|
{
|
||||||
case KeyboardConfig keyboardConfig:
|
case KeyboardConfig keyboardConfig:
|
||||||
_controllerType.SetActiveId(keyboardConfig.ControllerType.ToString());
|
if (!_controllerType.SetActiveId(keyboardConfig.ControllerType.ToString()))
|
||||||
|
{
|
||||||
|
_controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld
|
||||||
|
? ControllerType.Handheld.ToString()
|
||||||
|
: ControllerType.ProController.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
_lStickUp.Label = keyboardConfig.LeftJoycon.StickUp.ToString();
|
_lStickUp.Label = keyboardConfig.LeftJoycon.StickUp.ToString();
|
||||||
_lStickDown.Label = keyboardConfig.LeftJoycon.StickDown.ToString();
|
_lStickDown.Label = keyboardConfig.LeftJoycon.StickDown.ToString();
|
||||||
@ -310,7 +333,12 @@ namespace Ryujinx.Ui
|
|||||||
_rSr.Label = keyboardConfig.RightJoycon.ButtonSr.ToString();
|
_rSr.Label = keyboardConfig.RightJoycon.ButtonSr.ToString();
|
||||||
break;
|
break;
|
||||||
case ControllerConfig controllerConfig:
|
case ControllerConfig controllerConfig:
|
||||||
_controllerType.SetActiveId(controllerConfig.ControllerType.ToString());
|
if (!_controllerType.SetActiveId(controllerConfig.ControllerType.ToString()))
|
||||||
|
{
|
||||||
|
_controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld
|
||||||
|
? ControllerType.Handheld.ToString()
|
||||||
|
: ControllerType.ProController.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
_lStickX.Label = controllerConfig.LeftJoycon.StickX.ToString();
|
_lStickX.Label = controllerConfig.LeftJoycon.StickX.ToString();
|
||||||
_invertLStickX.Active = controllerConfig.LeftJoycon.InvertStickX;
|
_invertLStickX.Active = controllerConfig.LeftJoycon.InvertStickX;
|
||||||
@ -894,24 +922,31 @@ namespace Ryujinx.Ui
|
|||||||
{
|
{
|
||||||
InputConfig inputConfig = GetValues();
|
InputConfig inputConfig = GetValues();
|
||||||
|
|
||||||
|
var newConfig = new List<InputConfig>();
|
||||||
|
newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);
|
||||||
|
|
||||||
if (_inputConfig == null && inputConfig != null)
|
if (_inputConfig == null && inputConfig != null)
|
||||||
{
|
{
|
||||||
ConfigurationState.Instance.Hid.InputConfig.Value.Add(inputConfig);
|
newConfig.Add(inputConfig);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_inputDevice.ActiveId == "disabled")
|
if (_inputDevice.ActiveId == "disabled")
|
||||||
{
|
{
|
||||||
ConfigurationState.Instance.Hid.InputConfig.Value.Remove(_inputConfig);
|
newConfig.Remove(_inputConfig);
|
||||||
}
|
}
|
||||||
else if (inputConfig != null)
|
else if (inputConfig != null)
|
||||||
{
|
{
|
||||||
int index = ConfigurationState.Instance.Hid.InputConfig.Value.IndexOf(_inputConfig);
|
int index = newConfig.IndexOf(_inputConfig);
|
||||||
|
|
||||||
ConfigurationState.Instance.Hid.InputConfig.Value[index] = inputConfig;
|
newConfig[index] = inputConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Atomically replace and signal input change.
|
||||||
|
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
|
||||||
|
ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;
|
||||||
|
|
||||||
MainWindow.SaveConfig();
|
MainWindow.SaveConfig();
|
||||||
|
|
||||||
Dispose();
|
Dispose();
|
||||||
|
@ -138,13 +138,6 @@
|
|||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="tooltip_text" translatable="yes">The controller's type</property>
|
<property name="tooltip_text" translatable="yes">The controller's type</property>
|
||||||
<property name="active">0</property>
|
<property name="active">0</property>
|
||||||
<items>
|
|
||||||
<item id="Handheld" translatable="yes">Handheld</item>
|
|
||||||
<item id="ProController" translatable="yes">Pro Controller</item>
|
|
||||||
<item id="JoyconPair" translatable="yes">Paired Joycons</item>
|
|
||||||
<item id="JoyconLeft" translatable="yes">Left Joycon</item>
|
|
||||||
<item id="JoyconRight" translatable="yes">Right Joycon</item>
|
|
||||||
</items>
|
|
||||||
<signal name="changed" handler="Controller_Changed" swapped="no"/>
|
<signal name="changed" handler="Controller_Changed" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
|
@ -405,9 +405,9 @@ namespace Ryujinx.Ui
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
List<GamepadInput> gamepadInputs = new List<GamepadInput>();
|
List<GamepadInput> gamepadInputs = new List<GamepadInput>(NpadDevices.MaxControllers);
|
||||||
|
|
||||||
foreach (InputConfig inputConfig in ConfigurationState.Instance.Hid.InputConfig.Value.ToArray())
|
foreach (InputConfig inputConfig in ConfigurationState.Instance.Hid.InputConfig.Value)
|
||||||
{
|
{
|
||||||
ControllerKeys currentButton = 0;
|
ControllerKeys currentButton = 0;
|
||||||
JoystickPosition leftJoystick = new JoystickPosition();
|
JoystickPosition leftJoystick = new JoystickPosition();
|
||||||
@ -497,18 +497,21 @@ namespace Ryujinx.Ui
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_device.Hid.Npads.SetGamepadsInput(gamepadInputs.ToArray());
|
_device.Hid.Npads.Update(gamepadInputs);
|
||||||
|
|
||||||
// Hotkeys
|
if(IsFocused)
|
||||||
HotkeyButtons currentHotkeyButtons = KeyboardController.GetHotkeyButtons(OpenTK.Input.Keyboard.GetState());
|
|
||||||
|
|
||||||
if (currentHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync) &&
|
|
||||||
!_prevHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync))
|
|
||||||
{
|
{
|
||||||
_device.EnableDeviceVsync = !_device.EnableDeviceVsync;
|
// Hotkeys
|
||||||
}
|
HotkeyButtons currentHotkeyButtons = KeyboardController.GetHotkeyButtons(OpenTK.Input.Keyboard.GetState());
|
||||||
|
|
||||||
_prevHotkeyButtons = currentHotkeyButtons;
|
if (currentHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync) &&
|
||||||
|
!_prevHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync))
|
||||||
|
{
|
||||||
|
_device.EnableDeviceVsync = !_device.EnableDeviceVsync;
|
||||||
|
}
|
||||||
|
|
||||||
|
_prevHotkeyButtons = currentHotkeyButtons;
|
||||||
|
}
|
||||||
|
|
||||||
//Touchscreen
|
//Touchscreen
|
||||||
bool hasTouch = false;
|
bool hasTouch = false;
|
||||||
|
@ -16,6 +16,62 @@ namespace Ryujinx.Ui
|
|||||||
_parent = parent;
|
_parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool DisplayMessageDialog(ControllerAppletUiArgs args)
|
||||||
|
{
|
||||||
|
string playerCount = args.PlayerCountMin == args.PlayerCountMax
|
||||||
|
? $"exactly {args.PlayerCountMin}"
|
||||||
|
: $"{args.PlayerCountMin}-{args.PlayerCountMax}";
|
||||||
|
|
||||||
|
string message =
|
||||||
|
$"Application requests <b>{playerCount}</b> player(s) with:\n\n"
|
||||||
|
+ $"<tt><b>TYPES:</b> {args.SupportedStyles}</tt>\n\n"
|
||||||
|
+ $"<tt><b>PLAYERS:</b> {string.Join(", ", args.SupportedPlayers)}</tt>\n\n"
|
||||||
|
+ (args.IsDocked ? "Docked mode set. <tt>Handheld</tt> is also invalid.\n\n" : "")
|
||||||
|
+ "<i>Please reconfigure Input now and then press OK.</i>";
|
||||||
|
|
||||||
|
return DisplayMessageDialog("Controller Applet", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DisplayMessageDialog(string title, string message)
|
||||||
|
{
|
||||||
|
ManualResetEvent dialogCloseEvent = new ManualResetEvent(false);
|
||||||
|
bool okPressed = false;
|
||||||
|
|
||||||
|
Application.Invoke(delegate
|
||||||
|
{
|
||||||
|
MessageDialog msgDialog = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
msgDialog = new MessageDialog(_parent, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null)
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Text = message,
|
||||||
|
UseMarkup = true
|
||||||
|
};
|
||||||
|
|
||||||
|
msgDialog.SetDefaultSize(400, 0);
|
||||||
|
|
||||||
|
msgDialog.Response += (object o, ResponseArgs args) =>
|
||||||
|
{
|
||||||
|
if (args.ResponseId == ResponseType.Ok) okPressed = true;
|
||||||
|
dialogCloseEvent.Set();
|
||||||
|
msgDialog?.Dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
msgDialog.Show();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Error displaying Message Dialog: {e}");
|
||||||
|
dialogCloseEvent.Set();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogCloseEvent.WaitOne();
|
||||||
|
|
||||||
|
return okPressed;
|
||||||
|
}
|
||||||
|
|
||||||
public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
|
public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
|
||||||
{
|
{
|
||||||
ManualResetEvent dialogCloseEvent = new ManualResetEvent(false);
|
ManualResetEvent dialogCloseEvent = new ManualResetEvent(false);
|
||||||
|
@ -507,14 +507,6 @@ namespace Ryujinx.Ui
|
|||||||
_windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1);
|
_windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
device.Hid.Npads.AddControllers(ConfigurationState.Instance.Hid.InputConfig.Value.Select(inputConfig =>
|
|
||||||
new HLE.HOS.Services.Hid.ControllerConfig
|
|
||||||
{
|
|
||||||
Player = (PlayerIndex)inputConfig.PlayerIndex,
|
|
||||||
Type = (ControllerType)inputConfig.ControllerType
|
|
||||||
}
|
|
||||||
).ToArray());
|
|
||||||
|
|
||||||
_glWidget = new GlRenderer(_emulationContext, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
_glWidget = new GlRenderer(_emulationContext, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
||||||
|
|
||||||
Application.Invoke(delegate
|
Application.Invoke(delegate
|
||||||
|
Loading…
Reference in New Issue
Block a user