NpadReader.cs
1 using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; 2 using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; 3 4 namespace Ryujinx.HLE.UI.Input 5 { 6 /// <summary> 7 /// Class that converts Hid entries for the Npad into pressed / released events. 8 /// </summary> 9 class NpadReader 10 { 11 private readonly Switch _device; 12 private readonly NpadCommonState[] _lastStates; 13 14 public event NpadButtonHandler NpadButtonUpEvent; 15 public event NpadButtonHandler NpadButtonDownEvent; 16 17 public NpadReader(Switch device) 18 { 19 _device = device; 20 _lastStates = new NpadCommonState[_device.Hid.SharedMemory.Npads.Length]; 21 } 22 23 public NpadButton GetCurrentButtonsOfNpad(int npadIndex) 24 { 25 return _lastStates[npadIndex].Buttons; 26 } 27 28 public NpadButton GetCurrentButtonsOfAllNpads() 29 { 30 NpadButton buttons = 0; 31 32 foreach (var state in _lastStates) 33 { 34 buttons |= state.Buttons; 35 } 36 37 return buttons; 38 } 39 40 private static ref RingLifo<NpadCommonState> GetCommonStateLifo(ref NpadInternalState npad) 41 { 42 switch (npad.StyleSet) 43 { 44 case NpadStyleTag.FullKey: 45 return ref npad.FullKey; 46 case NpadStyleTag.Handheld: 47 return ref npad.Handheld; 48 case NpadStyleTag.JoyDual: 49 return ref npad.JoyDual; 50 case NpadStyleTag.JoyLeft: 51 return ref npad.JoyLeft; 52 case NpadStyleTag.JoyRight: 53 return ref npad.JoyRight; 54 case NpadStyleTag.Palma: 55 return ref npad.Palma; 56 default: 57 return ref npad.SystemExt; 58 } 59 } 60 61 public void Update(bool supressEvents = false) 62 { 63 ref var npads = ref _device.Hid.SharedMemory.Npads; 64 65 // Process each input individually. 66 for (int npadIndex = 0; npadIndex < npads.Length; npadIndex++) 67 { 68 UpdateNpad(npadIndex, supressEvents); 69 } 70 } 71 72 private void UpdateNpad(int npadIndex, bool supressEvents) 73 { 74 const int MaxEntries = 1024; 75 76 ref var npadState = ref _device.Hid.SharedMemory.Npads[npadIndex]; 77 ref var lastEntry = ref _lastStates[npadIndex]; 78 79 var fullKeyEntries = GetCommonStateLifo(ref npadState.InternalState).ReadEntries(MaxEntries); 80 81 int firstEntryNum; 82 83 // Scan the LIFO for the first entry that is newer that what's already processed. 84 for (firstEntryNum = fullKeyEntries.Length - 1; 85 firstEntryNum >= 0 && fullKeyEntries[firstEntryNum].Object.SamplingNumber <= lastEntry.SamplingNumber; 86 firstEntryNum--) 87 { 88 } 89 90 if (firstEntryNum == -1) 91 { 92 return; 93 } 94 95 for (; firstEntryNum >= 0; firstEntryNum--) 96 { 97 var entry = fullKeyEntries[firstEntryNum]; 98 99 // The interval of valid entries should be contiguous. 100 if (entry.SamplingNumber < lastEntry.SamplingNumber) 101 { 102 break; 103 } 104 105 if (!supressEvents) 106 { 107 ProcessNpadButtons(npadIndex, entry.Object.Buttons); 108 } 109 110 lastEntry = entry.Object; 111 } 112 } 113 114 private void ProcessNpadButtons(int npadIndex, NpadButton buttons) 115 { 116 NpadButton lastButtons = _lastStates[npadIndex].Buttons; 117 118 for (ulong buttonMask = 1; buttonMask != 0; buttonMask <<= 1) 119 { 120 NpadButton currentButton = (NpadButton)buttonMask & buttons; 121 NpadButton lastButton = (NpadButton)buttonMask & lastButtons; 122 123 if (lastButton != 0) 124 { 125 if (currentButton == 0) 126 { 127 NpadButtonUpEvent?.Invoke(npadIndex, lastButton); 128 } 129 } 130 else 131 { 132 if (currentButton != 0) 133 { 134 NpadButtonDownEvent?.Invoke(npadIndex, currentButton); 135 } 136 } 137 } 138 } 139 } 140 }