Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions rootfs/usr/share/inputplumber/capability_maps/gpd_win5_hid.yaml
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions rootfs/usr/share/inputplumber/devices/50-gpd_win5.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions src/config/capability_map/hidraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>,
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<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<u8>,
Comment on lines +16 to +17

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this field in decoding hidraw data?

}
1 change: 1 addition & 0 deletions src/input/event/hidraw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod translator;
132 changes: 132 additions & 0 deletions src/input/event/hidraw/translator.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
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<HidrawButtonMapping>,
state: Vec<bool>,
}

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() {
Comment thread
pastaq marked this conversation as resolved.
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;
}
Comment on lines +43 to +50

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this implementation only supports simple button decoding, it would be nice to add a TODO somewhere so we can ensure that we finish implementing decoding other types of input (e.g. axis info, gyro, etc.)


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 {
Comment thread
pastaq marked this conversation as resolved.
Outdated
!self.mappings.is_empty()
}

pub fn capabilities(&self) -> Vec<Capability> {
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<NativeEvent> {
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;
Comment thread
pastaq marked this conversation as resolved.
}

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()))
}
}
1 change: 1 addition & 0 deletions src/input/event/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod context;
pub mod dbus;
pub mod evdev;
pub mod hidraw;
pub mod native;
pub mod value;

Expand Down
67 changes: 61 additions & 6 deletions src/input/source/hidraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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,
};
Expand Down Expand Up @@ -76,6 +84,7 @@ pub enum HidRawDevice {
Blocked(SourceDriver<BlockedHidrawDevice>),
DualSense(SourceDriver<DualSenseController>),
Fts3528Touchscreen(SourceDriver<Fts3528Touchscreen>),
GenericButtons(SourceDriver<GenericHidrawButtons>),
GpdWinMiniTouchpad(SourceDriver<GpdWinMiniTouchpad>),
GpdWinMiniMacroKeyboard(SourceDriver<GpdWinMiniMacroKeyboard>),
HoripadSteam(SourceDriver<HoripadSteam>),
Expand All @@ -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(),
Expand All @@ -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(),
Expand All @@ -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(),
Expand All @@ -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,
Expand All @@ -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(),
Expand All @@ -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()
}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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) {
Comment thread
pastaq marked this conversation as resolved.
Outdated
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())
Comment on lines +299 to +302

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to further specify in the error here that no driver or capability map was found for this device.

}
DriverType::Blocked => {
let options = SourceDriverOptions {
poll_rate: Duration::from_millis(200),
Expand Down Expand Up @@ -456,6 +492,26 @@ impl HidRawDevice {
}
}

fn load_hidraw_capability_map(
conf: &Option<config::SourceDevice>,
) -> Option<CapabilityMapConfigV2> {
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);
Expand Down Expand Up @@ -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
}
}
Expand Down
Loading
Loading