From fff75664913237b086ecbccd68a74c8bc43e5a75 Mon Sep 17 00:00:00 2001 From: Ryan McClelland Date: Fri, 21 Aug 2026 00:08:00 -0700 Subject: [PATCH 1/2] libretro: add a sensor sub-device index to libretro.h A Wii Remote with a Nunchuk is one player on one port with two accelerometers, and the sensor interface addresses exactly one of each kind per port. Carry a sub-device index in the high bits of the existing id, the way RETRO_DEVICE_SUBCLASS already encodes a subclass into a device id. Index 0 encodes to exactly the values in use today, so nothing existing changes, and no environment number or capability flag is needed: a frontend that has never heard of this answers false from set_sensor_state and 0 from get_sensor_input, which is the fallback the header already documents. Mirrors libretro/RetroArch#19453; drop this patch when it lands. Header only; the reader follows. --- Externals/libretro-common/include/libretro.h | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Externals/libretro-common/include/libretro.h b/Externals/libretro-common/include/libretro.h index b09129494cd7..970d5754cb0f 100644 --- a/Externals/libretro-common/include/libretro.h +++ b/Externals/libretro-common/include/libretro.h @@ -4868,6 +4868,42 @@ enum retro_sensor_action #define RETRO_SENSOR_ILLUMINANCE 6 /** @} */ +/** @defgroup RETRO_SENSOR_SUBDEVICE Sensor Sub-Device Index + * @{ + */ + +/** + * The number of bits that a sub-device index is shifted by within a sensor ID + * or a \c retro_sensor_action. + * + * Addresses more than one sensor of the same kind on a single port. + * Index 0 denotes the controller itself and encodes to the values already in + * use (RETRO_SENSOR_SUBDEVICE(0, id) == id). Index 1 and above denote + * sub-devices, in whatever order the device type implies. + * + * @note \c retro_sensor_action is an enum, so an encoded action must be cast at + * the call site. \c RETRO_SENSOR_DUMMY pins the underlying type to \c int, so + * the encoded value is in range. + * + * @see RETRO_SENSOR_ID + * @see retro_sensor_action + */ +#define RETRO_SENSOR_INDEX_SHIFT 8 + +/** Mask of the bits below the sub-device index. */ +#define RETRO_SENSOR_INDEX_MASK ((1u << RETRO_SENSOR_INDEX_SHIFT) - 1u) + +/** Returns the sub-device index encoded in \c id; 0 is the controller itself. */ +#define RETRO_SENSOR_INDEX(id) ((unsigned)(id) >> RETRO_SENSOR_INDEX_SHIFT) + +/** Returns the sensor ID or action in \c id, without its sub-device index. */ +#define RETRO_SENSOR_BASE(id) ((unsigned)(id) & RETRO_SENSOR_INDEX_MASK) + +/** Addresses sensor \c id on sub-device \c index of a port. */ +#define RETRO_SENSOR_SUBDEVICE(index, id) \ + ((unsigned)((((unsigned)(index)) << RETRO_SENSOR_INDEX_SHIFT) | ((unsigned)(id)))) +/** @} */ + /** * Adjusts the state of a sensor. * From fe012fcc34d377945ed73a0891b6829174cbb7a5 Mon Sep 17 00:00:00 2001 From: Ryan McClelland Date: Fri, 21 Aug 2026 00:08:14 -0700 Subject: [PATCH 2/2] libretro: read the Nunchuk's accelerometer The sensor storage gains a sub-device dimension, so one port can carry more than one accelerometer, and a SensorDevice is registered per (port, sub-device). Sub-device 0 keeps the plain name "Sensor", so every control expression written before this still resolves to exactly the device it always did; the Nunchuk's is "Sensor1". Its accelerometer binds to the Nunchuk's own IMUAccelerometer group, which already existed and had simply never been bound. Binding it is additive. BuildDesiredExtensionState composes that group with swing, tilt and shake and substitutes a device lying flat when nothing is bound, so the L2 shake stays and a gamepad player sees no change. A frontend that cannot offer a second sensor says so, the flag stays clear, and nothing binds. Two fixes come with it, both separable in review: Seating a Nunchuk used to unbind the REMOTE's own accelerometer and the MotionPlus gyroscope as well. The whole sensor block sat in the else of "is there a Nunchuk", which also skipped the analog tilt fallback, so both hands went dead the moment the second one was plugged in. The block is hoisted out to cover every remote configuration. Its fallback now picks the right analog stick when a Nunchuk is fitted, since the left one is the Nunchuk's own -- which is what descWiimoteNunchuk has been advertising all along with nothing behind it. The remote's shake listened on L2 while every Wiimote descriptor advertises R2 for it. So the remote's shake button did nothing at all, and with a Nunchuk fitted L2 shook the remote and the Nunchuk together. Also drops the #if 0 around two unused Nunchuk group pointers. The bindings that used them were deleted rather than commented, so it read as though the groups were compiled out when they were only unbound. --- Source/Core/DolphinLibretro/Input.cpp | 247 ++++++++++++++++---------- Source/Core/DolphinLibretro/Input.h | 58 ++++-- 2 files changed, 191 insertions(+), 114 deletions(-) diff --git a/Source/Core/DolphinLibretro/Input.cpp b/Source/Core/DolphinLibretro/Input.cpp index 9b6360b60e71..d4d5cb727d28 100644 --- a/Source/Core/DolphinLibretro/Input.cpp +++ b/Source/Core/DolphinLibretro/Input.cpp @@ -92,6 +92,11 @@ typedef enum { SENSOR_COUNT } sensor_type_t; +/* Which sub-device of a Wii Remote's port the Nunchuk's accelerometer is. The + * remote itself is 0. A real Nunchuk has no gyroscope, so it is asked for an + * accelerometer and nothing else. */ +#define NUNCHUK_SENSOR_SUBDEVICE 1 + void retro_set_controller_port_device_gc(unsigned port, unsigned device); void retro_set_controller_port_device_wii(unsigned port, unsigned device); @@ -106,12 +111,12 @@ static struct retro_rumble_interface rumble{}; static unsigned input_types[8]; static bool g_init_wiimotes = false; static bool s_sensor_init_pending = false; -static bool sensor_enabled[NUM_CONTROLLERS_FOR_SENSORS][SENSOR_COUNT] = {}; +static bool sensor_enabled[NUM_CONTROLLERS_FOR_SENSORS][NUM_SENSOR_SUBDEVICES][SENSOR_COUNT] = {}; static int port_max; -double g_accel_pos[NUM_CONTROLLERS_FOR_SENSORS][3] = {}; // x, y, z -double g_accel_neg[NUM_CONTROLLERS_FOR_SENSORS][3] = {}; // x, y, z -double g_gyro_pos[NUM_CONTROLLERS_FOR_SENSORS][3] = {}; // x, y, z -double g_gyro_neg[NUM_CONTROLLERS_FOR_SENSORS][3] = {}; // x, y, z +double g_accel_pos[NUM_CONTROLLERS_FOR_SENSORS][NUM_SENSOR_SUBDEVICES][3] = {}; // x, y, z +double g_accel_neg[NUM_CONTROLLERS_FOR_SENSORS][NUM_SENSOR_SUBDEVICES][3] = {}; // x, y, z +double g_gyro_pos[NUM_CONTROLLERS_FOR_SENSORS][NUM_SENSOR_SUBDEVICES][3] = {}; // x, y, z +double g_gyro_neg[NUM_CONTROLLERS_FOR_SENSORS][NUM_SENSOR_SUBDEVICES][3] = {}; // x, y, z static struct retro_input_descriptor descGC[] = { {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left"}, @@ -660,18 +665,33 @@ void InitSensors() { if (sensor_interface.set_sensor_state) { - sensor_enabled[i][SENSOR_ACCELEROMETER] = sensor_interface.set_sensor_state(i, RETRO_SENSOR_ACCELEROMETER_ENABLE, 60); - sensor_enabled[i][SENSOR_GYRO] = sensor_interface.set_sensor_state(i, RETRO_SENSOR_GYROSCOPE_ENABLE, 60); - - if (sensor_enabled[i][SENSOR_ACCELEROMETER] || sensor_enabled[i][SENSOR_GYRO]) + for (unsigned s = 0; s < NUM_SENSOR_SUBDEVICES; s++) { - auto sensor = std::make_shared(i); - sensor->RegisterAll(); - g_controller_interface.AddDevice(sensor); - } + // The sub-device rides in the high bits of the action. A frontend + // that does not know about sub-devices returns false for anything but + // 0; the flag stays clear and nothing binds. + const retro_sensor_action accel_on = static_cast( + RETRO_SENSOR_SUBDEVICE(s, RETRO_SENSOR_ACCELEROMETER_ENABLE)); + sensor_enabled[i][s][SENSOR_ACCELEROMETER] = + sensor_interface.set_sensor_state(i, accel_on, 60); + + // Only the remote itself has a gyroscope to ask for; a Nunchuk has none. + if (s == 0) + { + sensor_enabled[i][s][SENSOR_GYRO] = + sensor_interface.set_sensor_state(i, RETRO_SENSOR_GYROSCOPE_ENABLE, 60); + } - INFO_LOG_FMT(BOOT, "Sensor interface: Port: {} ACCELEROMETER: {} GYROSCOPE: {}", i, - sensor_enabled[i][SENSOR_ACCELEROMETER], sensor_enabled[i][SENSOR_GYRO]); + if (sensor_enabled[i][s][SENSOR_ACCELEROMETER] || sensor_enabled[i][s][SENSOR_GYRO]) + { + auto sensor = std::make_shared(i, s); + sensor->RegisterAll(); + g_controller_interface.AddDevice(sensor); + } + + INFO_LOG_FMT(BOOT, "Sensor interface: Port: {} Sub-device: {} ACCELEROMETER: {} GYROSCOPE: {}", + i, s, sensor_enabled[i][s][SENSOR_ACCELEROMETER], sensor_enabled[i][s][SENSOR_GYRO]); + } } } @@ -698,15 +718,20 @@ void Shutdown() { Pad::ResetRumble(i); - // Was crossed over and hardcoded to port 0, leaving sensors running. - if (sensor_enabled[i][SENSOR_ACCELEROMETER]) - sensor_interface.set_sensor_state(i, RETRO_SENSOR_ACCELEROMETER_DISABLE, 0); + // Disable each sensor on the port it was enabled for. + for (unsigned s = 0; s < NUM_SENSOR_SUBDEVICES; s++) + { + if (sensor_enabled[i][s][SENSOR_ACCELEROMETER]) + sensor_interface.set_sensor_state(i, static_cast( + RETRO_SENSOR_SUBDEVICE(s, RETRO_SENSOR_ACCELEROMETER_DISABLE)), 0); - if (sensor_enabled[i][SENSOR_GYRO]) - sensor_interface.set_sensor_state(i, RETRO_SENSOR_GYROSCOPE_DISABLE, 0); + if (sensor_enabled[i][s][SENSOR_GYRO]) + sensor_interface.set_sensor_state(i, static_cast( + RETRO_SENSOR_SUBDEVICE(s, RETRO_SENSOR_GYROSCOPE_DISABLE)), 0); - sensor_enabled[i][SENSOR_ACCELEROMETER] = false; - sensor_enabled[i][SENSOR_GYRO] = false; + sensor_enabled[i][s][SENSOR_ACCELEROMETER] = false; + sensor_enabled[i][s][SENSOR_GYRO] = false; + } } Keyboard::Shutdown(); @@ -716,20 +741,24 @@ void Shutdown() rumble.set_rumble_state = nullptr; } -void UpdateAccelerometer(unsigned port) +void UpdateAccelerometer(unsigned port, unsigned subdevice) { - if (!sensor_enabled[port][SENSOR_ACCELEROMETER] || !sensor_interface.get_sensor_input) + if (!sensor_enabled[port][subdevice][SENSOR_ACCELEROMETER] || !sensor_interface.get_sensor_input) return; static const float G = 9.80665f; // read raw sensor values - float ax = sensor_interface.get_sensor_input(port, RETRO_SENSOR_ACCELEROMETER_X) * G; - float ay = sensor_interface.get_sensor_input(port, RETRO_SENSOR_ACCELEROMETER_Y) * G; - float az = sensor_interface.get_sensor_input(port, RETRO_SENSOR_ACCELEROMETER_Z) * G; - - // Collapsed, so a sideways remote with the dongle fitted still rotates. - if (wiimote_base_device(input_types[port]) == RETRO_DEVICE_WIIMOTE_SW) + float ax = sensor_interface.get_sensor_input( + port, RETRO_SENSOR_SUBDEVICE(subdevice, RETRO_SENSOR_ACCELEROMETER_X)) * G; + float ay = sensor_interface.get_sensor_input( + port, RETRO_SENSOR_SUBDEVICE(subdevice, RETRO_SENSOR_ACCELEROMETER_Y)) * G; + float az = sensor_interface.get_sensor_input( + port, RETRO_SENSOR_SUBDEVICE(subdevice, RETRO_SENSOR_ACCELEROMETER_Z)) * G; + + // Holding the remote sideways rotates what its sensors read. Sub-device 0 + // only: a Nunchuk is held the same way round regardless. + if (subdevice == 0 && wiimote_base_device(input_types[port]) == RETRO_DEVICE_WIIMOTE_SW) { float rx = -ay; // rotate 90° clockwise float ry = ax; @@ -738,19 +767,20 @@ void UpdateAccelerometer(unsigned port) } // write rotated values - g_accel_pos[port][0] = std::max(0.0f, ax); - g_accel_neg[port][0] = std::max(0.0f, -ax); + g_accel_pos[port][subdevice][0] = std::max(0.0f, ax); + g_accel_neg[port][subdevice][0] = std::max(0.0f, -ax); - g_accel_pos[port][1] = std::max(0.0f, ay); - g_accel_neg[port][1] = std::max(0.0f, -ay); + g_accel_pos[port][subdevice][1] = std::max(0.0f, ay); + g_accel_neg[port][subdevice][1] = std::max(0.0f, -ay); - g_accel_pos[port][2] = std::max(0.0f, az); - g_accel_neg[port][2] = std::max(0.0f, -az); + g_accel_pos[port][subdevice][2] = std::max(0.0f, az); + g_accel_neg[port][subdevice][2] = std::max(0.0f, -az); } void UpdateGyro(unsigned port) { - if (!sensor_enabled[port][SENSOR_GYRO] || !sensor_interface.get_sensor_input) + // Sub-device 0 only: the gyroscope is the remote's, by way of MotionPlus. + if (!sensor_enabled[port][0][SENSOR_GYRO] || !sensor_interface.get_sensor_input) return; // rad/s about the remote's own axes: +X left, +Y back, +Z up. @@ -766,16 +796,16 @@ void UpdateGyro(unsigned port) gy = ry; } - // Split across a one-sided pair; the expression parser clamps negatives away - // (see SensorDevice::RegisterAll). - g_gyro_pos[port][0] = std::max(0.0f, gx); - g_gyro_neg[port][0] = std::max(0.0f, -gx); + // Split each axis across a one-sided pair: the expression parser cannot + // carry a negative value (see SensorDevice::RegisterAll). + g_gyro_pos[port][0][0] = std::max(0.0f, gx); + g_gyro_neg[port][0][0] = std::max(0.0f, -gx); - g_gyro_pos[port][1] = std::max(0.0f, gy); - g_gyro_neg[port][1] = std::max(0.0f, -gy); + g_gyro_pos[port][0][1] = std::max(0.0f, gy); + g_gyro_neg[port][0][1] = std::max(0.0f, -gy); - g_gyro_pos[port][2] = std::max(0.0f, gz); - g_gyro_neg[port][2] = std::max(0.0f, -gz); + g_gyro_pos[port][0][2] = std::max(0.0f, gz); + g_gyro_neg[port][0][2] = std::max(0.0f, -gz); } void ResetControllers(const WiimoteUpdateFlags& f) @@ -848,7 +878,8 @@ void Update() for (int i = 0; i < port_max; ++i) { - UpdateAccelerometer(i); + for (unsigned s = 0; s < NUM_SENSOR_SUBDEVICES; ++s) + UpdateAccelerometer(i, s); UpdateGyro(i); } @@ -869,12 +900,12 @@ void Update() } } -static std::string GetQualifiedNameSensor(unsigned port) +static std::string GetQualifiedNameSensor(unsigned port, unsigned subdevice) { return ciface::Core::DeviceQualifier( std::string(Libretro::Input::source), static_cast(port), - std::string("Sensor") + Libretro::Input::SensorDeviceName(subdevice) ).ToString(); } @@ -1538,10 +1569,8 @@ void retro_set_controller_port_device_wii(unsigned port, unsigned device) ControllerEmu::ControlGroup* ncButtons = wm->GetNunchukGroup(NunchukGroup::Buttons); ControllerEmu::ControlGroup* ncStick = wm->GetNunchukGroup(NunchukGroup::Stick); ControllerEmu::ControlGroup* ncShake = wm->GetNunchukGroup(NunchukGroup::Shake); -#if 0 - ControllerEmu::ControlGroup* ncTilt = wm->GetNunchukGroup(NunchukGroup::Tilt); - ControllerEmu::ControlGroup* ncSwing = wm->GetNunchukGroup(NunchukGroup::Swing); -#endif + // Nunchuk Tilt and Swing stay unbound: its motion arrives below as a + // real accelerometer, and both sticks are already spoken for. ncButtons->SetControlExpression(0, "X"); // C ncButtons->SetControlExpression(1, "Y"); // Z ncStick->SetControlExpression(0, "`" + devAnalog + ":Y0-`"); // Up @@ -1578,58 +1607,86 @@ void retro_set_controller_port_device_wii(unsigned port, unsigned device) wmButtons->SetControlExpression(3, "A"); // 2 } - // Map accel data to tilt expressions - if (Libretro::Input::sensor_enabled[port][SENSOR_ACCELEROMETER] || - Libretro::Input::sensor_enabled[port][SENSOR_GYRO]) - { - std::string devSensor = Libretro::Input::GetQualifiedNameSensor(port); + wmButtons->SetControlExpression(4, "Select"); // - + wmButtons->SetControlExpression(5, "Start"); // + + } + + // Motion, for every remote configuration rather than only the ones with + // nothing on the expansion port. + if (Libretro::Input::sensor_enabled[port][0][SENSOR_ACCELEROMETER] || + Libretro::Input::sensor_enabled[port][0][SENSOR_GYRO]) + { + std::string devSensor = Libretro::Input::GetQualifiedNameSensor(port, 0); - if (Libretro::Input::sensor_enabled[port][SENSOR_ACCELEROMETER]) + if (Libretro::Input::sensor_enabled[port][0][SENSOR_ACCELEROMETER]) + { + // Accelerometer (6 inputs: Up, Down, Left, Right, Forward, Backward) + // Indices must match WiimoteEmu::LoadDefaults ordering (0..5) + auto* wmAccel = static_cast( + wm->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::IMUAccelerometer)); + if (wmAccel) { - // Accelerometer (6 inputs: Up, Down, Left, Right, Forward, Backward) - // Indices must match WiimoteEmu::LoadDefaults ordering (0..5) - auto* wmAccel = static_cast( - wm->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::IMUAccelerometer)); - if (wmAccel) - { - wmAccel->SetControlExpression(0, "`" + devSensor + ":AccelZ+`"); // Up - wmAccel->SetControlExpression(1, "`" + devSensor + ":AccelZ-`"); // Down - wmAccel->SetControlExpression(2, "`" + devSensor + ":AccelX+`"); // Left - wmAccel->SetControlExpression(3, "`" + devSensor + ":AccelX-`"); // Right - wmAccel->SetControlExpression(4, "`" + devSensor + ":AccelY-`"); // Forward - wmAccel->SetControlExpression(5, "`" + devSensor + ":AccelY+`"); // Backward - } + wmAccel->SetControlExpression(0, "`" + devSensor + ":AccelZ+`"); // Up + wmAccel->SetControlExpression(1, "`" + devSensor + ":AccelZ-`"); // Down + wmAccel->SetControlExpression(2, "`" + devSensor + ":AccelX+`"); // Left + wmAccel->SetControlExpression(3, "`" + devSensor + ":AccelX-`"); // Right + wmAccel->SetControlExpression(4, "`" + devSensor + ":AccelY-`"); // Forward + wmAccel->SetControlExpression(5, "`" + devSensor + ":AccelY+`"); // Backward } + } - // A sibling, not a child: nested, gyro-without-accelerometer bound neither. - if (Libretro::Input::sensor_enabled[port][SENSOR_GYRO]) + // A sibling of the accelerometer branch: gyro without accelerometer + // must still bind. + if (Libretro::Input::sensor_enabled[port][0][SENSOR_GYRO]) + { + // Gyroscope (6 inputs: PitchUp/Down, RollLeft/Right, YawLeft/Right) + auto* wmGyro = static_cast( + wm->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::IMUGyroscope)); + if (wmGyro) { - // Gyroscope (6 inputs: PitchUp/Down, RollLeft/Right, YawLeft/Right) - auto* wmGyro = static_cast( - wm->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::IMUGyroscope)); - if (wmGyro) - { - // Right-hand rule about +X left, +Y back, +Z up: +X is pitch down, - // +Y rolls the top left, +Z swings the nose left. GetRawState() - // reads these as [1]-[0], [2]-[3], [4]-[5]. - wmGyro->SetControlExpression(0, "`" + devSensor + ":GyroX-`"); // Pitch Up - wmGyro->SetControlExpression(1, "`" + devSensor + ":GyroX+`"); // Pitch Down - wmGyro->SetControlExpression(2, "`" + devSensor + ":GyroY+`"); // Roll Left - wmGyro->SetControlExpression(3, "`" + devSensor + ":GyroY-`"); // Roll Right - wmGyro->SetControlExpression(4, "`" + devSensor + ":GyroZ+`"); // Yaw Left - wmGyro->SetControlExpression(5, "`" + devSensor + ":GyroZ-`"); // Yaw Right - } + // Right-hand rule about the accelerometer's frame (+X left, +Y + // back, +Z up): +X is pitch down, +Y rolls the top left, +Z swings + // the nose left. GetRawState() reads [1]-[0], [2]-[3], [4]-[5]. + wmGyro->SetControlExpression(0, "`" + devSensor + ":GyroX-`"); // Pitch Up + wmGyro->SetControlExpression(1, "`" + devSensor + ":GyroX+`"); // Pitch Down + wmGyro->SetControlExpression(2, "`" + devSensor + ":GyroY+`"); // Roll Left + wmGyro->SetControlExpression(3, "`" + devSensor + ":GyroY-`"); // Roll Right + wmGyro->SetControlExpression(4, "`" + devSensor + ":GyroZ+`"); // Yaw Left + wmGyro->SetControlExpression(5, "`" + devSensor + ":GyroZ-`"); // Yaw Right } } - else + } + else + { + // No sensors, so tilt falls back to a stick -- the right one when a + // Nunchuk is fitted, since the left one is the Nunchuk's. + const std::string tiltAxis = (device == RETRO_DEVICE_WIIMOTE_NC) ? "1" : "0"; + wmTilt->SetControlExpression(0, "`" + devAnalog + ":Y" + tiltAxis + "-`"); // Forward + wmTilt->SetControlExpression(1, "`" + devAnalog + ":Y" + tiltAxis + "+`"); // Backward + wmTilt->SetControlExpression(2, "`" + devAnalog + ":X" + tiltAxis + "-`"); // Left + wmTilt->SetControlExpression(3, "`" + devAnalog + ":X" + tiltAxis + "+`"); // Right + } + + // The Nunchuk's own accelerometer. Additive: + // Nunchuk::BuildDesiredExtensionState composes this group with swing, tilt + // and shake, and substitutes a flat device when nothing is bound. + if (device == RETRO_DEVICE_WIIMOTE_NC && + Libretro::Input::sensor_enabled[port][NUNCHUK_SENSOR_SUBDEVICE][SENSOR_ACCELEROMETER]) + { + auto* ncAccel = static_cast( + wm->GetNunchukGroup(NunchukGroup::IMUAccelerometer)); + if (ncAccel) { - wmTilt->SetControlExpression(0, "`" + devAnalog + ":Y0-`"); // Forward - wmTilt->SetControlExpression(1, "`" + devAnalog + ":Y0+`"); // Backward - wmTilt->SetControlExpression(2, "`" + devAnalog + ":X0-`"); // Left - wmTilt->SetControlExpression(3, "`" + devAnalog + ":X0+`"); // Right + // Same six-index order and frame as the remote's above. + const std::string devNunchuk = + Libretro::Input::GetQualifiedNameSensor(port, NUNCHUK_SENSOR_SUBDEVICE); + ncAccel->SetControlExpression(0, "`" + devNunchuk + ":AccelZ+`"); // Up + ncAccel->SetControlExpression(1, "`" + devNunchuk + ":AccelZ-`"); // Down + ncAccel->SetControlExpression(2, "`" + devNunchuk + ":AccelX+`"); // Left + ncAccel->SetControlExpression(3, "`" + devNunchuk + ":AccelX-`"); // Right + ncAccel->SetControlExpression(4, "`" + devNunchuk + ":AccelY-`"); // Forward + ncAccel->SetControlExpression(5, "`" + devNunchuk + ":AccelY+`"); // Backward } - wmButtons->SetControlExpression(4, "Select"); // - - wmButtons->SetControlExpression(5, "Start"); // + } wmButtons->SetControlExpression(6, "R3"); // Home diff --git a/Source/Core/DolphinLibretro/Input.h b/Source/Core/DolphinLibretro/Input.h index fcebb3891b56..763632c4adc3 100644 --- a/Source/Core/DolphinLibretro/Input.h +++ b/Source/Core/DolphinLibretro/Input.h @@ -1,11 +1,17 @@ #pragma once +#include + #include #include "Common/WindowSystemInfo.h" // only 4 support sensors, but the array may be upto 8 if connected GC controllers #define NUM_CONTROLLERS_FOR_SENSORS 8 +// Sensors addressable on one port. Index 0 is the controller itself, index 1 +// whatever is plugged into it (for a Wii Remote, the Nunchuk). +#define NUM_SENSOR_SUBDEVICES 2 + struct WiimoteUpdateFlags { bool irMode = false; @@ -38,17 +44,24 @@ namespace Libretro namespace Input { constexpr std::string_view source = "Libretro"; -extern double g_accel_pos[NUM_CONTROLLERS_FOR_SENSORS][3]; -extern double g_accel_neg[NUM_CONTROLLERS_FOR_SENSORS][3]; -extern double g_gyro_pos[NUM_CONTROLLERS_FOR_SENSORS][3]; -extern double g_gyro_neg[NUM_CONTROLLERS_FOR_SENSORS][3]; +extern double g_accel_pos[NUM_CONTROLLERS_FOR_SENSORS][NUM_SENSOR_SUBDEVICES][3]; +extern double g_accel_neg[NUM_CONTROLLERS_FOR_SENSORS][NUM_SENSOR_SUBDEVICES][3]; +extern double g_gyro_pos[NUM_CONTROLLERS_FOR_SENSORS][NUM_SENSOR_SUBDEVICES][3]; +extern double g_gyro_neg[NUM_CONTROLLERS_FOR_SENSORS][NUM_SENSOR_SUBDEVICES][3]; static retro_sensor_interface sensor_interface = {0}; +/// The ciface device name for one port's sub-device. Index 0 keeps the plain +/// "Sensor", so pre-existing control expressions still resolve. +inline std::string SensorDeviceName(unsigned subdevice) +{ + return subdevice == 0 ? std::string("Sensor") : "Sensor" + std::to_string(subdevice); +} + void Init(const WindowSystemInfo& wsi); void InitStage2(); void InitSensors(); -void UpdateAccelerometer(unsigned port); +void UpdateAccelerometer(unsigned port, unsigned subdevice); void UpdateGyro(unsigned port); void Update(); void Shutdown(); @@ -62,9 +75,14 @@ void UpdateGCMappings(const WiimoteUpdateFlags& f, unsigned port, unsigned devic class SensorDevice : public ciface::Core::Device { public: - explicit SensorDevice(unsigned port) : m_port(port) {} + /// `port` picks the player, `subdevice` the sensor on that port. + SensorDevice(unsigned port, unsigned subdevice) + : m_port(port), m_subdevice(subdevice), + m_name(Libretro::Input::SensorDeviceName(subdevice)) + { + } - std::string GetName() const override { return "Sensor"; } + std::string GetName() const override { return m_name; } std::string GetSource() const override { return std::string(Libretro::Input::source); } unsigned int GetPort() const { return m_port; } ciface::Core::DeviceRemoval UpdateInput() override { return ciface::Core::DeviceRemoval::Keep; } @@ -87,22 +105,24 @@ class SensorDevice : public ciface::Core::Device /// subtract one direction from the other, rebuilding the signed value. void RegisterAll() { - AddInput(new ScalarInput("GyroX+", &Libretro::Input::g_gyro_pos[m_port][0])); - AddInput(new ScalarInput("GyroX-", &Libretro::Input::g_gyro_neg[m_port][0])); - AddInput(new ScalarInput("GyroY+", &Libretro::Input::g_gyro_pos[m_port][1])); - AddInput(new ScalarInput("GyroY-", &Libretro::Input::g_gyro_neg[m_port][1])); - AddInput(new ScalarInput("GyroZ+", &Libretro::Input::g_gyro_pos[m_port][2])); - AddInput(new ScalarInput("GyroZ-", &Libretro::Input::g_gyro_neg[m_port][2])); - AddInput(new ScalarInput("AccelX+", &Libretro::Input::g_accel_pos[m_port][0])); - AddInput(new ScalarInput("AccelX-", &Libretro::Input::g_accel_neg[m_port][0])); - AddInput(new ScalarInput("AccelY+", &Libretro::Input::g_accel_pos[m_port][1])); - AddInput(new ScalarInput("AccelY-", &Libretro::Input::g_accel_neg[m_port][1])); - AddInput(new ScalarInput("AccelZ+", &Libretro::Input::g_accel_pos[m_port][2])); - AddInput(new ScalarInput("AccelZ-", &Libretro::Input::g_accel_neg[m_port][2])); + AddInput(new ScalarInput("GyroX+", &Libretro::Input::g_gyro_pos[m_port][m_subdevice][0])); + AddInput(new ScalarInput("GyroX-", &Libretro::Input::g_gyro_neg[m_port][m_subdevice][0])); + AddInput(new ScalarInput("GyroY+", &Libretro::Input::g_gyro_pos[m_port][m_subdevice][1])); + AddInput(new ScalarInput("GyroY-", &Libretro::Input::g_gyro_neg[m_port][m_subdevice][1])); + AddInput(new ScalarInput("GyroZ+", &Libretro::Input::g_gyro_pos[m_port][m_subdevice][2])); + AddInput(new ScalarInput("GyroZ-", &Libretro::Input::g_gyro_neg[m_port][m_subdevice][2])); + AddInput(new ScalarInput("AccelX+", &Libretro::Input::g_accel_pos[m_port][m_subdevice][0])); + AddInput(new ScalarInput("AccelX-", &Libretro::Input::g_accel_neg[m_port][m_subdevice][0])); + AddInput(new ScalarInput("AccelY+", &Libretro::Input::g_accel_pos[m_port][m_subdevice][1])); + AddInput(new ScalarInput("AccelY-", &Libretro::Input::g_accel_neg[m_port][m_subdevice][1])); + AddInput(new ScalarInput("AccelZ+", &Libretro::Input::g_accel_pos[m_port][m_subdevice][2])); + AddInput(new ScalarInput("AccelZ-", &Libretro::Input::g_accel_neg[m_port][m_subdevice][2])); } private: unsigned m_port; + unsigned m_subdevice; + std::string m_name; }; class GyroDevice : public ciface::Core::Device