diff options
| author | mageven <62494521+mageven@users.noreply.github.com> | 2020-08-24 02:24:11 +0530 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-08-23 22:54:11 +0200 |
| commit | 27179d02180396750cc2ea08ac1e2cc5a91a8763 (patch) | |
| tree | d9fb3e03f4553b0aad2c9dec7e74e38f864e199c /Ryujinx.HLE/HOS/Services/Hid/HidDevices | |
| parent | 01ff648bdf46b85409d1a834987a69150de7adb9 (diff) | |
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.
Diffstat (limited to 'Ryujinx.HLE/HOS/Services/Hid/HidDevices')
| -rw-r--r-- | Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs | 261 |
1 files changed, 147 insertions, 114 deletions
diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs index 1c882887..334af975 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs @@ -1,74 +1,118 @@ -using System; +using Ryujinx.Common; using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; using Ryujinx.HLE.HOS.Kernel.Threading; +using System; +using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Hid { public class NpadDevices : BaseDevice { - internal NpadJoyHoldType JoyHold = NpadJoyHoldType.Vertical; + private const BatteryCharge DefaultBatteryCharge = BatteryCharge.Percent100; + + private const int NoMatchNotifyFrequencyMs = 2000; + private int _activeCount; + private long _lastNotifyTimestamp; + + public const int MaxControllers = 9; // Players 1-8 and Handheld + private ControllerType[] _configuredTypes; + private KEvent[] _styleSetUpdateEvents; + private bool[] _supportedPlayers; + + internal NpadJoyHoldType JoyHold { get; set; } internal bool SixAxisActive = false; // TODO: link to hidserver when implemented + internal ControllerType SupportedStyleSets { get; set; } - private enum FilterState + public NpadDevices(Switch device, bool active = true) : base(device, active) { - Unconfigured = 0, - Configured = 1, - Accepted = 2 + _configuredTypes = new ControllerType[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) + { + _styleSetUpdateEvents[i] = new KEvent(_device.System.KernelContext); + } + + _activeCount = 0; + + JoyHold = NpadJoyHoldType.Vertical; } - private struct NpadConfig + internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player) { - public ControllerType ConfiguredType; - public FilterState State; + return ref _styleSetUpdateEvents[(int)player]; } - private const int _maxControllers = 9; // Players1-8 and Handheld - private NpadConfig[] _configuredNpads; + internal void ClearSupportedPlayers() + { + _supportedPlayers.AsSpan().Clear(); + } - private ControllerType _supportedStyleSets = ControllerType.ProController | - ControllerType.JoyconPair | - ControllerType.JoyconLeft | - ControllerType.JoyconRight | - ControllerType.Handheld; + internal void SetSupportedPlayer(PlayerIndex player, bool supported = true) + { + _supportedPlayers[(int)player] = supported; + } - public ControllerType SupportedStyleSets + internal IEnumerable<PlayerIndex> GetSupportedPlayers() { - get => _supportedStyleSets; - set + for (int i = 0; i < _supportedPlayers.Length; ++i) { - if (_supportedStyleSets != value) // Deal with spamming + if (_supportedPlayers[i]) { - _supportedStyleSets = value; - MatchControllers(); + yield return (PlayerIndex)i; } } } - public PlayerIndex PrimaryController { get; set; } = PlayerIndex.Unknown; + public bool Validate(int playerMin, int playerMax, ControllerType acceptedTypes, out int configuredCount, out PlayerIndex primaryIndex) + { + primaryIndex = PlayerIndex.Unknown; + configuredCount = 0; - private KEvent[] _styleSetUpdateEvents; + for (int i = 0; i < MaxControllers; ++i) + { + ControllerType npad = _configuredTypes[i]; - private static readonly Array3<BatteryCharge> _fullBattery; + if (npad == ControllerType.Handheld && _device.System.State.DockedMode) + { + continue; + } - public NpadDevices(Switch device, bool active = true) : base(device, active) - { - _configuredNpads = new NpadConfig[_maxControllers]; + ControllerType currentType = _device.Hid.SharedMemory.Npads[i].Header.Type; - _styleSetUpdateEvents = new KEvent[_maxControllers]; + if (currentType != ControllerType.None && (npad & acceptedTypes) != 0 && _supportedPlayers[i]) + { + configuredCount++; + if (primaryIndex == PlayerIndex.Unknown) + { + primaryIndex = (PlayerIndex)i; + } + } + } - for (int i = 0; i < _styleSetUpdateEvents.Length; ++i) + if (configuredCount < playerMin || configuredCount > playerMax || primaryIndex == PlayerIndex.Unknown) { - _styleSetUpdateEvents[i] = new KEvent(_device.System.KernelContext); + return false; } - _fullBattery[0] = _fullBattery[1] = _fullBattery[2] = BatteryCharge.Percent100; + return true; } - public void AddControllers(params ControllerConfig[] configs) + public void Configure(params ControllerConfig[] configs) { + _configuredTypes = new ControllerType[MaxControllers]; + for (int i = 0; i < configs.Length; ++i) { - PlayerIndex player = configs[i].Player; + PlayerIndex player = configs[i].Player; ControllerType controllerType = configs[i].Type; if (player > PlayerIndex.Handheld) @@ -81,77 +125,87 @@ namespace Ryujinx.HLE.HOS.Services.Hid 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() + public void Update(IList<GamepadInput> states) { - PrimaryController = PlayerIndex.Unknown; + Remap(); + + UpdateAllEntries(); + + // Update configured inputs + for (int i = 0; i < states.Count; ++i) + { + UpdateInput(states[i]); + } + } - for (int i = 0; i < _configuredNpads.Length; ++i) + private void Remap() + { + // Remap/Init if necessary + for (int i = 0; i < MaxControllers; ++i) { - ref NpadConfig config = ref _configuredNpads[i]; + ControllerType config = _configuredTypes[i]; - if (config.State == FilterState.Unconfigured) + // Remove Handheld config when Docked + if (config == ControllerType.Handheld && _device.System.State.DockedMode) { - continue; // Ignore unconfigured + config = ControllerType.None; } - if ((config.ConfiguredType & _supportedStyleSets) == 0) + // Auto-remap ProController and JoyconPair + if (config == ControllerType.JoyconPair && (SupportedStyleSets & ControllerType.JoyconPair) == 0 && (SupportedStyleSets & ControllerType.ProController) != 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 + config = ControllerType.ProController; + } + else if (config == ControllerType.ProController && (SupportedStyleSets & ControllerType.ProController) == 0 && (SupportedStyleSets & ControllerType.JoyconPair) != 0) + { + config = ControllerType.JoyconPair; + } - continue; + // Check StyleSet and PlayerSet + if ((config & SupportedStyleSets) == 0 || !_supportedPlayers[i]) + { + config = ControllerType.None; } - InitController((PlayerIndex)i, config.ConfiguredType); + SetupNpad((PlayerIndex)i, config); } - // Couldn't find any matching configuration. Reassign to something that works. - if (PrimaryController == PlayerIndex.Unknown) + if (_activeCount == 0 && PerformanceCounter.ElapsedMilliseconds > _lastNotifyTimestamp + NoMatchNotifyFrequencyMs) { - 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."); + Logger.Warning?.Print(LogClass.Hid, $"No matching controllers found. Application requests '{SupportedStyleSets}' on '{string.Join(", ", GetSupportedPlayers())}'"); + _lastNotifyTimestamp = PerformanceCounter.ElapsedMilliseconds; } } - internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player) + private void SetupNpad(PlayerIndex player, ControllerType type) { - return ref _styleSetUpdateEvents[(int)player]; - } + ref ShMemNpad controller = ref _device.Hid.SharedMemory.Npads[(int)player]; - private void InitController(PlayerIndex player, ControllerType type) - { - if (type == ControllerType.Handheld) + ControllerType oldType = controller.Header.Type; + + if (oldType == type) { - player = PlayerIndex.Handheld; + return; // Already configured } - ref ShMemNpad controller = ref _device.Hid.SharedMemory.Npads[(int)player]; - 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 NpadStateHeader defaultHeader = new NpadStateHeader { @@ -168,7 +222,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid NpadSystemProperties.PowerInfo1Connected | NpadSystemProperties.PowerInfo2Connected; - controller.BatteryState = _fullBattery; + controller.BatteryState.ToSpan().Fill(DefaultBatteryCharge); switch (type) { @@ -217,19 +271,13 @@ namespace Ryujinx.HLE.HOS.Services.Hid controller.Header = defaultHeader; - if (PrimaryController == PlayerIndex.Unknown) - { - PrimaryController = player; - } - - _configuredNpads[(int)player].State = FilterState.Accepted; - _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.ProController => NpadLayoutsIndex.ProController, @@ -241,43 +289,28 @@ namespace Ryujinx.HLE.HOS.Services.Hid _ => NpadLayoutsIndex.SystemExternal }; - public void SetGamepadsInput(params GamepadInput[] states) + private void UpdateInput(GamepadInput state) { - UpdateAllEntries(); - - 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) + if (state.PlayerId == PlayerIndex.Unknown) { 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; } - ref ShMemNpad currentNpad = ref _device.Hid.SharedMemory.Npads[(int)player]; - ref NpadLayout currentLayout = ref currentNpad.Layouts[(int)ControllerTypeToLayout(currentNpad.Header.Type)]; + ref NpadLayout currentLayout = ref currentNpad.Layouts[(int)ControllerTypeToNpadLayout(currentNpad.Header.Type)]; ref NpadState currentEntry = ref currentLayout.Entries[(int)currentLayout.Header.LatestEntry]; - currentEntry.Buttons = buttons; - currentEntry.LStickX = leftJoystick.Dx; - currentEntry.LStickY = leftJoystick.Dy; - currentEntry.RStickX = rightJoystick.Dx; - currentEntry.RStickY = rightJoystick.Dy; + currentEntry.Buttons = state.Buttons; + currentEntry.LStickX = state.LStick.Dx; + currentEntry.LStickY = state.LStick.Dy; + currentEntry.RStickX = state.RStick.Dx; + currentEntry.RStickY = state.RStick.Dy; // Mirror data to Default layout just in case ref NpadLayout mainLayout = ref currentNpad.Layouts[(int)NpadLayoutsIndex.SystemExternal]; |
