@@ -5,6 +5,84 @@ pub const PIX_VID: u16 = 0x093A;
55pub const P274_REPORT_ID : u8 = 0x43 ;
66pub const P239_REPORT_ID : u8 = 0x42 ;
77
8+ // Standard HID Precision Touchpad (PTP) interface — every PTP-compliant touchpad
9+ // reports on this usage. Only haptic touchpads expose the feature reports below.
10+ const TOUCHPAD_USAGE_PAGE : u16 = 0x000D ; // Digitizers
11+ const TOUCHPAD_USAGE : u16 = 0x0005 ; // Touch Pad
12+
13+ // Haptic feedback intensity (HID Haptic page 0x0E, Usage 0x23 Intensity).
14+ // Descriptor says logical range 0..100, but the Boreas haptic firmware
15+ // only implements five steps: 0%, 25%, 50%, 75%, 100%.
16+ const HAPTIC_INTENSITY_REPORT_ID : u8 = 0x09 ;
17+ pub const HAPTIC_INTENSITY_LEVELS : [ u8 ; 5 ] = [ 0 , 25 , 50 , 75 , 100 ] ;
18+
19+ // Button press threshold / click force (HID Digitizer page 0x0D, Usage 0xB0).
20+ // 2-bit field, firmware accepts 1=Low, 2=Medium, 3=High.
21+ const CLICK_FORCE_REPORT_ID : u8 = 0x08 ;
22+
23+ #[ derive( Clone , Copy , Debug , PartialEq ) ]
24+ #[ repr( u8 ) ]
25+ pub enum ClickForce {
26+ Low = 1 ,
27+ Medium = 2 ,
28+ High = 3 ,
29+ }
30+
31+ /// Open the PTP HID interface of the touchpad. Note: every modern touchpad
32+ /// exposes this interface; only haptic touchpads respond to the feature
33+ /// reports used by `set_haptic_intensity` / `set_click_force`.
34+ fn open_haptic_touchpad ( ) -> Option < HidDevice > {
35+ let api = HidApi :: new ( ) . ok ( ) ?;
36+ for dev_info in api. device_list ( ) {
37+ if dev_info. usage_page ( ) != TOUCHPAD_USAGE_PAGE || dev_info. usage ( ) != TOUCHPAD_USAGE {
38+ continue ;
39+ }
40+ debug ! (
41+ " Touchpad candidate {:04X}:{:04X} (Usage Page {:04X}, Usage {:04X})" ,
42+ dev_info. vendor_id( ) ,
43+ dev_info. product_id( ) ,
44+ dev_info. usage_page( ) ,
45+ dev_info. usage( )
46+ ) ;
47+ if let Ok ( device) = dev_info. open_device ( & api) {
48+ return Some ( device) ;
49+ }
50+ }
51+ None
52+ }
53+
54+ // The firmware accepts SET_FEATURE for these reports but doesn't reply
55+ // to GET_FEATURE, so both controls are write-only.
56+
57+ fn hid_err ( message : impl Into < String > ) -> HidError {
58+ HidError :: HidApiError {
59+ message : message. into ( ) ,
60+ }
61+ }
62+
63+ pub fn set_haptic_intensity ( value : u8 ) -> Result < ( ) , HidError > {
64+ if !HAPTIC_INTENSITY_LEVELS . contains ( & value) {
65+ return Err ( hid_err ( format ! (
66+ "Haptic intensity must be one of: {:?}" ,
67+ HAPTIC_INTENSITY_LEVELS
68+ ) ) ) ;
69+ }
70+ let device =
71+ open_haptic_touchpad ( ) . ok_or_else ( || hid_err ( "Could not find a haptic touchpad" ) ) ?;
72+ let buf = [ HAPTIC_INTENSITY_REPORT_ID , value] ;
73+ debug ! ( " send_feature_report (haptic intensity) {:X?}" , buf) ;
74+ device. send_feature_report ( & buf)
75+ }
76+
77+ pub fn set_click_force ( force : ClickForce ) -> Result < ( ) , HidError > {
78+ let device =
79+ open_haptic_touchpad ( ) . ok_or_else ( || hid_err ( "Could not find a haptic touchpad" ) ) ?;
80+ // Field is 2 bits at the bottom of the report payload
81+ let buf = [ CLICK_FORCE_REPORT_ID , force as u8 ] ;
82+ debug ! ( " send_feature_report (click force) {:X?}" , buf) ;
83+ device. send_feature_report ( & buf)
84+ }
85+
886fn read_byte ( device : & HidDevice , report_id : u8 , addr : u8 ) -> Result < u8 , HidError > {
987 device. send_feature_report ( & [ report_id, addr, 0x10 , 0 ] ) ?;
1088
0 commit comments