From 33d78910470f23c8fb76b016f16a22508fa41149 Mon Sep 17 00:00:00 2001 From: Spencer Nisonoff Date: Fri, 4 Sep 2026 11:41:09 -0700 Subject: [PATCH] fix(config): add Mayflash GameCube Controller Adapter (0079:1844) support This adapter's HID report descriptor declares its top-level collection as Usage(Joystick) [0x04] rather than Usage(Gamepad) [0x05], with its 16 face/shoulder buttons reported as a plain bitmask under the generic Button usage page. Because of this, the Linux kernel assigns the legacy joystick-era evdev codes BTN_TRIGGER (0x120) through BTN_DEAD (0x12f) to these buttons instead of the modern gamepad codes (BTN_SOUTH/0x130 etc.) that InputPlumber's generic evdev translation recognizes, so none of this adapter's face/shoulder buttons produced any input at all. Adds: - A capability map + device profile for this adapter, with the bit-to- button layout empirically confirmed on real hardware (adapter in PC/ DInput mode) by pressing each button in complete isolation and watching the translated output - not inferred from the original GameCube console protocol, which does not match this adapter's actual bit order. - Three new EventCode variants (evdev codes 0x12c-0x12e) which have no symbolic name in the Linux kernel headers but are legitimately reported by this class of hardware. - A test validating both new config files parse correctly and that capability_map_id is set on the source_devices entry itself, not just at the CompositeDeviceConfig level - only the former actually wires up per-event translation at runtime. Co-developed-by: Claude Sonnet 5 AI disclosure: this fix was developed interactively with Claude Code (Sonnet 5) over an extended debugging session. The root-cause diagnosis (HID Joystick vs Gamepad usage causing legacy evdev codes) was Claude's own investigation of the InputPlumber source. The bit-to-button mapping in the capability map was NOT AI-inferred - it was determined empirically by the human author pressing each physical button on real hardware in isolation while Claude captured and reported the resulting translated evdev output; several early AI-generated assumptions about the bit layout (based on the original GameCube console protocol) turned out to be wrong and were corrected only after the human author pushed back and asked for further isolated testing. The three new EventCode enum variants and the accompanying test were written by Claude and reviewed by the human author. --- .../mayflash_gamecube_adapter.yaml | 182 ++++++++++++++++++ .../devices/55-mayflash_gamecube_adapter.yaml | 69 +++++++ src/config/capability_map/evdev.rs | 14 ++ src/config/config_test.rs | 47 +++++ 4 files changed, 312 insertions(+) create mode 100644 rootfs/usr/share/inputplumber/capability_maps/mayflash_gamecube_adapter.yaml create mode 100644 rootfs/usr/share/inputplumber/devices/55-mayflash_gamecube_adapter.yaml diff --git a/rootfs/usr/share/inputplumber/capability_maps/mayflash_gamecube_adapter.yaml b/rootfs/usr/share/inputplumber/capability_maps/mayflash_gamecube_adapter.yaml new file mode 100644 index 00000000..774f267c --- /dev/null +++ b/rootfs/usr/share/inputplumber/capability_maps/mayflash_gamecube_adapter.yaml @@ -0,0 +1,182 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/ShadowBlip/InputPlumber/main/rootfs/usr/share/inputplumber/schema/capability_map_v2.json +# Schema version number +version: 2 + +# The type of configuration schema +kind: CapabilityMap + +# Name for the device event map +name: Mayflash GameCube Controller Adapter + +# Unique identifier of the capability mapping +id: mayflash_gamecube_adapter + +# Root cause: this adapter's HID report descriptor declares its top-level +# collection as Usage(Joystick) [0x04] rather than Usage(Gamepad) [0x05], with +# its 16 face/shoulder buttons reported under the generic Button usage page +# (0x09, usages 1-16) as a single 16-bit bitmask - not under a labeled gamepad +# usage. Because of this, Linux's kernel HID input core (drivers/hid/hid-input.c) +# assigns the legacy joystick-era evdev codes BTN_TRIGGER (0x120) through +# BTN_DEAD (0x12f) sequentially to these 16 buttons, instead of the modern +# gamepad codes BTN_SOUTH (0x130) etc. that InputPlumber's generic evdev +# translation (src/input/event/evdev.rs) recognizes. Every one of these codes +# currently falls through to the catch-all `_ => Capability::NotImplemented` +# arm, so none of this adapter's face/shoulder buttons produce any usable +# input via the generic gamepad profile. +# +# Bit-to-button mapping below is EMPIRICALLY CONFIRMED against real hardware +# (adapter set to PC/DInput mode - it also has separate Wii U and Pokken +# Tournament modes, selected via button combos, which likely use a +# completely different report layout; this mapping is PC-mode-specific), one +# physical button pressed in complete isolation at a time while watching the +# translated xb360 target device's raw evdev output. +# +# An earlier version of this file assumed the raw bit order followed the +# original Nintendo console-to-controller protocol layout naively (A/B/X/Y +# in that literal order at bits 0-3, Z/R-click/L-click at bits 12-14, and +# Start unambiguous at bit 4). That assumption was WRONG on multiple counts, +# each caught by isolated single-button testing after initial batch tests +# gave misleading results: +# - A/B/X/Y are NOT in that order on the raw bitmask (confirmed by +# isolated presses of each one individually - this actually matches an +# initially-doubted community SDL gamecontrollerdb.txt entry for this +# same adapter, which turned out to be correct after all). +# - Z/R-click/L-click are NOT at bits 12-14 at all - those bits never +# fire no matter what is pressed. +# - Start is NOT at bit 4 - an earlier version of this file concluded +# bit 4 was "shared" between Start and L's digital click because every +# "isolated" Start test up to that point still triggered bit 4, but +# that was because pressing the real Start button on this particular +# unit never actually produced any signal at all through THAT bit - +# what fired was consistently L (confirmed via L's own analog trigger +# axis ramping in lockstep with the bit 4 signal). The real Start +# button was eventually found on bit 9, confirmed by watching the +# DPadDown-diagnostic-mapped ABS_HAT0Y axis move in the positive +# direction exactly when Start (and only Start) was pressed. Once bit 9 +# was identified as the true Start bit, bit 4 became unambiguously just +# L's digital click, free to map to LeftBumper like R does to +# RightBumper - there was never a real hardware bit-sharing limitation, +# just a mapping bug in an earlier version of this file. +# +# Complete confirmed bit layout (all 16 bits tested, either directly +# identified or confirmed to produce no signal for anything pressed): +# +# bit 0 (BTN_TRIGGER) = X bit 8 (BTN_BASE3) = unused +# bit 1 (BTN_THUMB) = A bit 9 (BTN_BASE4) = Start +# bit 2 (BTN_THUMB2) = B bit 10 (BTN_BASE5) = unused +# bit 3 (BTN_TOP) = Y bit 11 (BTN_BASE6) = unused +# bit 4 (BTN_TOP2) = L (click) bit 12 (BTN_EXTRA_ONE) = unused +# bit 5 (BTN_PINKIE) = R (click) bit 13 (BTN_EXTRA_TWO) = unused +# bit 6 (BTN_BASE) = unused bit 14 (BTN_EXTRA_THR) = unused +# bit 7 (BTN_BASE2) = Z bit 15 (BTN_DEAD) = unused +# +# L and R are analog triggers (evdev ABS_RX/ABS_RY on this adapter, not the +# more conventional ABS_Z/ABS_RZ) with a mechanical digital click only at +# full travel - a light press produces analog motion only, no KEY event. +# Confirmed by watching each trigger's own axis ramp to max in lockstep +# with its click firing right at the top and releasing right as it comes +# back down. +# +# GameCube button -> InputPlumber's South/East/North/West follows the usual +# cross-platform convention of matching by *function* (South = primary +# confirm, East = secondary/cancel, etc.), not by exact physical diamond +# position, since GameCube's face buttons aren't laid out symmetrically like +# an Xbox pad. Z has no clean Xbox-pad equivalent, so it's mapped to Select +# (unclaimed on this profile's "xb360" target model and present on every +# xb360 model, unlike RightPaddle1/2 which are xbox-elite-only and were +# confirmed to silently produce no output when targeted here). +mapping: + # Physical GameCube A (confirmed via isolated press) is raw bit 1. + - name: South Button (GameCube A) + source_events: + - evdev: + event_type: KEY + event_code: BTN_THUMB + value_type: button + target_event: + gamepad: + button: South + + # Physical GameCube B (confirmed via isolated press) is raw bit 2. + - name: East Button (GameCube B) + source_events: + - evdev: + event_type: KEY + event_code: BTN_THUMB2 + value_type: button + target_event: + gamepad: + button: East + + # Physical GameCube X (confirmed via isolated press) is raw bit 0. + - name: West Button (GameCube X) + source_events: + - evdev: + event_type: KEY + event_code: BTN_TRIGGER + value_type: button + target_event: + gamepad: + button: West + + # Physical GameCube Y (confirmed via isolated press) is raw bit 3. + - name: North Button (GameCube Y) + source_events: + - evdev: + event_type: KEY + event_code: BTN_TOP + value_type: button + target_event: + gamepad: + button: North + + # Confirmed via isolated press to be raw bit 9 - see the detailed + # investigation notes above for why this took multiple rounds to find. + - name: Start Button (GameCube Start) + source_events: + - evdev: + event_type: KEY + event_code: BTN_BASE4 + value_type: button + target_event: + gamepad: + button: Start + + # L's digital click, raw bit 4 - confirmed via axis-correlated isolated + # press to be exclusively L, not shared with anything else. + - name: Left Bumper (GameCube L digital click) + source_events: + - evdev: + event_type: KEY + event_code: BTN_TOP2 + value_type: button + target_event: + gamepad: + button: LeftBumper + + # R's digital click, raw bit 5 - confirmed via axis-correlated isolated + # press, no ambiguity. + - name: Right Bumper (GameCube R digital click) + source_events: + - evdev: + event_type: KEY + event_code: BTN_PINKIE + value_type: button + target_event: + gamepad: + button: RightBumper + + # Physical GameCube Z (confirmed via isolated press) is raw bit 7. + - name: Z Button (GameCube Z) + source_events: + - evdev: + event_type: KEY + event_code: BTN_BASE2 + value_type: button + target_event: + gamepad: + button: Select + + # Bits 6, 8, 10-15 confirmed to produce no signal for any button pressed + # during testing - left unmapped. The D-pad is already handled correctly + # via the normal ABS_HAT0X/Y translation path, unrelated to this bitmask. diff --git a/rootfs/usr/share/inputplumber/devices/55-mayflash_gamecube_adapter.yaml b/rootfs/usr/share/inputplumber/devices/55-mayflash_gamecube_adapter.yaml new file mode 100644 index 00000000..b17a7fe6 --- /dev/null +++ b/rootfs/usr/share/inputplumber/devices/55-mayflash_gamecube_adapter.yaml @@ -0,0 +1,69 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/ShadowBlip/InputPlumber/main/rootfs/usr/share/inputplumber/schema/composite_device_v1.json +# Schema version number +version: 1 + +# The type of configuration schema +kind: CompositeDevice + +# Name of the composite device mapping +name: Mayflash GameCube Controller Adapter + +# This adapter's HID report descriptor declares its top-level collection as +# Usage(Joystick) [0x04] rather than Usage(Gamepad) [0x05], with its 16 +# face/shoulder buttons reported under the generic Button usage page as a +# plain bitmask rather than a labeled gamepad usage. Because of this, the +# Linux kernel assigns the legacy joystick-era evdev codes BTN_TRIGGER +# (0x120) through BTN_DEAD (0x12f) to these buttons instead of the modern +# gamepad codes (BTN_SOUTH/0x130 etc.) that the 65-generic_gamepad.yaml +# fallback profile (and InputPlumber's generic evdev translation) expect - +# so without this profile, none of this adapter's face/shoulder buttons +# produce usable input. See capability_maps/mayflash_gamecube_adapter.yaml +# for the full writeup and the specific button mapping, which is applied +# below via capability_map_id. +# +# Note: 60-xbox_360_gamepad.yaml already lists a vendor/product entry for +# "0079"/"18d4", which does not match this adapter's actual reported product +# ID "1844" (this looks like a transposed-digit typo in that file, unrelated +# to this profile) - even if that typo were fixed, matching the xbox_360 +# profile alone would not solve this, since the actual problem is the +# evdev button-code translation, not device selection. +# Empty: this profile is selected purely by the vendor_id/product_id match +# on the source_devices entry below, not by any system-level match (like +# DMI data for handheld-specific profiles). +matches: [] + +# Maximum number of source devices per CompositeDevice. Each of this +# adapter's 4 ports enumerates as its own separate evdev device with the +# same vendor/product ID; each gets its own CompositeDevice. +maximum_sources: 1 + +# NOTE: capability_map_id exists as BOTH a CompositeDeviceConfig-level field +# and a SourceDevice-level field (src/config/mod.rs) - they are NOT the same +# mechanism. The top-level one only affects capability *declaration* +# bookkeeping for legacy V1 chord-style maps (composite_device/mod.rs +# load_capability_map / device.capabilities); it does not translate anything. +# The ACTUAL per-event translation (raw evdev KEY -> Capability, via +# EventTranslator) is wired only from the field on each source_devices[] +# entry below (src/input/source/evdev.rs EventDevice::new, which builds the +# GamepadEventDevice's EventTranslator from THIS field, not the top-level +# one). Setting only the top-level field (as an earlier version of this +# profile did) parses fine and loads a CompositeDevice successfully, but +# silently never translates any button - confirmed live: axis/D-pad events +# passed through correctly while all face-button presses produced zero +# output events. Both fields are set below to be safe. +capability_map_id: mayflash_gamecube_adapter + +source_devices: + - group: gamepad + unique: true + capability_map_id: mayflash_gamecube_adapter + evdev: + vendor_id: "0079" + product_id: "1844" + handler: event* + +# The target input device(s) to emulate by default +target_devices: + - xb360 + - mouse + - keyboard diff --git a/src/config/capability_map/evdev.rs b/src/config/capability_map/evdev.rs index 12a7435d..636cf054 100644 --- a/src/config/capability_map/evdev.rs +++ b/src/config/capability_map/evdev.rs @@ -362,6 +362,17 @@ pub enum EventCode { BtnBase4, BtnBase5, BtnBase6, + /// evdev code 0x12c. No symbolic name exists in the Linux kernel headers + /// (linux/input-event-codes.h jumps from BTN_BASE6/0x12b straight to + /// BTN_DEAD/0x12f), but the code itself is a valid, distinct evdev key + /// code that some hardware (e.g. HID joysticks whose report descriptor + /// declares a generic 16+ button Button usage page under a Joystick + /// rather than Gamepad top-level collection) legitimately reports. + BtnExtraOne, + /// evdev code 0x12d. See [EventCode::BtnExtraOne]. + BtnExtraTwo, + /// evdev code 0x12e. See [EventCode::BtnExtraOne]. + BtnExtraThree, BtnDead, BtnGamepad, BtnSouth, @@ -1090,6 +1101,9 @@ impl EventCode { EventCode::BtnBase4 => 0x129, EventCode::BtnBase5 => 0x12a, EventCode::BtnBase6 => 0x12b, + EventCode::BtnExtraOne => 0x12c, + EventCode::BtnExtraTwo => 0x12d, + EventCode::BtnExtraThree => 0x12e, EventCode::BtnDead => 0x12f, EventCode::BtnGamepad => 0x130, EventCode::BtnSouth => 0x130, diff --git a/src/config/config_test.rs b/src/config/config_test.rs index 80ca3937..c28d82e9 100644 --- a/src/config/config_test.rs +++ b/src/config/config_test.rs @@ -145,3 +145,50 @@ async fn check_autostart_rules() -> Result<(), Box> { Ok(()) } + +/// Quick validation test for the new Mayflash GameCube Controller Adapter +/// capability map and device profile added to fix legacy joystick-range +/// evdev button codes (BTN_TRIGGER/0x120 through BTN_DEAD/0x12f) not being +/// recognized as gamepad buttons. +#[test] +fn check_mayflash_gamecube_adapter_configs_parse() { + use crate::config::capability_map::CapabilityMapConfig; + + let map = CapabilityMapConfig::from_yaml_file( + "./rootfs/usr/share/inputplumber/capability_maps/mayflash_gamecube_adapter.yaml", + ) + .expect("capability map should parse"); + assert_eq!(map.id(), "mayflash_gamecube_adapter"); + + let device_config = CompositeDeviceConfig::from_yaml_file( + "./rootfs/usr/share/inputplumber/devices/55-mayflash_gamecube_adapter.yaml".to_string(), + ) + .expect("device profile should parse"); + assert_eq!(device_config.name, "Mayflash GameCube Controller Adapter"); + assert_eq!( + device_config.capability_map_id.as_deref(), + Some("mayflash_gamecube_adapter"), + "capability_map_id must be set at the CompositeDeviceConfig level to actually apply" + ); + assert_eq!(device_config.source_devices.len(), 1); + let evdev = device_config.source_devices[0] + .evdev + .as_ref() + .expect("source device should have an evdev matcher"); + assert_eq!(evdev.vendor_id.as_deref(), Some("0079")); + assert_eq!(evdev.product_id.as_deref(), Some("1844")); + // Setting capability_map_id only at the CompositeDeviceConfig level (as + // asserted above) is NOT sufficient to actually translate events - only + // the SourceDevice-level field wires up the EventTranslator that + // performs real-time evdev KEY -> Capability translation + // (src/input/source/evdev.rs EventDevice::new). Confirmed live: without + // this field set here, axis/D-pad events passed through fine but every + // face-button press produced zero output events. + assert_eq!( + device_config.source_devices[0].capability_map_id.as_deref(), + Some("mayflash_gamecube_adapter"), + "capability_map_id must ALSO be set on the source_devices entry itself \ + (not just at the CompositeDeviceConfig level) or button translation \ + silently does nothing at runtime" + ); +}