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" + ); +}