NpadController.cs
1 using Ryujinx.Common; 2 using Ryujinx.Common.Configuration.Hid; 3 using Ryujinx.Common.Configuration.Hid.Controller; 4 using Ryujinx.Common.Configuration.Hid.Controller.Motion; 5 using Ryujinx.Common.Logging; 6 using Ryujinx.HLE.HOS.Services.Hid; 7 using System; 8 using System.Collections.Concurrent; 9 using System.Numerics; 10 using System.Runtime.CompilerServices; 11 using CemuHookClient = Ryujinx.Input.Motion.CemuHook.Client; 12 using ConfigControllerType = Ryujinx.Common.Configuration.Hid.ControllerType; 13 14 namespace Ryujinx.Input.HLE 15 { 16 public class NpadController : IDisposable 17 { 18 private class HLEButtonMappingEntry 19 { 20 public readonly GamepadButtonInputId DriverInputId; 21 public readonly ControllerKeys HLEInput; 22 23 public HLEButtonMappingEntry(GamepadButtonInputId driverInputId, ControllerKeys hleInput) 24 { 25 DriverInputId = driverInputId; 26 HLEInput = hleInput; 27 } 28 } 29 30 private static readonly HLEButtonMappingEntry[] _hleButtonMapping = { 31 new(GamepadButtonInputId.A, ControllerKeys.A), 32 new(GamepadButtonInputId.B, ControllerKeys.B), 33 new(GamepadButtonInputId.X, ControllerKeys.X), 34 new(GamepadButtonInputId.Y, ControllerKeys.Y), 35 new(GamepadButtonInputId.LeftStick, ControllerKeys.LStick), 36 new(GamepadButtonInputId.RightStick, ControllerKeys.RStick), 37 new(GamepadButtonInputId.LeftShoulder, ControllerKeys.L), 38 new(GamepadButtonInputId.RightShoulder, ControllerKeys.R), 39 new(GamepadButtonInputId.LeftTrigger, ControllerKeys.Zl), 40 new(GamepadButtonInputId.RightTrigger, ControllerKeys.Zr), 41 new(GamepadButtonInputId.DpadUp, ControllerKeys.DpadUp), 42 new(GamepadButtonInputId.DpadDown, ControllerKeys.DpadDown), 43 new(GamepadButtonInputId.DpadLeft, ControllerKeys.DpadLeft), 44 new(GamepadButtonInputId.DpadRight, ControllerKeys.DpadRight), 45 new(GamepadButtonInputId.Minus, ControllerKeys.Minus), 46 new(GamepadButtonInputId.Plus, ControllerKeys.Plus), 47 48 new(GamepadButtonInputId.SingleLeftTrigger0, ControllerKeys.SlLeft), 49 new(GamepadButtonInputId.SingleRightTrigger0, ControllerKeys.SrLeft), 50 new(GamepadButtonInputId.SingleLeftTrigger1, ControllerKeys.SlRight), 51 new(GamepadButtonInputId.SingleRightTrigger1, ControllerKeys.SrRight), 52 }; 53 54 private class HLEKeyboardMappingEntry 55 { 56 public readonly Key TargetKey; 57 public readonly byte Target; 58 59 public HLEKeyboardMappingEntry(Key targetKey, byte target) 60 { 61 TargetKey = targetKey; 62 Target = target; 63 } 64 } 65 66 private static readonly HLEKeyboardMappingEntry[] _keyMapping = { 67 new(Key.A, 0x4), 68 new(Key.B, 0x5), 69 new(Key.C, 0x6), 70 new(Key.D, 0x7), 71 new(Key.E, 0x8), 72 new(Key.F, 0x9), 73 new(Key.G, 0xA), 74 new(Key.H, 0xB), 75 new(Key.I, 0xC), 76 new(Key.J, 0xD), 77 new(Key.K, 0xE), 78 new(Key.L, 0xF), 79 new(Key.M, 0x10), 80 new(Key.N, 0x11), 81 new(Key.O, 0x12), 82 new(Key.P, 0x13), 83 new(Key.Q, 0x14), 84 new(Key.R, 0x15), 85 new(Key.S, 0x16), 86 new(Key.T, 0x17), 87 new(Key.U, 0x18), 88 new(Key.V, 0x19), 89 new(Key.W, 0x1A), 90 new(Key.X, 0x1B), 91 new(Key.Y, 0x1C), 92 new(Key.Z, 0x1D), 93 94 new(Key.Number1, 0x1E), 95 new(Key.Number2, 0x1F), 96 new(Key.Number3, 0x20), 97 new(Key.Number4, 0x21), 98 new(Key.Number5, 0x22), 99 new(Key.Number6, 0x23), 100 new(Key.Number7, 0x24), 101 new(Key.Number8, 0x25), 102 new(Key.Number9, 0x26), 103 new(Key.Number0, 0x27), 104 105 new(Key.Enter, 0x28), 106 new(Key.Escape, 0x29), 107 new(Key.BackSpace, 0x2A), 108 new(Key.Tab, 0x2B), 109 new(Key.Space, 0x2C), 110 new(Key.Minus, 0x2D), 111 new(Key.Plus, 0x2E), 112 new(Key.BracketLeft, 0x2F), 113 new(Key.BracketRight, 0x30), 114 new(Key.BackSlash, 0x31), 115 new(Key.Tilde, 0x32), 116 new(Key.Semicolon, 0x33), 117 new(Key.Quote, 0x34), 118 new(Key.Grave, 0x35), 119 new(Key.Comma, 0x36), 120 new(Key.Period, 0x37), 121 new(Key.Slash, 0x38), 122 new(Key.CapsLock, 0x39), 123 124 new(Key.F1, 0x3a), 125 new(Key.F2, 0x3b), 126 new(Key.F3, 0x3c), 127 new(Key.F4, 0x3d), 128 new(Key.F5, 0x3e), 129 new(Key.F6, 0x3f), 130 new(Key.F7, 0x40), 131 new(Key.F8, 0x41), 132 new(Key.F9, 0x42), 133 new(Key.F10, 0x43), 134 new(Key.F11, 0x44), 135 new(Key.F12, 0x45), 136 137 new(Key.PrintScreen, 0x46), 138 new(Key.ScrollLock, 0x47), 139 new(Key.Pause, 0x48), 140 new(Key.Insert, 0x49), 141 new(Key.Home, 0x4A), 142 new(Key.PageUp, 0x4B), 143 new(Key.Delete, 0x4C), 144 new(Key.End, 0x4D), 145 new(Key.PageDown, 0x4E), 146 new(Key.Right, 0x4F), 147 new(Key.Left, 0x50), 148 new(Key.Down, 0x51), 149 new(Key.Up, 0x52), 150 151 new(Key.NumLock, 0x53), 152 new(Key.KeypadDivide, 0x54), 153 new(Key.KeypadMultiply, 0x55), 154 new(Key.KeypadSubtract, 0x56), 155 new(Key.KeypadAdd, 0x57), 156 new(Key.KeypadEnter, 0x58), 157 new(Key.Keypad1, 0x59), 158 new(Key.Keypad2, 0x5A), 159 new(Key.Keypad3, 0x5B), 160 new(Key.Keypad4, 0x5C), 161 new(Key.Keypad5, 0x5D), 162 new(Key.Keypad6, 0x5E), 163 new(Key.Keypad7, 0x5F), 164 new(Key.Keypad8, 0x60), 165 new(Key.Keypad9, 0x61), 166 new(Key.Keypad0, 0x62), 167 new(Key.KeypadDecimal, 0x63), 168 169 new(Key.F13, 0x68), 170 new(Key.F14, 0x69), 171 new(Key.F15, 0x6A), 172 new(Key.F16, 0x6B), 173 new(Key.F17, 0x6C), 174 new(Key.F18, 0x6D), 175 new(Key.F19, 0x6E), 176 new(Key.F20, 0x6F), 177 new(Key.F21, 0x70), 178 new(Key.F22, 0x71), 179 new(Key.F23, 0x72), 180 new(Key.F24, 0x73), 181 182 new(Key.ControlLeft, 0xE0), 183 new(Key.ShiftLeft, 0xE1), 184 new(Key.AltLeft, 0xE2), 185 new(Key.WinLeft, 0xE3), 186 new(Key.ControlRight, 0xE4), 187 new(Key.ShiftRight, 0xE5), 188 new(Key.AltRight, 0xE6), 189 new(Key.WinRight, 0xE7), 190 }; 191 192 private static readonly HLEKeyboardMappingEntry[] _keyModifierMapping = { 193 new(Key.ControlLeft, 0), 194 new(Key.ShiftLeft, 1), 195 new(Key.AltLeft, 2), 196 new(Key.WinLeft, 3), 197 new(Key.ControlRight, 4), 198 new(Key.ShiftRight, 5), 199 new(Key.AltRight, 6), 200 new(Key.WinRight, 7), 201 new(Key.CapsLock, 8), 202 new(Key.ScrollLock, 9), 203 new(Key.NumLock, 10), 204 }; 205 206 private MotionInput _leftMotionInput; 207 private MotionInput _rightMotionInput; 208 209 private IGamepad _gamepad; 210 private InputConfig _config; 211 212 public IGamepadDriver GamepadDriver { get; private set; } 213 public GamepadStateSnapshot State { get; private set; } 214 215 public string Id { get; private set; } 216 217 private readonly CemuHookClient _cemuHookClient; 218 219 public NpadController(CemuHookClient cemuHookClient) 220 { 221 State = default; 222 Id = null; 223 _cemuHookClient = cemuHookClient; 224 } 225 226 public bool UpdateDriverConfiguration(IGamepadDriver gamepadDriver, InputConfig config) 227 { 228 GamepadDriver = gamepadDriver; 229 230 _gamepad?.Dispose(); 231 232 Id = config.Id; 233 _gamepad = GamepadDriver.GetGamepad(Id); 234 235 UpdateUserConfiguration(config); 236 237 return _gamepad != null; 238 } 239 240 public void UpdateUserConfiguration(InputConfig config) 241 { 242 if (config is StandardControllerInputConfig controllerConfig) 243 { 244 bool needsMotionInputUpdate = _config is not StandardControllerInputConfig oldControllerConfig || 245 ((oldControllerConfig.Motion.EnableMotion != controllerConfig.Motion.EnableMotion) && 246 (oldControllerConfig.Motion.MotionBackend != controllerConfig.Motion.MotionBackend)); 247 248 if (needsMotionInputUpdate) 249 { 250 UpdateMotionInput(controllerConfig.Motion); 251 } 252 } 253 else 254 { 255 // Non-controller doesn't have motions. 256 _leftMotionInput = null; 257 } 258 259 _config = config; 260 261 _gamepad?.SetConfiguration(config); 262 } 263 264 private void UpdateMotionInput(MotionConfigController motionConfig) 265 { 266 if (motionConfig.MotionBackend != MotionInputBackendType.CemuHook) 267 { 268 _leftMotionInput = new MotionInput(); 269 } 270 else 271 { 272 _leftMotionInput = null; 273 } 274 } 275 276 public void Update() 277 { 278 // _gamepad may be altered by other threads 279 var gamepad = _gamepad; 280 281 if (gamepad != null && GamepadDriver != null) 282 { 283 State = gamepad.GetMappedStateSnapshot(); 284 285 if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion) 286 { 287 if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver) 288 { 289 if (gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion)) 290 { 291 Vector3 accelerometer = gamepad.GetMotionData(MotionInputId.Accelerometer); 292 Vector3 gyroscope = gamepad.GetMotionData(MotionInputId.Gyroscope); 293 294 accelerometer = new Vector3(accelerometer.X, -accelerometer.Z, accelerometer.Y); 295 gyroscope = new Vector3(gyroscope.X, -gyroscope.Z, gyroscope.Y); 296 297 _leftMotionInput.Update(accelerometer, gyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone); 298 299 if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair) 300 { 301 _rightMotionInput = _leftMotionInput; 302 } 303 } 304 } 305 else if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook && controllerConfig.Motion is CemuHookMotionConfigController cemuControllerConfig) 306 { 307 int clientId = (int)controllerConfig.PlayerIndex; 308 309 // First of all ensure we are registered 310 _cemuHookClient.RegisterClient(clientId, cemuControllerConfig.DsuServerHost, cemuControllerConfig.DsuServerPort); 311 312 // Then request and retrieve the data 313 _cemuHookClient.RequestData(clientId, cemuControllerConfig.Slot); 314 _cemuHookClient.TryGetData(clientId, cemuControllerConfig.Slot, out _leftMotionInput); 315 316 if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair) 317 { 318 if (!cemuControllerConfig.MirrorInput) 319 { 320 _cemuHookClient.RequestData(clientId, cemuControllerConfig.AltSlot); 321 _cemuHookClient.TryGetData(clientId, cemuControllerConfig.AltSlot, out _rightMotionInput); 322 } 323 else 324 { 325 _rightMotionInput = _leftMotionInput; 326 } 327 } 328 } 329 } 330 } 331 else 332 { 333 // Reset states 334 State = default; 335 _leftMotionInput = null; 336 } 337 } 338 339 public GamepadInput GetHLEInputState() 340 { 341 GamepadInput state = new(); 342 343 // First update all buttons 344 foreach (HLEButtonMappingEntry entry in _hleButtonMapping) 345 { 346 if (State.IsPressed(entry.DriverInputId)) 347 { 348 state.Buttons |= entry.HLEInput; 349 } 350 } 351 352 if (_gamepad is IKeyboard) 353 { 354 (float leftAxisX, float leftAxisY) = State.GetStick(StickInputId.Left); 355 (float rightAxisX, float rightAxisY) = State.GetStick(StickInputId.Right); 356 357 state.LStick = new JoystickPosition 358 { 359 Dx = ClampAxis(leftAxisX), 360 Dy = ClampAxis(leftAxisY), 361 }; 362 363 state.RStick = new JoystickPosition 364 { 365 Dx = ClampAxis(rightAxisX), 366 Dy = ClampAxis(rightAxisY), 367 }; 368 } 369 else if (_config is StandardControllerInputConfig controllerConfig) 370 { 371 (float leftAxisX, float leftAxisY) = State.GetStick(StickInputId.Left); 372 (float rightAxisX, float rightAxisY) = State.GetStick(StickInputId.Right); 373 374 state.LStick = ClampToCircle(ApplyDeadzone(leftAxisX, leftAxisY, controllerConfig.DeadzoneLeft), controllerConfig.RangeLeft); 375 state.RStick = ClampToCircle(ApplyDeadzone(rightAxisX, rightAxisY, controllerConfig.DeadzoneRight), controllerConfig.RangeRight); 376 } 377 378 return state; 379 } 380 381 [MethodImpl(MethodImplOptions.AggressiveInlining)] 382 private static JoystickPosition ApplyDeadzone(float x, float y, float deadzone) 383 { 384 float magnitudeClamped = Math.Min(MathF.Sqrt(x * x + y * y), 1f); 385 386 if (magnitudeClamped <= deadzone) 387 { 388 return new JoystickPosition { Dx = 0, Dy = 0 }; 389 } 390 391 return new JoystickPosition 392 { 393 Dx = ClampAxis((x / magnitudeClamped) * ((magnitudeClamped - deadzone) / (1 - deadzone))), 394 Dy = ClampAxis((y / magnitudeClamped) * ((magnitudeClamped - deadzone) / (1 - deadzone))), 395 }; 396 } 397 398 [MethodImpl(MethodImplOptions.AggressiveInlining)] 399 private static short ClampAxis(float value) 400 { 401 if (Math.Sign(value) < 0) 402 { 403 return (short)Math.Max(value * -short.MinValue, short.MinValue); 404 } 405 406 return (short)Math.Min(value * short.MaxValue, short.MaxValue); 407 } 408 409 [MethodImpl(MethodImplOptions.AggressiveInlining)] 410 private static JoystickPosition ClampToCircle(JoystickPosition position, float range) 411 { 412 Vector2 point = new Vector2(position.Dx, position.Dy) * range; 413 414 if (point.Length() > short.MaxValue) 415 { 416 point = point / point.Length() * short.MaxValue; 417 } 418 419 return new JoystickPosition 420 { 421 Dx = (int)point.X, 422 Dy = (int)point.Y, 423 }; 424 } 425 426 public SixAxisInput GetHLEMotionState(bool isJoyconRightPair = false) 427 { 428 float[] orientationForHLE = new float[9]; 429 Vector3 gyroscope; 430 Vector3 accelerometer; 431 Vector3 rotation; 432 433 MotionInput motionInput = _leftMotionInput; 434 435 if (isJoyconRightPair) 436 { 437 if (_rightMotionInput == null) 438 { 439 return default; 440 } 441 442 motionInput = _rightMotionInput; 443 } 444 445 if (motionInput != null) 446 { 447 gyroscope = Truncate(motionInput.Gyroscrope * 0.0027f, 3); 448 accelerometer = Truncate(motionInput.Accelerometer, 3); 449 rotation = Truncate(motionInput.Rotation * 0.0027f, 3); 450 451 Matrix4x4 orientation = motionInput.GetOrientation(); 452 453 orientationForHLE[0] = Math.Clamp(orientation.M11, -1f, 1f); 454 orientationForHLE[1] = Math.Clamp(orientation.M12, -1f, 1f); 455 orientationForHLE[2] = Math.Clamp(orientation.M13, -1f, 1f); 456 orientationForHLE[3] = Math.Clamp(orientation.M21, -1f, 1f); 457 orientationForHLE[4] = Math.Clamp(orientation.M22, -1f, 1f); 458 orientationForHLE[5] = Math.Clamp(orientation.M23, -1f, 1f); 459 orientationForHLE[6] = Math.Clamp(orientation.M31, -1f, 1f); 460 orientationForHLE[7] = Math.Clamp(orientation.M32, -1f, 1f); 461 orientationForHLE[8] = Math.Clamp(orientation.M33, -1f, 1f); 462 } 463 else 464 { 465 gyroscope = new Vector3(); 466 accelerometer = new Vector3(); 467 rotation = new Vector3(); 468 } 469 470 return new SixAxisInput 471 { 472 Accelerometer = accelerometer, 473 Gyroscope = gyroscope, 474 Rotation = rotation, 475 Orientation = orientationForHLE, 476 }; 477 } 478 479 private static Vector3 Truncate(Vector3 value, int decimals) 480 { 481 float power = MathF.Pow(10, decimals); 482 483 value.X = float.IsNegative(value.X) ? MathF.Ceiling(value.X * power) / power : MathF.Floor(value.X * power) / power; 484 value.Y = float.IsNegative(value.Y) ? MathF.Ceiling(value.Y * power) / power : MathF.Floor(value.Y * power) / power; 485 value.Z = float.IsNegative(value.Z) ? MathF.Ceiling(value.Z * power) / power : MathF.Floor(value.Z * power) / power; 486 487 return value; 488 } 489 490 public static KeyboardInput GetHLEKeyboardInput(IGamepadDriver KeyboardDriver) 491 { 492 var keyboard = KeyboardDriver.GetGamepad("0") as IKeyboard; 493 494 KeyboardStateSnapshot keyboardState = keyboard.GetKeyboardStateSnapshot(); 495 496 KeyboardInput hidKeyboard = new() 497 { 498 Modifier = 0, 499 Keys = new ulong[0x4], 500 }; 501 502 foreach (HLEKeyboardMappingEntry entry in _keyMapping) 503 { 504 ulong value = keyboardState.IsPressed(entry.TargetKey) ? 1UL : 0UL; 505 506 hidKeyboard.Keys[entry.Target / 0x40] |= (value << (entry.Target % 0x40)); 507 } 508 509 foreach (HLEKeyboardMappingEntry entry in _keyModifierMapping) 510 { 511 int value = keyboardState.IsPressed(entry.TargetKey) ? 1 : 0; 512 513 hidKeyboard.Modifier |= value << entry.Target; 514 } 515 516 return hidKeyboard; 517 518 } 519 520 protected virtual void Dispose(bool disposing) 521 { 522 if (disposing) 523 { 524 _gamepad?.Dispose(); 525 } 526 } 527 528 public void Dispose() 529 { 530 GC.SuppressFinalize(this); 531 Dispose(true); 532 } 533 534 public void UpdateRumble(ConcurrentQueue<(VibrationValue, VibrationValue)> queue) 535 { 536 if (queue.TryDequeue(out (VibrationValue, VibrationValue) dualVibrationValue)) 537 { 538 if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble) 539 { 540 VibrationValue leftVibrationValue = dualVibrationValue.Item1; 541 VibrationValue rightVibrationValue = dualVibrationValue.Item2; 542 543 float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble)); 544 float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble)); 545 546 _gamepad.Rumble(low, high, uint.MaxValue); 547 548 Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " + 549 $"L.low.amp={leftVibrationValue.AmplitudeLow}, " + 550 $"L.high.amp={leftVibrationValue.AmplitudeHigh}, " + 551 $"R.low.amp={rightVibrationValue.AmplitudeLow}, " + 552 $"R.high.amp={rightVibrationValue.AmplitudeHigh} " + 553 $"--> ({low}, {high})"); 554 } 555 } 556 } 557 } 558 }