Support Linux Wii controller mappings

This commit is contained in:
patchzyy
2026-10-18 12:00:08 +02:00
parent 90589131b5
commit 5688b8b5b6
3 changed files with 62 additions and 9 deletions
+6 -3
View File
@@ -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).
@@ -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;
+6
View File
@@ -790,6 +790,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) {
+50 -6
View File
@@ -63,6 +63,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.
@@ -86,6 +88,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
@@ -346,6 +349,40 @@ 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;
}
// 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
@@ -464,6 +501,7 @@ void Poll() {
for (uint32_t port = 0; port < PAD_MAX_CONTROLLERS; ++port) {
(void)EffectiveKind(port);
}
WarnIfKernelDriverServing();
if (!g_wiiDriverEnabled) {
return;
}
@@ -518,11 +556,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;
@@ -674,8 +714,12 @@ 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;
// The kernel's evdev node has a regular SDL mapping, so it is read as a gamepad.
const bool mapped = IsKernelWiiNode(SDL_GetGamepadID(gamepad));
const auto cl = [&](SDL_GamepadButton button, uint32_t bit) {
if (mapped ? SDL_GetGamepadButton(gamepad, button) : SDL_GetJoystickButton(joystick, button)) {
sample.clHold |= bit;
}
};
cl(SDL_GAMEPAD_BUTTON_EAST, kClA);
cl(SDL_GAMEPAD_BUTTON_SOUTH, kClB);