Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions aurora-main/lib/dolphin/pad/pad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ std::array<PADButtonMapping, PAD_BUTTON_COUNT> 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).
Expand Down Expand Up @@ -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<PADButtonMapping, PAD_BUTTON_COUNT>& 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;
Expand Down
6 changes: 6 additions & 0 deletions runtime/src/settings_overlay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,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<uint32_t>(g_controllerPort), &mappingCount);
if (mappings == nullptr || mappingCount != PAD_BUTTON_COUNT) {
Expand Down
237 changes: 200 additions & 37 deletions runtime/src/wii_remote_input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
#include <cmath>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <optional>
#include <string>
#include <system_error>
#include <unordered_map>
#include <unordered_set>

namespace WiiRemoteInput {
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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<SDL_JoystickID, std::string> 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;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// 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
Expand Down Expand Up @@ -553,6 +663,7 @@ void Poll() {
for (uint32_t port = 0; port < PAD_MAX_CONTROLLERS; ++port) {
(void)EffectiveKind(port);
}
WarnIfKernelDriverServing();
if (!g_wiiDriverEnabled) {
return;
}
Expand Down Expand Up @@ -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;
Expand All @@ -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<int>(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.
Expand All @@ -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<int>(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;
Expand All @@ -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<int>(port)))) {
statuses[port] = {};
statuses[port].err = PAD_ERR_NO_CONTROLLER;
}
Expand All @@ -699,6 +812,14 @@ int16_t ClassicStickRaw(Sint16 axis, bool invert) {
return static_cast<int16_t>(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<float>(axis) / 32767.0f;
if (invert) value = -value;
value = std::clamp(value, -1.0f, 1.0f);
return static_cast<int16_t>(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;
Expand All @@ -723,7 +844,7 @@ bool ReadKpadSample(uint32_t chan, KpadSample& sample) {
return false;
}
SDL_Gamepad* gamepad = SDL_GetGamepadFromPlayerIndex(static_cast<int>(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)) {
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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<float>(lx) / 32767.0f, -1.0f, 1.0f);
sample.clLStick[1] = std::clamp(-static_cast<float>(ly) / 32767.0f, -1.0f, 1.0f);
sample.clRStick[0] = std::clamp(static_cast<float>(rx) / 32767.0f, -1.0f, 1.0f);
sample.clRStick[1] = std::clamp(-static_cast<float>(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;
Expand Down
Loading