diff --git a/aurora-main/lib/dolphin/pad/pad.cpp b/aurora-main/lib/dolphin/pad/pad.cpp index 7ae292f..acdbe2a 100644 --- a/aurora-main/lib/dolphin/pad/pad.cpp +++ b/aurora-main/lib/dolphin/pad/pad.cpp @@ -195,7 +195,7 @@ std::array g_defaultButtonsJoyPair{{ }}; // Wii U Pro Controllers through SDL's HIDAPI Wii driver. No SDL_GamepadType -// singles them out, so they are picked by the name the driver gives them (see +// singles them out, so they are picked by name or USB id (see // __PADSetDefaultMapping). Wii Remotes, with or without a Nunchuk or Classic // Controller, are read by the game through KPAD instead and never get a // GameCube mapping (the runtime hides those ports from PADRead). @@ -451,11 +451,14 @@ static void reset_alt_button_mapping(aurora::input::GameController* controller) // SDL's hidapi Wii driver names the pad "Nintendo Wii U Pro Controller"; the // other names it produces are Wii Remotes, which the game reads through KPAD -// and which therefore never take a GameCube mapping. +// and which therefore never take a GameCube mapping. The Linux kernel driver's +// "Nintendo Wii Remote Pro Controller" is only recognised by its USB id. static bool wii_default_mapping(const aurora::input::GameController* controller, std::array& out) { const char* name = SDL_GetGamepadName(controller->m_controller); - if (name == nullptr || SDL_strstr(name, "Wii U Pro Controller") == nullptr) { + const bool wiiUProId = SDL_GetGamepadVendor(controller->m_controller) == 0x057e && + SDL_GetGamepadProduct(controller->m_controller) == 0x0330; + if (!wiiUProId && (name == nullptr || SDL_strstr(name, "Wii U Pro Controller") == nullptr)) { return false; } out = g_defaultButtonsWiiUPro; diff --git a/runtime/src/settings_overlay.cpp b/runtime/src/settings_overlay.cpp index 6ac04dd..4836c6e 100644 --- a/runtime/src/settings_overlay.cpp +++ b/runtime/src/settings_overlay.cpp @@ -798,6 +798,12 @@ void DrawControllerSettings() { ImGui::EndMenu(); } + if (WiiRemoteInput::IsRemoteChannel(selectedGamePort)) { + ImGui::TextDisabled("The game reads this controller as a Wii Remote, so the button mapping does not apply."); + DrawRumbleSettings(); + return; + } + uint32_t mappingCount = 0; PADButtonMapping* mappings = PADGetButtonMappings(static_cast(g_controllerPort), &mappingCount); if (mappings == nullptr || mappingCount != PAD_BUTTON_COUNT) { diff --git a/runtime/src/wii_remote_input.cpp b/runtime/src/wii_remote_input.cpp index 8e286e4..b1df42e 100644 --- a/runtime/src/wii_remote_input.cpp +++ b/runtime/src/wii_remote_input.cpp @@ -17,9 +17,12 @@ #include #include #include +#include #include #include #include +#include +#include #include namespace WiiRemoteInput { @@ -66,6 +69,8 @@ constexpr uint32_t kClUp = 0x0001, kClLeft = 0x0002, kClZR = 0x0004, kClX = 0x00 kClB = 0x0040, kClZL = 0x0080, kClR = 0x0200, kClPlus = 0x0400, kClHome = 0x0800, kClMinus = 0x1000, kClL = 0x2000, kClDown = 0x4000, kClRight = 0x8000; +constexpr Uint16 kNintendoVendor = 0x057e; + // How long a vanished remote keeps its channel alive with neutral input. SDL's // in-place reconnect after an extension change takes well under a second; a // remote that is really gone shows up as disconnected after this. @@ -89,6 +94,7 @@ bool g_scanning = false; uint64_t g_driverOffSinceMs = 0; // When the current scan started (last Wii controller seen). uint64_t g_lostAtMs = 0; +bool g_hidrawWarned = false; // Instance ids whose accelerometers have been switched on. SDL keeps sensors // off until asked and forgets that when the gamepad is closed, so a re-paired @@ -435,6 +441,110 @@ bool AnyWiiControllerConnected() { return false; } +// True for an evdev node served by the kernel hid-wiimote driver. +bool IsKernelWiiNode(SDL_JoystickID id) { + const char* path = SDL_GetGamepadPathForID(id); + return SDL_GetGamepadVendorForID(id) == kNintendoVendor && path != nullptr && + std::strncmp(path, "/dev/input/", 11) == 0; +} + +bool IsClassicNodeName(const char* name) { + return NameContains(name, "Classic Controller"); +} + +// HID device behind a kernel evdev node; the remote and its extension nodes share it. +std::string KernelParent(SDL_JoystickID id) { + static std::unordered_map s_parents; + if (const auto it = s_parents.find(id); it != s_parents.end()) { + return it->second; + } + const char* path = SDL_GetGamepadPathForID(id); + if (path == nullptr) { + return {}; + } + std::error_code ec; + const auto parent = std::filesystem::canonical( + std::filesystem::path("/sys/class/input") / std::filesystem::path(path).filename() / "device" / "device", ec); + if (ec) { + return {}; + } + return s_parents.emplace(id, parent.string()).first->second; +} + +// The kernel node on the same remote as `gamepad`: its Classic Controller node +// when `wantClassic`, else its remote node. +SDL_Gamepad* KernelSibling(SDL_Gamepad* gamepad, bool wantClassic) { + const SDL_JoystickID self = SDL_GetGamepadID(gamepad); + const std::string parent = KernelParent(self); + if (parent.empty()) { + return nullptr; + } + int count = 0; + SDL_JoystickID* ids = SDL_GetGamepads(&count); + if (ids == nullptr) { + return nullptr; + } + SDL_Gamepad* sibling = nullptr; + for (int i = 0; i < count && sibling == nullptr; ++i) { + if (ids[i] != self && IsKernelWiiNode(ids[i]) && + IsClassicNodeName(SDL_GetGamepadNameForID(ids[i])) == wantClassic && KernelParent(ids[i]) == parent) { + sibling = SDL_GetGamepadFromID(ids[i]); + } + } + SDL_free(ids); + return sibling; +} + +// The kernel exposes a remote and its Classic Controller as separate gamepads. +// The remote's port reads both, as on a Wii; the Classic node's own port stays empty. +Kind KindForGamepad(SDL_Gamepad* gamepad) { + if (gamepad == nullptr) { + return Kind::NotWii; + } + const char* name = SDL_GetGamepadName(gamepad); + const Kind kind = KindForName(name); + if ((kind != Kind::Remote && kind != Kind::RemoteWithClassic) || !IsKernelWiiNode(SDL_GetGamepadID(gamepad))) { + return kind; + } + if (IsClassicNodeName(name)) { + return KernelSibling(gamepad, false) != nullptr ? Kind::NotWii : kind; + } + return KernelSibling(gamepad, true) != nullptr ? Kind::RemoteWithClassic : kind; +} + +// A kernel Classic Controller node whose remote node carries it. +bool IsCarriedKernelNode(SDL_Gamepad* gamepad) { + return gamepad != nullptr && IsKernelWiiNode(SDL_GetGamepadID(gamepad)) && + IsClassicNodeName(SDL_GetGamepadName(gamepad)) && KernelSibling(gamepad, false) != nullptr; +} + +// Logs once when the kernel driver serves a Wii controller SDL's driver could have had. +void WarnIfKernelDriverServing() { + if (g_hidrawWarned || !g_wiiDriverEnabled) { + return; + } + const uint64_t now = SDL_GetTicks(); + static uint64_t s_nextCheckMs = 0; + if (now < s_nextCheckMs) { + return; + } + s_nextCheckMs = now + 2000; + int count = 0; + SDL_JoystickID* ids = SDL_GetGamepads(&count); + if (ids == nullptr) { + return; + } + for (int i = 0; i < count && !g_hidrawWarned; ++i) { + const Uint16 product = SDL_GetGamepadProductForID(ids[i]); + if ((product == 0x0306 || product == 0x0330) && IsKernelWiiNode(ids[i])) { + g_hidrawWarned = true; + RT_LOG(RT_TAG_CONFIG) << "A Wii controller is served by the kernel hid-wiimote driver. For full Wii " + << "Remote support see the Linux udev rule in the README." << std::endl; + } + } + SDL_free(ids); +} + // Route SDL's input diagnostics (HIDAPI open failures, the Wii driver's // extension/status messages) into console.log, minus the periodic chatter. // A sub-warning message is written once: SDL repeats the same line on every @@ -553,6 +663,7 @@ void Poll() { for (uint32_t port = 0; port < PAD_MAX_CONTROLLERS; ++port) { (void)EffectiveKind(port); } + WarnIfKernelDriverServing(); if (!g_wiiDriverEnabled) { return; } @@ -607,11 +718,13 @@ uint32_t ScanCount() { return g_scanCount; } -// Maps the gamepad name SDL's Wii driver reports to a Kind. +// Maps the gamepad name SDL reports to a Kind: "Nintendo Wii Remote", +// "... with Nunchuk", "... with Classic Controller" and "Nintendo Wii U Pro +// Controller" from SDL's Wii driver; the Linux kernel's "Nintendo Wii Remote +// Classic Controller" is read like the Classic Controller. Kind KindForName(const char* name) { - // Names come from SDL's hidapi Wii driver: "Nintendo Wii Remote", - // "Nintendo Wii Remote with Nunchuk", "Nintendo Wii Remote with Classic - // Controller" and "Nintendo Wii U Pro Controller". + // The kernel's Pro Controller is a plain gamepad with a built-in SDL mapping. + if (NameContains(name, "Wii Remote Pro Controller")) return Kind::NotWii; if (NameContains(name, "Wii U Pro Controller")) return Kind::WiiUPro; if (!NameContains(name, "Wii Remote")) return Kind::NotWii; if (NameContains(name, "Nunchuk")) return Kind::RemoteWithNunchuk; @@ -623,8 +736,7 @@ Kind KindForName(const char* name) { Kind KindForPort(uint32_t port) { if (port >= PAD_MAX_CONTROLLERS) return Kind::NotWii; SDL_Gamepad* gamepad = SDL_GetGamepadFromPlayerIndex(static_cast(port)); - if (gamepad == nullptr) return Kind::NotWii; - return KindForName(SDL_GetGamepadName(gamepad)); + return KindForGamepad(gamepad); } // Human-readable name of a Kind for the settings overlay. @@ -650,7 +762,7 @@ Kind EffectiveKind(uint32_t chan) { if (chan >= PAD_MAX_CONTROLLERS) return Kind::NotWii; PortMemory& memory = g_ports[chan]; SDL_Gamepad* gamepad = SDL_GetGamepadFromPlayerIndex(static_cast(chan)); - const Kind live = gamepad != nullptr ? KindForName(SDL_GetGamepadName(gamepad)) : Kind::NotWii; + const Kind live = KindForGamepad(gamepad); const uint64_t now = SDL_GetTicks(); if (live != Kind::NotWii) { memory.lastKind = live; @@ -676,7 +788,8 @@ bool IsRemoteChannel(uint32_t chan) { // Marks KPAD-served ports as "no controller" in the GameCube pad statuses. void HideRemotesFromPad(PADStatus* statuses, uint32_t count) { for (uint32_t port = 0; port < count && port < PAD_MAX_CONTROLLERS; ++port) { - if (IsRemoteChannel(port)) { + if (IsRemoteChannel(port) || + IsCarriedKernelNode(SDL_GetGamepadFromPlayerIndex(static_cast(port)))) { statuses[port] = {}; statuses[port].err = PAD_ERR_NO_CONTROLLER; } @@ -699,6 +812,14 @@ int16_t ClassicStickRaw(Sint16 axis, bool invert) { return static_cast(std::clamp(std::lround(value / 64.0), -512L, 511L)); } +// The kernel driver's evdev axes span SDL's full range; scaled to WPAD's. +int16_t KernelClassicStickRaw(Sint16 axis, bool invert) { + float value = static_cast(axis) / 32767.0f; + if (invert) value = -value; + value = std::clamp(value, -1.0f, 1.0f); + return static_cast(std::clamp(std::lround(value * 512.0f), -512L, 511L)); +} + // KPAD's default Classic Controller stick limits; the game uses the cross clamp. constexpr float kKpadStickMin = 60.0f; constexpr float kKpadStickMax = 308.0f; @@ -723,7 +844,7 @@ bool ReadKpadSample(uint32_t chan, KpadSample& sample) { return false; } SDL_Gamepad* gamepad = SDL_GetGamepadFromPlayerIndex(static_cast(chan)); - const Kind kind = gamepad != nullptr ? KindForName(SDL_GetGamepadName(gamepad)) : Kind::NotWii; + const Kind kind = KindForGamepad(gamepad); if (!IsKpadKind(kind)) { const Kind remembered = EffectiveKind(chan); if (!IsKpadKind(remembered)) { @@ -739,20 +860,42 @@ bool ReadKpadSample(uint32_t chan, KpadSample& sample) { EnsureSensors(gamepad, chan); sample = {}; - const auto raw = [&](int index, uint32_t bit) { - if (SDL_GetJoystickButton(joystick, index)) sample.hold |= bit; - }; - raw(kRawA, kWpadA); - raw(kRawB, kWpadB); - raw(kRawOne, kWpadOne); - raw(kRawTwo, kWpadTwo); - raw(kRawPlus, kWpadPlus); - raw(kRawMinus, kWpadMinus); - raw(kRawHome, kWpadHome); - raw(kRawDpadUp, kWpadUp); - raw(kRawDpadDown, kWpadDown); - raw(kRawDpadLeft, kWpadLeft); - raw(kRawDpadRight, kWpadRight); + // The kernel's evdev nodes have regular SDL mappings, so they are read as gamepads. + const bool kernel = IsKernelWiiNode(SDL_GetGamepadID(gamepad)); + const bool classicNode = kernel && IsClassicNodeName(SDL_GetGamepadName(gamepad)); + if (kernel) { + const auto mapped = [&](SDL_GamepadButton button, uint32_t bit) { + if (SDL_GetGamepadButton(gamepad, button)) sample.hold |= bit; + }; + if (!classicNode) { + mapped(SDL_GAMEPAD_BUTTON_EAST, kWpadA); + mapped(SDL_GAMEPAD_BUTTON_SOUTH, kWpadB); + mapped(SDL_GAMEPAD_BUTTON_WEST, kWpadOne); + mapped(SDL_GAMEPAD_BUTTON_NORTH, kWpadTwo); + mapped(SDL_GAMEPAD_BUTTON_START, kWpadPlus); + mapped(SDL_GAMEPAD_BUTTON_BACK, kWpadMinus); + mapped(SDL_GAMEPAD_BUTTON_GUIDE, kWpadHome); + mapped(SDL_GAMEPAD_BUTTON_DPAD_UP, kWpadUp); + mapped(SDL_GAMEPAD_BUTTON_DPAD_DOWN, kWpadDown); + mapped(SDL_GAMEPAD_BUTTON_DPAD_LEFT, kWpadLeft); + mapped(SDL_GAMEPAD_BUTTON_DPAD_RIGHT, kWpadRight); + } + } else { + const auto raw = [&](int index, uint32_t bit) { + if (SDL_GetJoystickButton(joystick, index)) sample.hold |= bit; + }; + raw(kRawA, kWpadA); + raw(kRawB, kWpadB); + raw(kRawOne, kWpadOne); + raw(kRawTwo, kWpadTwo); + raw(kRawPlus, kWpadPlus); + raw(kRawMinus, kWpadMinus); + raw(kRawHome, kWpadHome); + raw(kRawDpadUp, kWpadUp); + raw(kRawDpadDown, kWpadDown); + raw(kRawDpadLeft, kWpadLeft); + raw(kRawDpadRight, kWpadRight); + } float rawG[3] = {}; const bool haveRaw = ReadSdlAccel(gamepad, SDL_SENSOR_ACCEL, rawG); @@ -778,8 +921,17 @@ bool ReadKpadSample(uint32_t chan, KpadSample& sample) { // Home, the L/R clicks as shoulders, the D-pad as buttons 11-14 (never // through SDL's gamepad mapping, which expects a hat), ZL/ZR as the // trigger axes. - const auto cl = [&](int index, uint32_t bit) { - if (SDL_GetJoystickButton(joystick, index)) sample.clHold |= bit; + SDL_Gamepad* classic = gamepad; + if (kernel && !classicNode) { + classic = KernelSibling(gamepad, true); + if (classic == nullptr) { + classic = gamepad; + } + } + const auto cl = [&](SDL_GamepadButton button, uint32_t bit) { + if (kernel ? SDL_GetGamepadButton(classic, button) : SDL_GetJoystickButton(joystick, button)) { + sample.clHold |= bit; + } }; cl(SDL_GAMEPAD_BUTTON_EAST, kClA); cl(SDL_GAMEPAD_BUTTON_SOUTH, kClB); @@ -794,18 +946,29 @@ bool ReadKpadSample(uint32_t chan, KpadSample& sample) { cl(SDL_GAMEPAD_BUTTON_DPAD_DOWN, kClDown); cl(SDL_GAMEPAD_BUTTON_DPAD_LEFT, kClLeft); cl(SDL_GAMEPAD_BUTTON_DPAD_RIGHT, kClRight); - if (SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) > 0) sample.clHold |= kClZL; - if (SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) > 0) sample.clHold |= kClZR; - const Sint16 lx = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX); - const Sint16 ly = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY); - const Sint16 rx = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX); - const Sint16 ry = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY); - sample.clLStickRaw[0] = ClassicStickRaw(lx, false); - sample.clLStickRaw[1] = ClassicStickRaw(ly, true); - sample.clRStickRaw[0] = ClassicStickRaw(rx, false); - sample.clRStickRaw[1] = ClassicStickRaw(ry, true); - ClassicStickFloat(sample.clLStickRaw, sample.clLStick); - ClassicStickFloat(sample.clRStickRaw, sample.clRStick); + if (SDL_GetGamepadAxis(classic, SDL_GAMEPAD_AXIS_LEFT_TRIGGER) > 0) sample.clHold |= kClZL; + if (SDL_GetGamepadAxis(classic, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) > 0) sample.clHold |= kClZR; + const Sint16 lx = SDL_GetGamepadAxis(classic, SDL_GAMEPAD_AXIS_LEFTX); + const Sint16 ly = SDL_GetGamepadAxis(classic, SDL_GAMEPAD_AXIS_LEFTY); + const Sint16 rx = SDL_GetGamepadAxis(classic, SDL_GAMEPAD_AXIS_RIGHTX); + const Sint16 ry = SDL_GetGamepadAxis(classic, SDL_GAMEPAD_AXIS_RIGHTY); + if (kernel) { + sample.clLStickRaw[0] = KernelClassicStickRaw(lx, false); + sample.clLStickRaw[1] = KernelClassicStickRaw(ly, true); + sample.clRStickRaw[0] = KernelClassicStickRaw(rx, false); + sample.clRStickRaw[1] = KernelClassicStickRaw(ry, true); + sample.clLStick[0] = std::clamp(static_cast(lx) / 32767.0f, -1.0f, 1.0f); + sample.clLStick[1] = std::clamp(-static_cast(ly) / 32767.0f, -1.0f, 1.0f); + sample.clRStick[0] = std::clamp(static_cast(rx) / 32767.0f, -1.0f, 1.0f); + sample.clRStick[1] = std::clamp(-static_cast(ry) / 32767.0f, -1.0f, 1.0f); + } else { + sample.clLStickRaw[0] = ClassicStickRaw(lx, false); + sample.clLStickRaw[1] = ClassicStickRaw(ly, true); + sample.clRStickRaw[0] = ClassicStickRaw(rx, false); + sample.clRStickRaw[1] = ClassicStickRaw(ry, true); + ClassicStickFloat(sample.clLStickRaw, sample.clLStick); + ClassicStickFloat(sample.clRStickRaw, sample.clRStick); + } // Only the full-press click of L/R reaches SDL; report it as a full pull. sample.clTriggerL = (sample.clHold & kClL) ? 255 : 0; sample.clTriggerR = (sample.clHold & kClR) ? 255 : 0;