Last active
December 17, 2024 14:19
-
-
Save advanceboy/7a105124e46cdc34ca81cfdfee936aa6 to your computer and use it in GitHub Desktop.
`rawinput-touchpad.rs` dump RawInput HID reports of touchpad
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Generated by Cargo | |
| # will have compiled files and executables | |
| debug/ | |
| target/ | |
| # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries | |
| # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html | |
| Cargo.lock | |
| # These are backup files generated by rustfmt | |
| **/*.rs.bk | |
| # MSVC Windows builds of rustc generate these, which store debugging information | |
| *.pdb | |
| # RustRover | |
| # JetBrains specific template is maintained in a separate JetBrains.gitignore that can | |
| # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore | |
| # and can be added to the global gitignore or merged into this file. For a more nuclear | |
| # option (not recommended) you can uncomment the following to ignore the entire idea folder. | |
| #.idea/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [package] | |
| name = "rawinput-touchpad" | |
| version = "0.1.0" | |
| edition = "2021" | |
| [[bin]] | |
| name = "rawinput-touchpad" | |
| path = "main.rs" | |
| [dependencies.windows] | |
| version = "0.*" | |
| features = [ | |
| "Win32_Devices_HumanInterfaceDevice", | |
| "Win32_Graphics_Gdi", | |
| "Win32_System_LibraryLoader", | |
| "Win32_UI_Input", | |
| "Win32_UI_WindowsAndMessaging", | |
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::ffi::c_void; | |
| use std::{ | |
| collections::HashMap, | |
| sync::{LazyLock, RwLock}, | |
| }; | |
| use windows::Win32::Foundation::NTSTATUS; | |
| use windows::{ | |
| core::*, | |
| Win32::{ | |
| Devices::HumanInterfaceDevice::*, | |
| Foundation::{BOOL, HANDLE, HINSTANCE, HWND, LPARAM, LRESULT, WPARAM}, | |
| Graphics::Gdi::UpdateWindow, System::LibraryLoader::GetModuleHandleW, | |
| UI::{Input, Input::*, WindowsAndMessaging, WindowsAndMessaging::*} | |
| } | |
| }; | |
| #[derive(Debug)] | |
| struct NtstatusError(NTSTATUS); | |
| impl ::std::error::Error for NtstatusError {} | |
| impl ::core::fmt::Display for NtstatusError { | |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| write!(f, "HIDP returns {:#08x}", self.0.0) | |
| } | |
| } | |
| type DynResult<T> = ::std::result::Result<T, Box<dyn ::std::error::Error>>; | |
| // RawInput デバイスハンドルをキーにした、 Preparsed Data (レポートディスクリプタの情報) のハッシュマップ | |
| static HID_CAPS_MAP: LazyLock<RwLock<HashMap<usize, CapsResult>>> = LazyLock::new(|| RwLock::new(HashMap::new())); | |
| fn main() -> Result<()> { | |
| let hinstance: HINSTANCE = unsafe { GetModuleHandleW(PCWSTR::null())?.into() }; | |
| // メインウィンドウクラスの作成 | |
| // ウィンドウの×を押したらアプリケーションを閉じれるようにするためだけで、特にほかに役割はない | |
| let wc_main = WNDCLASSW::from({ | |
| let mut wc = WNDCLASSW::default(); | |
| wc.lpfnWndProc = Some(window_proc); | |
| wc.hInstance = hinstance; | |
| wc.lpszMenuName = w!("MainMenu"); | |
| wc.lpszClassName = w!("MainMenuClass"); | |
| wc | |
| }); | |
| if unsafe { RegisterClassW(&wc_main) } == 0 { return Err(Error::from_win32()); } | |
| let hwnd_main = unsafe { CreateWindowExW( | |
| WINDOW_EX_STYLE(0), | |
| wc_main.lpszClassName, | |
| w!("Main"), | |
| WS_OVERLAPPEDWINDOW, | |
| CW_USEDEFAULT, | |
| CW_USEDEFAULT, | |
| CW_USEDEFAULT, | |
| CW_USEDEFAULT, | |
| HWND(core::ptr::null_mut()), | |
| HMENU(core::ptr::null_mut()), | |
| hinstance, | |
| Option::None | |
| )? }; | |
| unsafe { | |
| let _ = ShowWindow(hwnd_main, SHOW_WINDOW_CMD(5)); | |
| let _ = UpdateWindow(hwnd_main); | |
| }; | |
| // RawInput を受け取るメッセージ専用ウィンドウ (非表示) の作成 | |
| let wc_input = WNDCLASSW::from({ | |
| let mut wc = WNDCLASSW::default(); | |
| wc.lpfnWndProc = Some(input_proc); | |
| wc.hInstance = hinstance; | |
| wc.lpszClassName = w!("InputMessageClass"); | |
| wc | |
| }); | |
| if unsafe { RegisterClassW(&wc_input) } == 0 { return Err(Error::from_win32()); } | |
| let lett = unsafe { CreateWindowExW(WINDOW_EX_STYLE(0), wc_input.lpszClassName, PCWSTR::null(), WS_OVERLAPPED, 0, 0, 0, 0, HWND_MESSAGE, HMENU(core::ptr::null_mut()), hinstance, None) }; | |
| let hwnd_input = lett?; | |
| // USB HID (https://usb.org/sites/default/files/hut1_5.pdf) より、 | |
| // Digitizers Page (0x0D) の Touch Pad (0x05) の RawInput を受け取るウィンドウの登録 | |
| let rids: [_; 1] = [ | |
| RAWINPUTDEVICE { | |
| usUsagePage: HID_USAGE_PAGE_DIGITIZER, | |
| usUsage: HID_USAGE_DIGITIZER_TOUCH_PAD, | |
| dwFlags: RIDEV_INPUTSINK, | |
| hwndTarget: hwnd_input, | |
| } | |
| ]; | |
| unsafe { RegisterRawInputDevices(&rids, size_of::<RAWINPUTDEVICE>() as u32)? }; | |
| // メッセージループ | |
| let mut msg = MSG::default(); | |
| let mut b_ret; | |
| while { | |
| b_ret = unsafe { GetMessageW(&mut msg, HWND(core::ptr::null_mut()), 0, 0) }; | |
| BOOL(0) != b_ret | |
| } { | |
| if b_ret == BOOL(-1) { | |
| continue; | |
| } else { | |
| unsafe { | |
| let _ = TranslateMessage(&msg); | |
| let _ = DispatchMessageW(&msg); | |
| } | |
| } | |
| } | |
| Ok(()) | |
| } | |
| unsafe extern "system" fn window_proc( | |
| hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM | |
| ) -> LRESULT { | |
| // ウィンドウの×を押したら閉じるだけ | |
| match msg { | |
| WindowsAndMessaging::WM_DESTROY => PostQuitMessage(0), | |
| _ => return DefWindowProcW(hwnd, msg, wparam, lparam), | |
| }; | |
| return LRESULT(0); | |
| } | |
| unsafe extern "system" fn input_proc( | |
| hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM | |
| ) -> LRESULT { | |
| if msg == WM_INPUT { | |
| if let Err(err) = (|| -> DynResult<()> { | |
| let mut buf = Vec::<u8>::new(); | |
| let (ri_result, raw_data_list) = get_raw_input_data(lparam, &mut buf)?; | |
| match RID_DEVICE_INFO_TYPE(ri_result.header.dwType) { | |
| Input::RIM_TYPEMOUSE => {}, | |
| Input::RIM_TYPEKEYBOARD => {}, | |
| Input::RIM_TYPEHID => { | |
| let hdevice_key: usize = ri_result.header.hDevice.0 as usize; | |
| { | |
| // search from cache | |
| let caps_map_read = HID_CAPS_MAP.read()?; | |
| if let Some(recent_caps) = caps_map_read.get(&hdevice_key) { | |
| for raw_data in raw_data_list { | |
| print_raw_input_data(raw_data, recent_caps)?; | |
| } | |
| return Ok(()); | |
| } | |
| } // free caps_map_read | |
| let mut caps_map_write = HID_CAPS_MAP.write()?; | |
| let caps_result = get_new_raw_input_preparsed_capabilities(ri_result.header.hDevice)?; | |
| caps_map_write.insert(hdevice_key, caps_result.clone()); | |
| for raw_data in raw_data_list { | |
| print_raw_input_data(raw_data, &caps_result)?; | |
| } | |
| return Ok(()); | |
| }, | |
| _ => {}, | |
| } | |
| Ok(()) | |
| })() { | |
| eprintln!("{}", err); | |
| }; | |
| }; | |
| return DefWindowProcW(hwnd, msg, wparam, lparam); | |
| } | |
| unsafe fn get_raw_input_data<'a>(lparam: LPARAM, buf: &'a mut Vec<u8>) -> Result<(&'a RAWINPUT, Vec<&'a [u8]>)> { | |
| let mut dw_size: u32 = 0; | |
| GetRawInputData(HRAWINPUT(lparam.0 as *mut c_void), RID_INPUT, None, &mut dw_size, size_of::<RAWINPUTHEADER>() as u32); | |
| buf.resize(dw_size as usize, 0); | |
| if GetRawInputData(HRAWINPUT(lparam.0 as *mut c_void), RID_INPUT, Some(buf.as_mut_ptr() as *mut c_void), &mut dw_size, size_of::<RAWINPUTHEADER>() as u32) != dw_size { | |
| eprintln!("GetRawInputData does not return correct size!"); | |
| return Err(Error::from_win32()); | |
| } | |
| let ri = &*(buf.as_ptr() as *const c_void as *const RAWINPUT); | |
| let mut raw_data_list = Vec::<&'a [u8]>::with_capacity(ri.data.hid.dwCount as usize); | |
| if RID_DEVICE_INFO_TYPE(ri.header.dwType) == Input::RIM_TYPEHID { | |
| let offset = size_of::<RAWINPUTHEADER>() + size_of_val(&ri.data.hid.dwSizeHid) + size_of_val(&ri.data.hid.dwCount); | |
| for i in 0..ri.data.hid.dwCount { | |
| raw_data_list.push(&buf[(offset + (ri.data.hid.dwSizeHid * i) as usize)..(offset + (ri.data.hid.dwSizeHid * (i + 1)) as usize)]); | |
| } | |
| } | |
| Ok((ri, raw_data_list)) | |
| } | |
| #[derive(Clone)] | |
| struct CapsResult { | |
| preparseddata_buf: Vec<u8>, | |
| buttoncaps_list: Vec<HIDP_BUTTON_CAPS>, | |
| valuecaps_list: Vec<HIDP_VALUE_CAPS>, | |
| } | |
| impl CapsResult { | |
| fn get_preparseddata(&self) -> PHIDP_PREPARSED_DATA { | |
| PHIDP_PREPARSED_DATA(self.preparseddata_buf.as_ptr() as *const c_void as isize) | |
| } | |
| } | |
| unsafe fn get_new_raw_input_preparsed_capabilities(hdevice: HANDLE) -> DynResult<CapsResult> { | |
| // Get PreparsedData | |
| let mut dw_size: u32 = 0; | |
| GetRawInputDeviceInfoW(hdevice, RIDI_PREPARSEDDATA, None, &mut dw_size); | |
| let mut buf: Vec<u8> = vec![0; dw_size as usize]; | |
| if GetRawInputDeviceInfoW(hdevice, RIDI_PREPARSEDDATA, Some(buf.as_mut_ptr() as *mut c_void), &mut dw_size) != dw_size { | |
| eprintln!("GetRawInputDeviceInfoW does not return correct size!"); | |
| return Err(Error::from_win32().into()); | |
| } | |
| let preparseddata = PHIDP_PREPARSED_DATA(buf.as_mut_ptr() as *mut c_void as isize); | |
| // Parse Capabilities | |
| let mut capabilities = HIDP_CAPS::default(); | |
| let hidp_result = HidP_GetCaps(preparseddata, &mut capabilities); | |
| if hidp_result != HIDP_STATUS_SUCCESS { return Err(NtstatusError(hidp_result).into()); } | |
| let mut buttoncapslength = capabilities.NumberInputButtonCaps; | |
| let mut buttoncaps_list = vec![HIDP_BUTTON_CAPS::default(); buttoncapslength as usize]; | |
| let hidp_result = HidP_GetButtonCaps(HidP_Input, buttoncaps_list.as_mut_ptr(), &mut buttoncapslength, preparseddata); | |
| if hidp_result != HIDP_STATUS_SUCCESS { return Err(NtstatusError(hidp_result).into()); } | |
| let mut valuecapslength = capabilities.NumberInputValueCaps; | |
| let mut valuecaps_list = vec![HIDP_VALUE_CAPS::default(); valuecapslength as usize]; | |
| let hidp_result = HidP_GetValueCaps(HidP_Input, valuecaps_list.as_mut_ptr(), &mut valuecapslength, preparseddata); | |
| if hidp_result != HIDP_STATUS_SUCCESS { return Err(NtstatusError(hidp_result).into()); } | |
| Ok(CapsResult { preparseddata_buf: buf, buttoncaps_list, valuecaps_list }) | |
| } | |
| unsafe fn print_raw_input_data(raw_data: &[u8], caps_result: &CapsResult) -> DynResult<()> { | |
| let preparseddata = caps_result.get_preparseddata(); | |
| // Momentary Control (MC) | |
| // ON になっている HID ボタンを一覧 | |
| print!("btn: [ "); | |
| // buttoncaps を LinkCollection でグループ化 | |
| let linkcl_usage_map = caps_result.buttoncaps_list.iter().fold( | |
| HashMap::<u16, Vec::<USAGE_AND_PAGE>>::new(), | |
| |mut acc, btncaps| { | |
| let array = match acc.get_mut(&btncaps.LinkCollection) { | |
| Some(array) => array, | |
| None => { | |
| acc.insert(btncaps.LinkCollection, Vec::new()); | |
| acc.get_mut(&btncaps.LinkCollection).unwrap() | |
| }, | |
| }; | |
| array.push(USAGE_AND_PAGE { Usage: btncaps.Anonymous.NotRange.Usage, UsagePage: btncaps.UsagePage }); | |
| acc | |
| } | |
| ); | |
| for mapitem in linkcl_usage_map { | |
| let mut dw_length = caps_result.buttoncaps_list.len() as u32; | |
| let mut buttonlist = vec![USAGE_AND_PAGE::default(); dw_length as usize]; | |
| let hidp_result = HidP_GetUsagesEx(HidP_Input, mapitem.0, buttonlist.as_mut_ptr(), &mut dw_length, preparseddata, raw_data); | |
| if hidp_result != HIDP_STATUS_SUCCESS { return Err(NtstatusError(hidp_result).into()); } | |
| if mapitem.0 == 0 { | |
| // LinkCollection == 0 のとき、 HidP_GetUsagesEx が全ての UsagePage/Usage のペアを返すので、最上位の LinkCollection のものに絞り込む | |
| for i in 0..(dw_length as usize) { | |
| if mapitem.1.contains(&buttonlist[i]) { | |
| print!("({:#04x}) {:#04x}-{}, ", buttonlist[i].UsagePage, buttonlist[i].Usage, mapitem.0); | |
| } | |
| } | |
| } else { | |
| for i in 0..(dw_length as usize) { | |
| print!("({:#04x}) {:#04x}-{}, ", buttonlist[i].UsagePage, buttonlist[i].Usage, mapitem.0); | |
| } | |
| } | |
| } | |
| // Usage Type が Dynamic Value (DV) などのデータの取得 | |
| print!("] values: [ "); | |
| for value in caps_result.valuecaps_list.iter() { | |
| // レポートの数に応じて HidP_GetUsageValue と HidP_GetUsageValueArray を呼び分ける | |
| // https://learn.microsoft.com/ja-jp/windows-hardware/drivers/hid/value-capability-arrays | |
| if value.ReportCount == 1 { | |
| let mut usage_value = 0u32; | |
| let hidp_result = HidP_GetUsageValue(HidP_Input, value.UsagePage, value.LinkCollection, value.Anonymous.NotRange.Usage, &mut usage_value, preparseddata, raw_data); | |
| if hidp_result != HIDP_STATUS_SUCCESS { return Err(NtstatusError(hidp_result).into()); } | |
| print!("({:#04x}) {:#04x}-{}: {}, ", value.UsagePage, value.Anonymous.NotRange.Usage, value.LinkCollection, usage_value); | |
| } else if value.ReportCount > 1 { | |
| if value.BitSize > 64 { panic!("unsupported bit size: {}", value.BitSize); } | |
| print!("({:#04x}) {:#04x}-{}: [", value.UsagePage, value.Anonymous.NotRange.Usage, value.LinkCollection); | |
| let usage_bytes_length = (value.BitSize * value.ReportCount).div_ceil(8) as usize; | |
| let mut usagevalue = vec![0u8; usage_bytes_length]; | |
| let hidp_result = HidP_GetUsageValueArray(HidP_Input, value.UsagePage, value.LinkCollection, value.Anonymous.NotRange.Usage, &mut usagevalue, preparseddata, raw_data); | |
| if hidp_result != HIDP_STATUS_SUCCESS { return Err(NtstatusError(hidp_result).into()); } | |
| // usagevalue には、 value.BitSize ビットのデータがビット単位に詰められている。 これを value.BitSize 事に切り分ける。 | |
| // https://learn.microsoft.com/ja-jp/windows-hardware/drivers/hid/value-capability-arrays#usage-value-array | |
| let mut result = Vec::<u64>::new(); | |
| let mut buffer = 0u64; | |
| let mut bits_in_buffer = 0; | |
| for &byte in &usagevalue { | |
| buffer = (buffer << 8) | byte as u64; | |
| bits_in_buffer += 8; | |
| while bits_in_buffer >= value.BitSize { | |
| bits_in_buffer -= value.BitSize; | |
| let chunk = (buffer >> bits_in_buffer) & ((1 << value.BitSize) - 1); | |
| result.push(chunk); | |
| } | |
| } | |
| for &value in &result { | |
| print!("{}, ", value); | |
| } | |
| print!("]"); | |
| } | |
| } | |
| println!("]"); | |
| Ok(()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment