feat: Switch-layout button remap for PDP wired Switch pads (Standard quirk + Direct button order) - #159
Merged
Merged
Conversation
emir-hasanbegovic
marked this pull request as ready for review
August 16, 2026 21:00
…mily) PDP's wired Switch controllers (0e6f:0180/0181/0184/0185/0187) declare their buttons in the Switch usage order (Y B A X L R ZL ZR Minus Plus L3 R3 Home Capture). On Standard, Generic.kl hands that row over as BUTTON_A..BUTTON_THUMBL, so physical A arrived as BUTTON_C and was dropped, R arrived as BUTTON_Z and was dropped, and every other button landed one label off. On Direct, the generic HID decoder assumed the western A B X Y order and scrambled the pad differently. Standard path: a QUIRK_SWITCH_LAYOUT bit, resolved per VID:PID and pushed through the existing quirk plumbing, swaps in a positional keycode table matching decodeSwitchProUsb. ZL/ZR drive the triggers through the ltFromKey/rtFromKey latch, and both key gates consult the device quirk before consuming an event. Direct path: classifyDevice now carries a ButtonOrder from the model catalog, the attach path stamps HidLayout.switchOrderButtons after the descriptor fetch, and decodeFromLayout applies the Switch-order button table with ZL/ZR folding into the trigger bytes. The five models join kImported so they get names while staying off the verified fast lane. 0e6f:0186 (Afterglow Wireless) is excluded on purpose: SDL classifies it as a Switch Pro protocol device whose USB port is charge-only. Hardware-verified on a Faceoff Wired Pro (0e6f:0180); the siblings follow SDL's SwitchInputOnlyController list.
…o the 1.0.2 notes CHANGELOG.md now reads for gamers instead of developers: the stale Unreleased backlog folds into the 1.0.1 initial-release entry (tagged 2026-07-25), Unreleased carries only what actually landed since then (Steam Controller support and the PDP wired Switch pad fix), and the wire-coordination marker becomes a plain "update Satellite too" note. The 10002 Play notes in all five locales gain the PDP fix and stay under Play's 500-character limit; check_play_metadata.py reports no errors.
emir-hasanbegovic
force-pushed
the
feat/switch-layout-hid-remap
branch
from
August 16, 2026 21:04
e5a1320 to
7986fc4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A PDP Faceoff Wired Pro Controller for Nintendo Switch (USB
0e6f:0180, hardware in hand) streams with its A button dead in Standard mode. Tracing the pad (live USB descriptor read plus the Android input chain) showed the whole button row is shifted; A is just the loudest casualty.These pads declare 14 buttons in the Switch usage order: Y B A X L R ZL ZR Minus Plus L3 R3 Home Capture.
Standard: the kernel maps button usages sequentially from
BTN_SOUTH, andGeneric.klturns that intoBUTTON_A..BUTTON_THUMBL. Physical A therefore arrives asKEYCODE_BUTTON_Cand physical R asKEYCODE_BUTTON_Z;keycodeToXusb()had no case for either, so both key gates dropped the events. Everything else landed one label off: Y acted as A, L as Y, ZL/ZR as bumpers, Minus/Plus as full-press triggers, stick clicks as Back/Start, Capture as L3, and Home vanished.Direct: the pad classifies as
GENERIC_HID_GAMEPADand its descriptor parses fine, butbuttonBit()assumes the western A B X Y order, which scrambles the pad differently (A acted as X, ZL/ZR as Back/Start, R3/Home/Capture dropped). So Direct was not a workaround either.Fix
One Switch-order layout applied on both paths, positional to match
decodeSwitchProUsb: south to A, east to B, west to X, north to Y, L/R to the bumpers, ZL/ZR to the triggers, Minus/Plus to Back/Start, L3/R3 to the stick clicks, Home to Guide. Capture stays unmapped because XUSB has no equivalent bit.Standard path
QUIRK_SWITCH_LAYOUTbit (0x04) ingamepad_input.handGamepadQuirks.kt;resolveGamepadQuirk()now takes VID and PID and the registry pushes both.applyKey()branches on the quirk intoswitchLayoutKeycodeToXusb(); ZL/ZR (arriving asBUTTON_L1/R1) drive the triggers through the existingltFromKey/rtFromKeylatch so a zero axis sample cannot clear a held trigger.gamepadKeyFilterandprocessGamepadKeyEvent) now look the device quirk up underg_devicesMtxbefore deciding whether a keycode is consumable, soBUTTON_C/BUTTON_Z/BUTTON_MODEare consumed only for Switch-layout devices. Non-quirked devices keep the exact previous gate behavior, including the pre-IME gate not special-casingBUTTON_7/8.Direct path
KnownDeviceandClassificationcarry aButtonOrder(defaultWESTERN).HidLayout.switchOrderButtonsafterfetchHidLayout(the parse resets the struct, pinned by a test).decodeFromLayout()usesswitchOrderButtonBit()when flagged, with button indices 6/7 folding intobLT/bRT.Model catalog
Five PDP models join
kImportedwithParser::GENERIC_HID_GAMEPADandButtonOrder::SWITCH: 0180 Faceoff Wired Pro, 0181 Faceoff Deluxe, 0184 Faceoff Deluxe+ Audio, 0185 Wired Fight Pad Pro, 0187 Rock Candy. They get card names and stay off the verified fast lane, so nothing auto-claims into Direct.0e6f:0186(Afterglow Wireless) is excluded on purpose: SDL classifies it as a Switch Pro protocol controller whose USB port is charge-only, so the wired Switch-order remap would be wrong for it. Source: SDL controller_list.h.The model set intentionally lives in two places (Kotlin quirk table for the framework path,
kImportedfor the direct path); each side pins its five PIDs in tests, and the exclusion of 0186 is tested on both sides too.Tests
Current-state pins (documenting the bug and the assumptions the fix rests on):
keycodeToXusbdropsBUTTON_C/BUTTON_Z/BUTTON_MODE;applyKeywithout the quirk ignores them.WESTERN; PDP Switch pads are not verified fast lane.New behavior:
switchLayoutKeycodeToXusbandswitchOrderButtonBit(face row, bumpers, Minus/Plus, stick clicks, Home, dpad, Capture unmapped).bLT/bRTon both paths, survive zero axis samples while held, and Minus/Plus do not touch the triggers.switchLayoutConsumesKey), quirk-bit cross-language pin (0x04 asserted in both gtest and JUnit), stray swap bits do not double-remap.decodeReportwith a Switch-order layout throughParserState.QUIRK_SWITCH_LAYOUT, 0186 and PDP Xbox PIDs resolve to none, Nintendo vendor behavior unchanged.Verification
:app:nativeTest).:app:ktlintCheck,:app:detekt,GamepadQuirksTestgreen; full unit-test source set compiles.app/src/main/cpp.:app:externalNativeBuildDebuggreen on arm64-v8a and x86_64.Notes for review
resolveGamepadQuirkcomment,decodeSwitchProUsb). If label mapping is preferred for these pads, it is a two-table change.g_devicesMtxslightly earlier (to read the quirk) and hold it across the same work as before; no new lock ordering.