diff options
| author | mageven <62494521+mageven@users.noreply.github.com> | 2020-04-03 05:40:02 +0530 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-04-03 11:10:02 +1100 |
| commit | 2365ddfc363e76ac1ac9d2e32ef9b36b85463431 (patch) | |
| tree | 408bdb4a35e4f625a69ddbd65f22e9f056f7ebec /Ryujinx.HLE/HOS/Services/Hid/HidDevices | |
| parent | 5b5239ab5b452f991d9fc4f8ad1f9a2880b8bad1 (diff) | |
HID SharedMem Rework (#1003)
* Delete old HLE.Input
* Add new HLE Input.
git shows Hid.cs as modified because of the same name. It is new.
* Change HID Service
* Change Ryujinx UI to reflect new Input
* Add basic ControllerApplet
* Add DebugPad
Should fix Kirby Star Allies
* Address Ac_K's comments
* Moved all of HLE.Input to Services.Hid
* Separated all structs and enums each to a file
* Removed vars
* Made some naming changes to align with switchbrew
* Added official joycon colors
As an aside, fixed a mistake in touchscreen headers and added checks to
important SharedMem structs at init time.
* Further address Ac_K's comments
* Addressed gdkchan's and some more Ac_K's comments
* Address AcK's review comments
* Address AcK's second review comments
* Replace missed Marshal.SizeOf and address gdkchan's comments
Diffstat (limited to 'Ryujinx.HLE/HOS/Services/Hid/HidDevices')
11 files changed, 545 insertions, 0 deletions
diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs new file mode 100644 index 00000000..59d6dfa3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs @@ -0,0 +1,29 @@ +using static Ryujinx.HLE.HOS.Services.Hid.Hid; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public abstract class BaseDevice + { + protected readonly Switch _device; + public bool Active; + + public BaseDevice(Switch device, bool active) + { + _device = device; + Active = active; + } + + internal static int UpdateEntriesHeader(ref CommonEntriesHeader header, out int previousEntry) + { + header.NumEntries = SharedMemEntryCount; + header.MaxEntryIndex = SharedMemEntryCount - 1; + + previousEntry = (int)header.LatestEntry; + header.LatestEntry = (header.LatestEntry + 1) % SharedMemEntryCount; + + header.TimestampTicks = GetTimestampTicks(); + + return (int)header.LatestEntry; // EntryCount shouldn't overflow int + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs new file mode 100644 index 00000000..77e9205f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class DebugPadDevice : BaseDevice + { + public DebugPadDevice(Switch device, bool active) : base(device, active) { } + + public void Update() + { + ref ShMemDebugPad debugPad = ref _device.Hid.SharedMemory.DebugPad; + + int currentIndex = UpdateEntriesHeader(ref debugPad.Header, out int previousIndex); + + if (!Active) + { + return; + } + + ref DebugPadEntry currentEntry = ref debugPad.Entries[currentIndex]; + DebugPadEntry previousEntry = debugPad.Entries[previousIndex]; + + currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs new file mode 100644 index 00000000..e8ed6a3e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs @@ -0,0 +1,32 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class KeyboardDevice : BaseDevice + { + public KeyboardDevice(Switch device, bool active) : base(device, active) { } + + public unsafe void Update(KeyboardInput keyState) + { + ref ShMemKeyboard keyboard = ref _device.Hid.SharedMemory.Keyboard; + + int currentIndex = UpdateEntriesHeader(ref keyboard.Header, out int previousIndex); + + if (!Active) + { + return; + } + + ref KeyboardState currentEntry = ref keyboard.Entries[currentIndex]; + KeyboardState previousEntry = keyboard.Entries[previousIndex]; + + currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1; + currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1; + + for (int i = 0; i < 8; ++i) + { + currentEntry.Keys[i] = (uint)keyState.Keys[i]; + } + + currentEntry.Modifier = (ulong)keyState.Modifier; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs new file mode 100644 index 00000000..ee58a563 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class MouseDevice : BaseDevice + { + public MouseDevice(Switch device, bool active) : base(device, active) { } + + public void Update(int mouseX, int mouseY, int buttons = 0, int scrollX = 0, int scrollY = 0) + { + ref ShMemMouse mouse = ref _device.Hid.SharedMemory.Mouse; + + int currentIndex = UpdateEntriesHeader(ref mouse.Header, out int previousIndex); + + if (!Active) + { + return; + } + + ref MouseState currentEntry = ref mouse.Entries[currentIndex]; + MouseState previousEntry = mouse.Entries[previousIndex]; + + currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1; + currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1; + + currentEntry.Buttons = (ulong)buttons; + + currentEntry.Position = new MousePosition + { + X = mouseX, + Y = mouseY, + VelocityX = mouseX - previousEntry.Position.X, + VelocityY = mouseY - previousEntry.Position.Y, + ScrollVelocityX = scrollX, + ScrollVelocityY = scrollY + }; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs new file mode 100644 index 00000000..ff330312 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs @@ -0,0 +1,332 @@ +using System; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class NpadDevices : BaseDevice + { + internal NpadJoyHoldType JoyHold = NpadJoyHoldType.Vertical; + internal bool SixAxisActive = false; // TODO: link to hidserver when implemented + + enum FilterState + { + Unconfigured = 0, + Configured = 1, + Accepted = 2 + } + + 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 { return _supportedStyleSets; } + set + { + if (_supportedStyleSets != value) // Deal with spamming + { + _supportedStyleSets = value; + MatchControllers(); + } + } + } + + public PlayerIndex PrimaryController { get; set; } = PlayerIndex.Unknown; + + KEvent[] _styleSetUpdateEvents; + + static readonly Array3<BatteryCharge> _fullBattery; + + public NpadDevices(Switch device, bool active = true) : base(device, active) + { + _configuredNpads = new NpadConfig[_maxControllers]; + + _styleSetUpdateEvents = new KEvent[_maxControllers]; + + for (int i = 0; i < _styleSetUpdateEvents.Length; ++i) + { + _styleSetUpdateEvents[i] = new KEvent(_device.System); + } + + _fullBattery[0] = _fullBattery[1] = _fullBattery[2] = BatteryCharge.Percent100; + } + + public void AddControllers(params ControllerConfig[] configs) + { + for (int i = 0; i < configs.Length; ++i) + { + PlayerIndex player = configs[i].Player; + ControllerType controllerType = configs[i].Type; + + if (player > PlayerIndex.Handheld) + { + throw new ArgumentOutOfRangeException("Player must be Player1-8 or Handheld"); + } + + if (controllerType == ControllerType.Handheld) + { + player = PlayerIndex.Handheld; + } + + _configuredNpads[(int)player] = new NpadConfig { ConfiguredType = controllerType, State = FilterState.Configured }; + } + + MatchControllers(); + } + + 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.PrintWarning(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.PrintWarning(LogClass.Hid, $"No matching controllers found. Reassigning input as ControllerType {controllerType}..."); + + InitController(controllerType == ControllerType.Handheld ? PlayerIndex.Handheld : PlayerIndex.Player1, controllerType); + + return; + } + } + + Logger.PrintError(LogClass.Hid, "Couldn't find any appropriate controller."); + } + } + + internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player) + { + return ref _styleSetUpdateEvents[(int)player]; + } + + void InitController(PlayerIndex player, ControllerType type) + { + if (type == ControllerType.Handheld) + { + player = PlayerIndex.Handheld; + } + + ref ShMemNpad controller = ref _device.Hid.SharedMemory.Npads[(int)player]; + + controller = new ShMemNpad(); // Zero it + + // TODO: Allow customizing colors at config + NpadStateHeader defaultHeader = new NpadStateHeader + { + IsHalf = false, + SingleColorBody = NpadColor.BodyGray, + SingleColorButtons = NpadColor.ButtonGray, + LeftColorBody = NpadColor.BodyNeonBlue, + LeftColorButtons = NpadColor.ButtonGray, + RightColorBody = NpadColor.BodyNeonRed, + RightColorButtons = NpadColor.ButtonGray + }; + + controller.SystemProperties = NpadSystemProperties.PowerInfo0Connected | + NpadSystemProperties.PowerInfo1Connected | + NpadSystemProperties.PowerInfo2Connected; + + controller.BatteryState = _fullBattery; + + switch (type) + { + case ControllerType.ProController: + defaultHeader.Type = ControllerType.ProController; + controller.DeviceType = DeviceType.FullKey; + controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented | + NpadSystemProperties.PlusButtonCapability | + NpadSystemProperties.MinusButtonCapability; + break; + case ControllerType.Handheld: + defaultHeader.Type = ControllerType.Handheld; + controller.DeviceType = DeviceType.HandheldLeft | + DeviceType.HandheldRight; + controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented | + NpadSystemProperties.PlusButtonCapability | + NpadSystemProperties.MinusButtonCapability; + break; + case ControllerType.JoyconPair: + defaultHeader.Type = ControllerType.JoyconPair; + controller.DeviceType = DeviceType.JoyLeft | + DeviceType.JoyRight; + controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented | + NpadSystemProperties.PlusButtonCapability | + NpadSystemProperties.MinusButtonCapability; + break; + case ControllerType.JoyconLeft: + defaultHeader.Type = ControllerType.JoyconLeft; + defaultHeader.IsHalf = true; + controller.DeviceType = DeviceType.JoyLeft; + controller.SystemProperties |= NpadSystemProperties.SlSrButtonOriented | + NpadSystemProperties.MinusButtonCapability; + break; + case ControllerType.JoyconRight: + defaultHeader.Type = ControllerType.JoyconRight; + defaultHeader.IsHalf = true; + controller.DeviceType = DeviceType.JoyRight; + controller.SystemProperties |= NpadSystemProperties.SlSrButtonOriented | + NpadSystemProperties.PlusButtonCapability; + break; + case ControllerType.Pokeball: + defaultHeader.Type = ControllerType.Pokeball; + controller.DeviceType = DeviceType.Palma; + break; + } + + controller.Header = defaultHeader; + + if (PrimaryController == PlayerIndex.Unknown) + { + PrimaryController = player; + } + + _configuredNpads[(int)player].State = FilterState.Accepted; + + _styleSetUpdateEvents[(int)player].ReadableEvent.Signal(); + + Logger.PrintInfo(LogClass.Hid, $"Connected ControllerType {type} to PlayerIndex {player}"); + } + + static NpadLayoutsIndex ControllerTypeToLayout(ControllerType controllerType) + => controllerType switch + { + ControllerType.ProController => NpadLayoutsIndex.ProController, + ControllerType.Handheld => NpadLayoutsIndex.Handheld, + ControllerType.JoyconPair => NpadLayoutsIndex.JoyDual, + ControllerType.JoyconLeft => NpadLayoutsIndex.JoyLeft, + ControllerType.JoyconRight => NpadLayoutsIndex.JoyRight, + ControllerType.Pokeball => NpadLayoutsIndex.Pokeball, + _ => NpadLayoutsIndex.SystemExternal + }; + + public void SetGamepadsInput(params GamepadInput[] states) + { + UpdateAllEntries(); + + for (int i = 0; i < states.Length; ++i) + { + SetGamepadState(states[i].PlayerId, states[i].Buttons, states[i].LStick, states[i].RStick); + } + } + + void SetGamepadState(PlayerIndex player, ControllerKeys buttons, + JoystickPosition leftJoystick, JoystickPosition rightJoystick) + { + if (player == PlayerIndex.Auto) + { + player = PrimaryController; + } + + if (player == PlayerIndex.Unknown) + { + return; + } + + if (_configuredNpads[(int)player].State != FilterState.Accepted) + { + return; + } + + ref ShMemNpad currentNpad = ref _device.Hid.SharedMemory.Npads[(int)player]; + ref NpadLayout currentLayout = ref currentNpad.Layouts[(int)ControllerTypeToLayout(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; + + // Mirror data to Default layout just in case + ref NpadLayout mainLayout = ref currentNpad.Layouts[(int)NpadLayoutsIndex.SystemExternal]; + mainLayout.Entries[(int)mainLayout.Header.LatestEntry] = currentEntry; + } + + void UpdateAllEntries() + { + ref Array10<ShMemNpad> controllers = ref _device.Hid.SharedMemory.Npads; + for (int i = 0; i < controllers.Length; ++i) + { + ref Array7<NpadLayout> layouts = ref controllers[i].Layouts; + for (int l = 0; l < layouts.Length; ++l) + { + ref NpadLayout currentLayout = ref layouts[l]; + int currentIndex = UpdateEntriesHeader(ref currentLayout.Header, out int previousIndex); + + ref NpadState currentEntry = ref currentLayout.Entries[currentIndex]; + NpadState previousEntry = currentLayout.Entries[previousIndex]; + + currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1; + currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1; + + if (controllers[i].Header.Type == ControllerType.None) + { + continue; + } + + currentEntry.ConnectionState = NpadConnectionState.ControllerStateConnected; + + switch (controllers[i].Header.Type) + { + case ControllerType.Handheld: + case ControllerType.ProController: + currentEntry.ConnectionState |= NpadConnectionState.ControllerStateWired; + break; + case ControllerType.JoyconPair: + currentEntry.ConnectionState |= NpadConnectionState.JoyLeftConnected | + NpadConnectionState.JoyRightConnected; + break; + case ControllerType.JoyconLeft: + currentEntry.ConnectionState |= NpadConnectionState.JoyLeftConnected; + break; + case ControllerType.JoyconRight: + currentEntry.ConnectionState |= NpadConnectionState.JoyRightConnected; + break; + } + } + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs new file mode 100644 index 00000000..10c34453 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs @@ -0,0 +1,46 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class TouchDevice : BaseDevice + { + public TouchDevice(Switch device, bool active) : base(device, active) { } + + public void Update(params TouchPoint[] points) + { + ref ShMemTouchScreen touchscreen = ref _device.Hid.SharedMemory.TouchScreen; + + int currentIndex = UpdateEntriesHeader(ref touchscreen.Header, out int previousIndex); + + if (!Active) + { + return; + } + + ref TouchScreenState currentEntry = ref touchscreen.Entries[currentIndex]; + TouchScreenState previousEntry = touchscreen.Entries[previousIndex]; + + currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1; + currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1; + + currentEntry.NumTouches = (ulong)points.Length; + + int pointsLength = Math.Min(points.Length, currentEntry.Touches.Length); + + for (int i = 0; i < pointsLength; ++i) + { + TouchPoint pi = points[i]; + currentEntry.Touches[i] = new TouchScreenStateData + { + SampleTimestamp = currentEntry.SampleTimestamp, + X = pi.X, + Y = pi.Y, + TouchIndex = (uint)i, + DiameterX = pi.DiameterX, + DiameterY = pi.DiameterY, + Angle = pi.Angle + }; + } + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs new file mode 100644 index 00000000..e59ba312 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct ControllerConfig + { + public PlayerIndex Player; + public ControllerType Type; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs new file mode 100644 index 00000000..2488057e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct GamepadInput + { + public PlayerIndex PlayerId; + public ControllerKeys Buttons; + public JoystickPosition LStick; + public JoystickPosition RStick; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs new file mode 100644 index 00000000..6df477d6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct JoystickPosition + { + public int Dx; + public int Dy; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs new file mode 100644 index 00000000..26681270 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct KeyboardInput + { + public int Modifier; + public int[] Keys; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs new file mode 100644 index 00000000..d1172dd0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct TouchPoint + { + public uint X; + public uint Y; + public uint DiameterX; + public uint DiameterY; + public uint Angle; + } +}
\ No newline at end of file |
