Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
44 changes: 44 additions & 0 deletions rootfs/usr/share/inputplumber/capability_maps/gpd_v2_hid1.yaml
Comment thread
pastaq marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 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: 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
Comment thread
pastaq marked this conversation as resolved.
# 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_v2_hid1
- group: keyboard
evdev:
name: AT Translated Set 2 keyboard
Expand Down
29 changes: 22 additions & 7 deletions rootfs/usr/share/inputplumber/schema/capability_map_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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": {
Expand Down Expand Up @@ -1446,4 +1461,4 @@
]
}
}
}
}
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>,
}
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;
129 changes: 129 additions & 0 deletions src/input/event/hidraw/translator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
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 {
source_events: 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 source_events = 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;
}

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
};

source_events.push(HidrawButtonMapping {
report_id: hidraw.report_id,
byte_index: hidraw.byte_start as usize,
detection,
capability: cap,
});
}
}

let state = vec![false; source_events.len()];
Self { source_events, state }
}

pub fn has_hid_translation(&self) -> bool {
!self.source_events.is_empty()
}

pub fn capabilities(&self) -> Vec<Capability> {
self.source_events.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.source_events.iter().enumerate() {
if let Some(expected_id) = mapping.report_id {
if report.first().copied() != Some(expected_id) {
continue;
}
}

if mapping.byte_index >= report.len() {
log::warn!(
"HID report too short for mapping at byte {}: got {} bytes",
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
}
}
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
Loading
Loading