From dc2ddc6e998a805c8626a354ad2e2dc027d9766e Mon Sep 17 00:00:00 2001 From: honjow Date: Fri, 20 Mar 2026 01:37:03 +0800 Subject: [PATCH 1/4] feat(CapabilityMap): add generic hidraw button driver --- src/config/capability_map/hidraw.rs | 9 +- src/input/event/hidraw.rs | 1 + src/input/event/hidraw/translator.rs | 132 +++++++++++++++++++++ src/input/event/mod.rs | 1 + src/input/source/hidraw.rs | 67 ++++++++++- src/input/source/hidraw/generic_buttons.rs | 80 +++++++++++++ 6 files changed, 282 insertions(+), 8 deletions(-) create mode 100644 src/input/event/hidraw.rs create mode 100644 src/input/event/hidraw/translator.rs create mode 100644 src/input/source/hidraw/generic_buttons.rs diff --git a/src/config/capability_map/hidraw.rs b/src/config/capability_map/hidraw.rs index 3d680b30..748b6ba2 100644 --- a/src/config/capability_map/hidraw.rs +++ b/src/config/capability_map/hidraw.rs @@ -6,8 +6,13 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize, Clone, JsonSchema, PartialEq)] #[serde(rename_all = "snake_case")] pub struct HidrawConfig { - pub report_id: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub report_id: Option, pub input_type: String, pub byte_start: u64, - pub bit_offset: u8, + /// Bit position within the byte (LSB=0). + #[serde(skip_serializing_if = "Option::is_none")] + pub bit_offset: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, } diff --git a/src/input/event/hidraw.rs b/src/input/event/hidraw.rs new file mode 100644 index 00000000..a73a1679 --- /dev/null +++ b/src/input/event/hidraw.rs @@ -0,0 +1 @@ +pub mod translator; diff --git a/src/input/event/hidraw/translator.rs b/src/input/event/hidraw/translator.rs new file mode 100644 index 00000000..b659a442 --- /dev/null +++ b/src/input/event/hidraw/translator.rs @@ -0,0 +1,132 @@ +use crate::{ + config::capability_map::CapabilityMapConfigV2, + input::{ + capability::Capability, + event::{native::NativeEvent, value::InputValue}, + }, +}; + +#[derive(Debug, Clone)] +struct HidrawButtonMapping { + report_id: Option, + byte_index: usize, + detection: DetectionMode, + capability: Capability, +} + +#[derive(Debug, Clone)] +enum DetectionMode { + NonZero, + Value(u8), + /// Bit position (LSB=0) + Bit(u8), +} + +/// Translates raw HID reports into [NativeEvent]s using a capability map. +#[derive(Debug)] +pub struct HidrawEventTranslator { + mappings: Vec, + state: Vec, +} + +impl HidrawEventTranslator { + /// Create a new translator from a V2 capability map. + pub fn new(capability_map: &CapabilityMapConfigV2) -> Self { + let mut mappings = Vec::new(); + + for mapping in capability_map.mapping.iter() { + for source in mapping.source_events.iter() { + let Some(hidraw) = source.hidraw.as_ref() else { + continue; + }; + + if hidraw.input_type != "button" { + log::warn!( + "Unsupported hidraw input_type '{}' in mapping '{}', skipping", + hidraw.input_type, + mapping.name, + ); + continue; + } + + let cap: Capability = mapping.target_event.clone().into(); + if cap == Capability::NotImplemented { + log::warn!( + "Unresolved target capability in mapping '{}', skipping", + mapping.name, + ); + continue; + } + + let detection = if let Some(value) = hidraw.value { + DetectionMode::Value(value) + } else if let Some(bit) = hidraw.bit_offset { + DetectionMode::Bit(bit) + } else { + DetectionMode::NonZero + }; + + mappings.push(HidrawButtonMapping { + report_id: hidraw.report_id, + byte_index: hidraw.byte_start as usize, + detection, + capability: cap, + }); + } + } + + let state = vec![false; mappings.len()]; + Self { mappings, state } + } + + pub fn has_mappings(&self) -> bool { + !self.mappings.is_empty() + } + + pub fn capabilities(&self) -> Vec { + self.mappings.iter().map(|m| m.capability.clone()).collect() + } + + /// Translate a raw HID report into [NativeEvent]s. Only emits events on + /// state changes. + pub fn translate(&mut self, report: &[u8]) -> Vec { + let mut events = Vec::new(); + + for (idx, mapping) in self.mappings.iter().enumerate() { + if let Some(expected_id) = mapping.report_id { + if report.first().copied() != Some(expected_id) { + continue; + } + } + + if mapping.byte_index >= report.len() { + continue; + } + + let byte_val = report[mapping.byte_index]; + let pressed = match mapping.detection { + DetectionMode::NonZero => byte_val != 0, + DetectionMode::Value(expected) => byte_val == expected, + DetectionMode::Bit(bit) => (byte_val & (1 << bit)) != 0, + }; + + if pressed != self.state[idx] { + self.state[idx] = pressed; + events.push(NativeEvent::new( + mapping.capability.clone(), + InputValue::Bool(pressed), + )); + } + } + + events + } + + /// Returns whether the capability map contains any hidraw source events. + pub fn has_hidraw_mappings(capability_map: &CapabilityMapConfigV2) -> bool { + capability_map + .mapping + .iter() + .any(|m| m.source_events.iter().any(|s| s.hidraw.is_some())) + } +} diff --git a/src/input/event/mod.rs b/src/input/event/mod.rs index 93b11064..8e237a1f 100644 --- a/src/input/event/mod.rs +++ b/src/input/event/mod.rs @@ -1,6 +1,7 @@ pub mod context; pub mod dbus; pub mod evdev; +pub mod hidraw; pub mod native; pub mod value; diff --git a/src/input/source/hidraw.rs b/src/input/source/hidraw.rs index 0358a375..f2a0b37b 100644 --- a/src/input/source/hidraw.rs +++ b/src/input/source/hidraw.rs @@ -2,6 +2,7 @@ pub mod blocked; pub mod dualsense; pub mod flydigi_vader_4_pro; pub mod fts3528; +pub mod generic_buttons; pub mod gpd_win_mini_touchpad; pub mod gpd_win_mini_macro_keyboard; pub mod horipad_steam; @@ -20,6 +21,7 @@ use std::{error::Error, time::Duration}; use blocked::BlockedHidrawDevice; use flydigi_vader_4_pro::Vader4Pro; +use generic_buttons::GenericHidrawButtons; use gpd_win_mini_touchpad::GpdWinMiniTouchpad; use gpd_win_mini_macro_keyboard::GpdWinMiniMacroKeyboard; use horipad_steam::HoripadSteam; @@ -31,12 +33,18 @@ use xpad_uhid::XpadUhid; use zotac_zone::ZotacZone; use crate::{ - config, + config::{ + self, + capability_map::{load_capability_mappings, CapabilityMapConfig, CapabilityMapConfigV2}, + }, constants::BUS_SOURCES_PREFIX, drivers, input::{ - capability::Capability, composite_device::client::CompositeDeviceClient, - info::DeviceInfoRef, output_capability::OutputCapability, + capability::Capability, + composite_device::client::CompositeDeviceClient, + event::hidraw::translator::HidrawEventTranslator, + info::DeviceInfoRef, + output_capability::OutputCapability, }, udev::device::UdevDevice, }; @@ -76,6 +84,7 @@ pub enum HidRawDevice { Blocked(SourceDriver), DualSense(SourceDriver), Fts3528Touchscreen(SourceDriver), + GenericButtons(SourceDriver), GpdWinMiniTouchpad(SourceDriver), GpdWinMiniMacroKeyboard(SourceDriver), HoripadSteam(SourceDriver), @@ -98,6 +107,7 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Blocked(source_driver) => source_driver.info_ref(), HidRawDevice::DualSense(source_driver) => source_driver.info_ref(), HidRawDevice::Fts3528Touchscreen(source_driver) => source_driver.info_ref(), + HidRawDevice::GenericButtons(source_driver) => source_driver.info_ref(), HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.info_ref(), HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => source_driver.info_ref(), HidRawDevice::HoripadSteam(source_driver) => source_driver.info_ref(), @@ -120,6 +130,7 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Blocked(source_driver) => source_driver.get_id(), HidRawDevice::DualSense(source_driver) => source_driver.get_id(), HidRawDevice::Fts3528Touchscreen(source_driver) => source_driver.get_id(), + HidRawDevice::GenericButtons(source_driver) => source_driver.get_id(), HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.get_id(), HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => source_driver.get_id(), HidRawDevice::HoripadSteam(source_driver) => source_driver.get_id(), @@ -142,6 +153,7 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Blocked(source_driver) => source_driver.client(), HidRawDevice::DualSense(source_driver) => source_driver.client(), HidRawDevice::Fts3528Touchscreen(source_driver) => source_driver.client(), + HidRawDevice::GenericButtons(source_driver) => source_driver.client(), HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.client(), HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => source_driver.client(), HidRawDevice::HoripadSteam(source_driver) => source_driver.client(), @@ -164,6 +176,7 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Blocked(source_driver) => source_driver.run().await, HidRawDevice::DualSense(source_driver) => source_driver.run().await, HidRawDevice::Fts3528Touchscreen(source_driver) => source_driver.run().await, + HidRawDevice::GenericButtons(source_driver) => source_driver.run().await, HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.run().await, HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => source_driver.run().await, HidRawDevice::HoripadSteam(source_driver) => source_driver.run().await, @@ -186,6 +199,7 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Blocked(source_driver) => source_driver.get_capabilities(), HidRawDevice::DualSense(source_driver) => source_driver.get_capabilities(), HidRawDevice::Fts3528Touchscreen(source_driver) => source_driver.get_capabilities(), + HidRawDevice::GenericButtons(source_driver) => source_driver.get_capabilities(), HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.get_capabilities(), HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => source_driver.get_capabilities(), HidRawDevice::HoripadSteam(source_driver) => source_driver.get_capabilities(), @@ -210,6 +224,9 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Fts3528Touchscreen(source_driver) => { source_driver.get_output_capabilities() } + HidRawDevice::GenericButtons(source_driver) => { + source_driver.get_output_capabilities() + } HidRawDevice::GpdWinMiniTouchpad(source_driver) => { source_driver.get_output_capabilities() } @@ -238,6 +255,7 @@ impl SourceDeviceCompatible for HidRawDevice { HidRawDevice::Blocked(source_driver) => source_driver.get_device_path(), HidRawDevice::DualSense(source_driver) => source_driver.get_device_path(), HidRawDevice::Fts3528Touchscreen(source_driver) => source_driver.get_device_path(), + HidRawDevice::GenericButtons(source_driver) => source_driver.get_device_path(), HidRawDevice::GpdWinMiniTouchpad(source_driver) => source_driver.get_device_path(), HidRawDevice::GpdWinMiniMacroKeyboard(source_driver) => source_driver.get_device_path(), HidRawDevice::HoripadSteam(source_driver) => source_driver.get_device_path(), @@ -269,7 +287,25 @@ impl HidRawDevice { let driver_type = HidRawDevice::get_driver_type(&device_info, is_blocked); match driver_type { - DriverType::Unknown => Err("No driver for hidraw interface found".into()), + DriverType::Unknown => { + if let Some(cap_map) = Self::load_hidraw_capability_map(&conf) { + log::info!( + "Using generic hidraw button driver with capability map '{}'", + cap_map.name, + ); + let device = GenericHidrawButtons::new(device_info.clone(), cap_map)?; + let source_device = + SourceDriver::new(composite_device, device, device_info.into(), conf); + return Ok(Self::GenericButtons(source_device)); + } + + let vid = device_info.id_vendor(); + let pid = device_info.id_product(); + Err(format!( + "No driver for hidraw interface found. VID: {vid:#06x}, PID: {pid:#06x}" + ) + .into()) + } DriverType::Blocked => { let options = SourceDriverOptions { poll_rate: Duration::from_millis(200), @@ -456,6 +492,26 @@ impl HidRawDevice { } } + fn load_hidraw_capability_map( + conf: &Option, + ) -> Option { + let cap_map_id = conf.as_ref()?.capability_map_id.as_ref()?; + let mappings = load_capability_mappings(); + let cap_map = match mappings.get(cap_map_id) { + Some(CapabilityMapConfig::V2(config)) => config.clone(), + _ => { + log::warn!("Capability map '{cap_map_id}' not found or not V2"); + return None; + } + }; + + if !HidrawEventTranslator::has_hidraw_mappings(&cap_map) { + return None; + } + + Some(cap_map) + } + /// Return the driver type for the given vendor and product fn get_driver_type(device: &UdevDevice, is_blocked: bool) -> DriverType { log::debug!("Finding driver for interface: {:?}", device); @@ -593,8 +649,7 @@ impl HidRawDevice { return DriverType::GpdWinMiniMacroKeyboard; } - // Unknown - log::warn!("No driver for hidraw interface found. VID: {vid}, PID: {pid}"); + log::debug!("No specialized hidraw driver for VID: {vid:#06x}, PID: {pid:#06x}"); DriverType::Unknown } } diff --git a/src/input/source/hidraw/generic_buttons.rs b/src/input/source/hidraw/generic_buttons.rs new file mode 100644 index 00000000..5f8d63d1 --- /dev/null +++ b/src/input/source/hidraw/generic_buttons.rs @@ -0,0 +1,80 @@ +use std::{error::Error, ffi::CString, fmt::Debug}; + +use hidapi::HidDevice; + +use crate::{ + config::capability_map::CapabilityMapConfigV2, + input::{ + capability::Capability, + event::{hidraw::translator::HidrawEventTranslator, native::NativeEvent}, + source::{InputError, SourceInputDevice, SourceOutputDevice}, + }, + udev::device::UdevDevice, +}; + +const HID_TIMEOUT: i32 = 10; +const READ_BUF_SIZE: usize = 64; + +/// Generic hidraw button source device driven by a capability map. +pub struct GenericHidrawButtons { + device: HidDevice, + translator: HidrawEventTranslator, +} + +impl GenericHidrawButtons { + pub fn new( + device_info: UdevDevice, + capability_map: CapabilityMapConfigV2, + ) -> Result> { + let path = device_info.devnode(); + let cs_path = CString::new(path.clone())?; + let api = hidapi::HidApi::new()?; + let device = api.open_path(&cs_path)?; + device.set_blocking_mode(false)?; + + let translator = HidrawEventTranslator::new(&capability_map); + if !translator.has_mappings() { + return Err(format!( + "Capability map '{}' has no hidraw button mappings", + capability_map.name + ) + .into()); + } + + log::info!( + "Opened generic hidraw button device at {path} with {} mapping(s)", + translator.capabilities().len(), + ); + + Ok(Self { device, translator }) + } +} + +impl SourceInputDevice for GenericHidrawButtons { + fn poll(&mut self) -> Result, InputError> { + let mut buf = [0u8; READ_BUF_SIZE]; + let bytes_read = self + .device + .read_timeout(&mut buf[..], HID_TIMEOUT) + .map_err(|e| InputError::DeviceError(e.to_string()))?; + + if bytes_read == 0 { + return Ok(vec![]); + } + + let events = self.translator.translate(&buf[..bytes_read]); + Ok(events) + } + + fn get_capabilities(&self) -> Result, InputError> { + Ok(self.translator.capabilities()) + } +} + +impl SourceOutputDevice for GenericHidrawButtons {} + +impl Debug for GenericHidrawButtons { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GenericHidrawButtons").finish() + } +} From 0a0613dc5547618452fd249c18726a81f3a5c90e Mon Sep 17 00:00:00 2001 From: honjow Date: Fri, 20 Mar 2026 01:37:03 +0800 Subject: [PATCH 2/4] feat(Hardware Support): add GPD Win 5 HID button support for new firmware --- .../capability_maps/gpd_win5_hid.yaml | 37 +++++++++++++++++++ .../inputplumber/devices/50-gpd_win5.yaml | 7 ++++ 2 files changed, 44 insertions(+) create mode 100644 rootfs/usr/share/inputplumber/capability_maps/gpd_win5_hid.yaml diff --git a/rootfs/usr/share/inputplumber/capability_maps/gpd_win5_hid.yaml b/rootfs/usr/share/inputplumber/capability_maps/gpd_win5_hid.yaml new file mode 100644 index 00000000..1df61a5a --- /dev/null +++ b/rootfs/usr/share/inputplumber/capability_maps/gpd_win5_hid.yaml @@ -0,0 +1,37 @@ +version: 2 +kind: CapabilityMap +name: GPD Win 5 HID Buttons +id: gpd_win5_hid + +# GPD Win 5 vendor HID report (VID 0x2f24, PID 0x0137, Usage Page 0xFF00) +# Idle: 01 a5 00 5a ff 00 01 09 00 00 00 00 +# BUF[8] = 0x68 mode switch, 0x00 released +# BUF[9] = 0x69 left back, 0x00 released +# BUF[10] = 0x6a right back, 0x00 released +mapping: + - name: Mode Switch + source_events: + - hidraw: + input_type: button + byte_start: 8 + target_event: + gamepad: + button: QuickAccess + + - name: Left Back + source_events: + - hidraw: + input_type: button + byte_start: 9 + target_event: + gamepad: + button: LeftPaddle1 + + - name: Right Back + source_events: + - hidraw: + input_type: button + byte_start: 10 + target_event: + gamepad: + button: RightPaddle1 diff --git a/rootfs/usr/share/inputplumber/devices/50-gpd_win5.yaml b/rootfs/usr/share/inputplumber/devices/50-gpd_win5.yaml index e5949af3..f3aac9d2 100644 --- a/rootfs/usr/share/inputplumber/devices/50-gpd_win5.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-gpd_win5.yaml @@ -38,6 +38,13 @@ source_devices: name: " Keyboard for Windows" handler: event* phys_path: usb-0000:66:00.0-5.3/input0 + # New firmware: back buttons via vendor HID report + - group: keyboard + hidraw: + vendor_id: 0x2f24 + product_id: 0x0137 + interface_num: 0 + capability_map_id: gpd_win5_hid - group: keyboard evdev: name: AT Translated Set 2 keyboard From 3152835ac4878ef5fdb6da49900e31c0ce18c50a Mon Sep 17 00:00:00 2001 From: honjow Date: Thu, 26 Mar 2026 17:22:37 +0800 Subject: [PATCH 3/4] refactor(hidraw): simplify generic button driver and translator API --- src/input/event/hidraw/translator.rs | 31 ++++++++++------------ src/input/source/hidraw.rs | 21 ++++----------- src/input/source/hidraw/generic_buttons.rs | 2 +- 3 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/input/event/hidraw/translator.rs b/src/input/event/hidraw/translator.rs index b659a442..3ea6e76c 100644 --- a/src/input/event/hidraw/translator.rs +++ b/src/input/event/hidraw/translator.rs @@ -25,14 +25,14 @@ enum DetectionMode { /// Translates raw HID reports into [NativeEvent]s using a capability map. #[derive(Debug)] pub struct HidrawEventTranslator { - mappings: Vec, + source_events: Vec, state: Vec, } impl HidrawEventTranslator { /// Create a new translator from a V2 capability map. pub fn new(capability_map: &CapabilityMapConfigV2) -> Self { - let mut mappings = Vec::new(); + let mut source_events = Vec::new(); for mapping in capability_map.mapping.iter() { for source in mapping.source_events.iter() { @@ -66,7 +66,7 @@ impl HidrawEventTranslator { DetectionMode::NonZero }; - mappings.push(HidrawButtonMapping { + source_events.push(HidrawButtonMapping { report_id: hidraw.report_id, byte_index: hidraw.byte_start as usize, detection, @@ -75,16 +75,16 @@ impl HidrawEventTranslator { } } - let state = vec![false; mappings.len()]; - Self { mappings, state } + let state = vec![false; source_events.len()]; + Self { source_events, state } } - pub fn has_mappings(&self) -> bool { - !self.mappings.is_empty() + pub fn has_hid_translation(&self) -> bool { + !self.source_events.is_empty() } pub fn capabilities(&self) -> Vec { - self.mappings.iter().map(|m| m.capability.clone()).collect() + self.source_events.iter().map(|m| m.capability.clone()).collect() } /// Translate a raw HID report into [NativeEvent]s. Only emits events on @@ -92,7 +92,7 @@ impl HidrawEventTranslator { pub fn translate(&mut self, report: &[u8]) -> Vec { let mut events = Vec::new(); - for (idx, mapping) in self.mappings.iter().enumerate() { + for (idx, mapping) in self.source_events.iter().enumerate() { if let Some(expected_id) = mapping.report_id { if report.first().copied() != Some(expected_id) { continue; @@ -100,6 +100,11 @@ impl HidrawEventTranslator { } if mapping.byte_index >= report.len() { + log::warn!( + "HID report too short for mapping at byte {}: got {} bytes", + mapping.byte_index, + report.len(), + ); continue; } @@ -121,12 +126,4 @@ impl HidrawEventTranslator { events } - - /// Returns whether the capability map contains any hidraw source events. - pub fn has_hidraw_mappings(capability_map: &CapabilityMapConfigV2) -> bool { - capability_map - .mapping - .iter() - .any(|m| m.source_events.iter().any(|s| s.hidraw.is_some())) - } } diff --git a/src/input/source/hidraw.rs b/src/input/source/hidraw.rs index f2a0b37b..a26231c9 100644 --- a/src/input/source/hidraw.rs +++ b/src/input/source/hidraw.rs @@ -42,7 +42,6 @@ use crate::{ input::{ capability::Capability, composite_device::client::CompositeDeviceClient, - event::hidraw::translator::HidrawEventTranslator, info::DeviceInfoRef, output_capability::OutputCapability, }, @@ -288,11 +287,7 @@ impl HidRawDevice { match driver_type { DriverType::Unknown => { - if let Some(cap_map) = Self::load_hidraw_capability_map(&conf) { - log::info!( - "Using generic hidraw button driver with capability map '{}'", - cap_map.name, - ); + if let Some(cap_map) = Self::load_capability_map_v2(&conf) { let device = GenericHidrawButtons::new(device_info.clone(), cap_map)?; let source_device = SourceDriver::new(composite_device, device, device_info.into(), conf); @@ -492,24 +487,18 @@ impl HidRawDevice { } } - fn load_hidraw_capability_map( + fn load_capability_map_v2( conf: &Option, ) -> Option { let cap_map_id = conf.as_ref()?.capability_map_id.as_ref()?; let mappings = load_capability_mappings(); - let cap_map = match mappings.get(cap_map_id) { - Some(CapabilityMapConfig::V2(config)) => config.clone(), + match mappings.get(cap_map_id) { + Some(CapabilityMapConfig::V2(config)) => Some(config.clone()), _ => { log::warn!("Capability map '{cap_map_id}' not found or not V2"); - return None; + None } - }; - - if !HidrawEventTranslator::has_hidraw_mappings(&cap_map) { - return None; } - - Some(cap_map) } /// Return the driver type for the given vendor and product diff --git a/src/input/source/hidraw/generic_buttons.rs b/src/input/source/hidraw/generic_buttons.rs index 5f8d63d1..d3f16e9e 100644 --- a/src/input/source/hidraw/generic_buttons.rs +++ b/src/input/source/hidraw/generic_buttons.rs @@ -33,7 +33,7 @@ impl GenericHidrawButtons { device.set_blocking_mode(false)?; let translator = HidrawEventTranslator::new(&capability_map); - if !translator.has_mappings() { + if !translator.has_hid_translation() { return Err(format!( "Capability map '{}' has no hidraw button mappings", capability_map.name From 401830a9c09df50be7adbb25adf648129a5e7936 Mon Sep 17 00:00:00 2001 From: honjow Date: Mon, 30 Mar 2026 09:36:35 +0800 Subject: [PATCH 4/4] feat(Hardware Support): rename GPD Win 5 HID capability map and update schema --- .../{gpd_win5_hid.yaml => gpd_v2_hid1.yaml} | 11 +++++-- .../inputplumber/devices/50-gpd_win5.yaml | 2 +- .../schema/capability_map_v2.json | 29 ++++++++++++++----- 3 files changed, 32 insertions(+), 10 deletions(-) rename rootfs/usr/share/inputplumber/capability_maps/{gpd_win5_hid.yaml => gpd_v2_hid1.yaml} (73%) diff --git a/rootfs/usr/share/inputplumber/capability_maps/gpd_win5_hid.yaml b/rootfs/usr/share/inputplumber/capability_maps/gpd_v2_hid1.yaml similarity index 73% rename from rootfs/usr/share/inputplumber/capability_maps/gpd_win5_hid.yaml rename to rootfs/usr/share/inputplumber/capability_maps/gpd_v2_hid1.yaml index 1df61a5a..28d0a674 100644 --- a/rootfs/usr/share/inputplumber/capability_maps/gpd_win5_hid.yaml +++ b/rootfs/usr/share/inputplumber/capability_maps/gpd_v2_hid1.yaml @@ -1,7 +1,14 @@ +# 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: GPD Win 5 HID Buttons -id: gpd_win5_hid + +# Name for the device event map +name: GPD HID Type 1 + +id: gpd_v2_hid1 # GPD Win 5 vendor HID report (VID 0x2f24, PID 0x0137, Usage Page 0xFF00) # Idle: 01 a5 00 5a ff 00 01 09 00 00 00 00 diff --git a/rootfs/usr/share/inputplumber/devices/50-gpd_win5.yaml b/rootfs/usr/share/inputplumber/devices/50-gpd_win5.yaml index f3aac9d2..8bd064d5 100644 --- a/rootfs/usr/share/inputplumber/devices/50-gpd_win5.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-gpd_win5.yaml @@ -44,7 +44,7 @@ source_devices: vendor_id: 0x2f24 product_id: 0x0137 interface_num: 0 - capability_map_id: gpd_win5_hid + capability_map_id: gpd_v2_hid1 - group: keyboard evdev: name: AT Translated Set 2 keyboard diff --git a/rootfs/usr/share/inputplumber/schema/capability_map_v2.json b/rootfs/usr/share/inputplumber/schema/capability_map_v2.json index 38d21393..0c2b7959 100644 --- a/rootfs/usr/share/inputplumber/schema/capability_map_v2.json +++ b/rootfs/usr/share/inputplumber/schema/capability_map_v2.json @@ -1191,7 +1191,11 @@ "type": "object", "properties": { "bit_offset": { - "type": "integer", + "description": "Bit position within the byte (LSB=0).", + "type": [ + "integer", + "null" + ], "format": "uint8", "maximum": 255, "minimum": 0 @@ -1205,16 +1209,27 @@ "type": "string" }, "report_id": { - "type": "integer", - "format": "uint32", + "type": [ + "integer", + "null" + ], + "format": "uint8", + "maximum": 255, + "minimum": 0 + }, + "value": { + "type": [ + "integer", + "null" + ], + "format": "uint8", + "maximum": 255, "minimum": 0 } }, "required": [ - "report_id", "input_type", - "byte_start", - "bit_offset" + "byte_start" ] }, "MappingType": { @@ -1446,4 +1461,4 @@ ] } } -} +} \ No newline at end of file