Skip to content

Instantly share code, notes, and snippets.

@advanceboy
Last active December 17, 2024 14:19
Show Gist options
  • Select an option

  • Save advanceboy/7a105124e46cdc34ca81cfdfee936aa6 to your computer and use it in GitHub Desktop.

Select an option

Save advanceboy/7a105124e46cdc34ca81cfdfee936aa6 to your computer and use it in GitHub Desktop.
`rawinput-touchpad.rs` dump RawInput HID reports of touchpad
# 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/
[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",
]
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