xinput/functions/
get_capabilities_.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-xinputgetcapabilities)\]
8/// XInputGetCapabilities
9///
10/// ### Arguments
11/// *   `user_index`    &mdash; The controller to get capabilities and features for (<code>0 .. [xuser::MAX_COUNT]</code>.)
12/// *   `flags`         &mdash; [`Flag::None`] or [`Flag::Gamepad`].
13///
14/// ### Example
15/// ```rust
16/// let caps = xinput::get_capabilities(0, xinput::Flag::None);
17/// println!("{caps:#?}");
18/// ```
19///
20/// ### Output
21/// ```text
22/// Ok(
23///     Capabilities {
24///         ty: DevType::Gamepad,
25///         sub_type: DevSubType::Gamepad,
26///         flags: Caps::None,
27///         gamepad: Gamepad {
28///             buttons: Buttons::{DPadUp|DPadDown|DPadLeft|DPadRight|Start|Back|LeftThumb|RightThumb|LeftShoulder|RightShoulder|A|B|X|Y},
29///             left_trigger: 255,
30///             right_trigger: 255,
31///             left_thumb_x: -64,
32///             left_thumb_y: -64,
33///             right_thumb_x: -64,
34///             right_thumb_y: -64,
35///         },
36///         vibration: Vibration {
37///             left_motor_speed: 255,
38///             right_motor_speed: 255,
39///         },
40///     },
41/// )
42/// ```
43///
44/// ### Errors
45/// *   [error::BAD_ARGUMENTS]          - Invalid [`Flag`]
46/// *   [error::BAD_ARGUMENTS]          - Invalid `user_index` (expected <code>0 .. [xuser::MAX_COUNT]</code>)
47/// *   [error::DEVICE_NOT_CONNECTED]   - [`Flag::None`]
48/// *   [error::DEVICE_NOT_CONNECTED]   - No gamepad connected for `user_index`.
49/// *   [error::INVALID_FUNCTION]       - API unavailable: XInput not loaded
50pub fn get_capabilities(user_index: impl TryInto<u32>, flags: Flag) -> Result<Capabilities, Error> {
51    fn_context!(xinput::get_capabilities => XInputGetCapabilities);
52    #[allow(non_snake_case)] let XInputGetCapabilities = imports::XInputGetCapabilities.load(core::sync::atomic::Ordering::Relaxed);
53    let user_index = user_index.try_into().map_err(|_| fn_param_error!(user_index, error::BAD_ARGUMENTS))?;
54
55    let mut caps = Capabilities::zeroed();
56    // SAFETY: ✔️
57    //  * fuzzed        in `tests/fuzz-xinput.rs`
58    //  * `user_index`  is well tested
59    //  * `flags`       is decently tested (0, 1, 2 (OOB), 4, 8, 16, 32, 64, 128, 0xFFFFFFFF)
60    //  * `caps`        is out-only, no cbSize field, fixed size, sane
61    let code = unsafe { XInputGetCapabilities(user_index, flags.into(), caps.as_mut()) };
62    check_success!(code)?;
63    Ok(caps)
64}
65
66#[test] fn test_valid_params() {
67    for user_index in 0 .. 4 {
68        for flag in [Flag::None, Flag::Gamepad] {
69            if let Err(err) = get_capabilities(user_index, flag) {
70                assert!(matches!(err.kind(), error::DEVICE_NOT_CONNECTED | error::CO_E_NOTINITIALIZED), "unexpected error type: {err:?}");
71            }
72        }
73    }
74}
75
76#[test] fn test_bad_user_index() {
77    for user_index in xuser::invalids().chain(Some(xuser::INDEX_ANY)) {
78        for flag in [Flag::None, Flag::Gamepad, Flag::from_unchecked(42), Flag::from_unchecked(!0)] {
79            let err = get_capabilities(user_index, flag).expect_err("get_capabilities should return an error on a bad user_index");
80            assert!(matches!(err.kind(), error::BAD_ARGUMENTS | error::CO_E_NOTINITIALIZED), "unexpected error type: {err:?}");
81        }
82    }
83}
84
85#[test] fn test_bad_flags() {
86    for user_index in 0 .. 4 {
87        for flag in [Flag::from_unchecked(42), Flag::from_unchecked(!0)] {
88            let err = get_capabilities(user_index, flag).expect_err("get_capabilities should return an error on a bad flag");
89            assert!(matches!(err.kind(), error::BAD_ARGUMENTS | error::CO_E_NOTINITIALIZED), "unexpected error type: {err:?}");
90        }
91    }
92}