xinput/functions/get_keystroke_.rs
1use crate::*;
2
3use bytemuck::Zeroable;
4
5
6
7/// \[[microsoft.com](https://learn.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputgetkeystroke)\]
8/// XInputGetKeystroke
9/// <span style="opacity: 50%">(1.3+)</span>
10///
11/// Retrieves gamepad input events.
12///
13/// ### Argumen.t
14/// * `user_index` — The controller to get button/keystrokes for (<code>0 .. [xuser::MAX_COUNT]</code> or [`xuser::INDEX_ANY`].)
15/// * `_reserved` — Reserved for future use, simply pass `()`.
16///
17/// ### Example
18/// ```rust
19/// // Wait for any gamepad button
20/// let gamepad = 0;
21/// loop {
22/// match xinput::get_keystroke(gamepad, ()) {
23/// Ok(Some(keystroke)) => break println!("{keystroke:#?}"),
24/// # Ok(None) if true => break, // don't actually wait
25/// Ok(None) => std::thread::yield_now(), // wait
26/// Err(err) => break println!("no xinput or no gamepad: {err:?}"),
27/// }
28/// }
29/// ```
30///
31/// ### Output
32/// ```text
33/// Keystroke {
34/// virtual_key: VK::PadStart,
35/// unicode: 0,
36/// flags: Keystroke::KeyDown,
37/// user_index: 0,
38/// hid_code: 0,
39/// }
40/// ```
41///
42/// ### Errors
43/// * [error::BAD_ARGUMENTS] - Invalid `user_index` (expected <code>0 .. [xuser::MAX_COUNT]</code> or [`xuser::INDEX_ANY`])
44/// * [error::DEVICE_NOT_CONNECTED] - No gamepad connected for `user_index`.
45/// * ~~error::EMPTY~~ - No [`Keystroke`]s available. Returns <code>[Ok]\([None]\)</code> instead.
46/// * [error::INVALID_FUNCTION] - API unavailable: requires XInput 1.3 or later
47pub fn get_keystroke(user_index: impl TryInto<u32>, _reserved: ()) -> Result<Option<Keystroke>, Error> {
48 fn_context!(xinput::get_keystroke => XInputGetKeystroke);
49 #[allow(non_snake_case)] let XInputGetKeystroke = imports::XInputGetKeystroke.load(core::sync::atomic::Ordering::Relaxed);
50 let user_index = user_index.try_into().map_err(|_| fn_param_error!(user_index, error::BAD_ARGUMENTS))?;
51
52 let mut keystroke = Keystroke::zeroed();
53 // SAFETY: ✔️
54 // * fuzzed in `tests/fuzz-xinput.rs`
55 // * tested in `examples/xinput-exercise-all.rs`
56 // * `user_index` is well tested
57 let code = unsafe { XInputGetKeystroke(user_index, 0, keystroke.as_mut()) };
58 if code == winresult::ERROR::EMPTY.to_u32() { return Ok(None) }
59 check_success!(code)?;
60 Ok(Some(keystroke))
61}
62
63#[test] fn test_valid_args() {
64 for user_index in (0..4).chain(Some(xuser::INDEX_ANY)) {
65 if let Err(err) = get_keystroke(user_index, ()) {
66 assert!(matches!(err.kind(), error::DEVICE_NOT_CONNECTED | error::INVALID_FUNCTION | error::CO_E_NOTINITIALIZED), "unexpected error type: {err:?}");
67 }
68 }
69}
70
71#[test] fn test_bad_user_index() {
72 // xuser::INDEX_ANY is valid
73 for user_index in xuser::invalids() {
74 let err = get_keystroke(user_index, ()).expect_err("get_keystroke should return an error for invalid users");
75 assert!(matches!(err.kind(), error::BAD_ARGUMENTS | error::INVALID_FUNCTION | error::CO_E_NOTINITIALIZED), "unexpected error type: {err:?}");
76 }
77}