cue_sdk/event/
mod.rs

1//! Contains the event types, and event errors that can be returned from the
2//! iCUE SDK.
3use cue_sdk_sys as ffi;
4use num_traits::FromPrimitive;
5
6use super::key::KeyId;
7use crate::device::{DeviceId, DeviceIdFromFfiError};
8mod subscription;
9
10#[cfg(feature = "async")]
11pub use subscription::EventSubscription;
12
13/// The two-variant event that can come back from the iCUE SDK.
14///
15/// The first is a notification of device status connection changes, with
16/// the device id and whether it is now connected or not.
17///
18/// The second is a notification of a key event (pressed or released). Note
19/// you will only receive notifications of keys that are value `KeyId`s (primarily
20/// corsair specific media and "G" keys).
21#[derive(Debug, Clone, PartialEq)]
22#[repr(u32)]
23pub enum CueEvent {
24    DeviceConnectedStatusChangedEvent(DeviceId, bool),
25    KeyEvent(DeviceId, KeyId, bool),
26}
27
28/// All of the various reasons why creating a CueEvent from the FFI interface can fail.
29#[derive(Debug, Clone, Fail, PartialEq)]
30pub enum CueEventFromFfiError {
31    #[fail(display = "Received non-utf8 device id, error: {:?}.", _0)]
32    DeviceIdError(DeviceIdFromFfiError),
33    #[fail(display = "Received unknown event type: {}.", _0)]
34    UnknownEventType(u32),
35    #[fail(display = "Received unknown key id: {}.", _0)]
36    UnknownKeyId(u32),
37    #[fail(display = "The deviceConnectionStatusChangedEvent pointer was null.")]
38    NullPointerDeviceConnectStatusChangedEvent,
39    #[fail(display = "The keyEvent pointer was null.")]
40    NullPointerKeyEvent,
41}
42
43impl CueEvent {
44    pub(crate) fn from_ffi(event: ffi::CorsairEvent) -> Result<CueEvent, CueEventFromFfiError> {
45        match event.id {
46            ffi::CorsairEventId_CEI_DeviceConnectionStatusChangedEvent => {
47                if unsafe {
48                    event
49                        .event_union
50                        .deviceConnectionStatusChangedEvent
51                        .is_null()
52                } {
53                    return Err(CueEventFromFfiError::NullPointerDeviceConnectStatusChangedEvent);
54                }
55                let event = unsafe { *event.event_union.deviceConnectionStatusChangedEvent };
56                let device_id = DeviceId::from_ffi(event.deviceId)
57                    .map_err(|e| CueEventFromFfiError::DeviceIdError(e))?;
58                Ok(CueEvent::DeviceConnectedStatusChangedEvent(
59                    device_id,
60                    event.isConnected,
61                ))
62            }
63            ffi::CorsairEventId_CEI_KeyEvent => {
64                if unsafe { event.event_union.keyEvent.is_null() } {
65                    return Err(CueEventFromFfiError::NullPointerKeyEvent);
66                }
67                let event = unsafe { *event.event_union.keyEvent };
68                let device_id = DeviceId::from_ffi(event.deviceId)
69                    .map_err(|e| CueEventFromFfiError::DeviceIdError(e))?;
70                let key_id = KeyId::from_u32(event.keyId);
71                match key_id {
72                    Some(k) => Ok(CueEvent::KeyEvent(device_id, k, event.isPressed)),
73                    None => Err(CueEventFromFfiError::UnknownKeyId(event.keyId)),
74                }
75            }
76            _ => Err(CueEventFromFfiError::UnknownEventType(event.id)),
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use cue_sdk_sys as ffi;
84
85    use std::ptr;
86
87    use super::{CueEvent, CueEventFromFfiError};
88    use crate::device::DeviceId;
89    use crate::key::KeyId;
90
91    const EXAMPLE_DEVICE_ID: &[i8; 128] = &[
92        0x30, 0x40, 0x50, 0x20, 0x30, 0x20, 0x30, 0x40, 0x10, 0x11, 0x12, 0x13, 0x30, 0x40, 0x50,
93        0x20, 0x30, 0x20, 0x30, 0x40, 0x10, 0x11, 0x12, 0x13, 0x30, 0x40, 0x50, 0x20, 0x30, 0x20,
94        0x30, 0x40, 0x10, 0x11, 0x12, 0x13, 0x30, 0x40, 0x50, 0x20, 0x30, 0x20, 0x30, 0x40, 0x10,
95        0x11, 0x12, 0x13, 0x30, 0x40, 0x50, 0x20, 0x30, 0x20, 0x30, 0x40, 0x10, 0x11, 0x12, 0x13,
96        0x30, 0x40, 0x50, 0x20, 0x30, 0x20, 0x30, 0x40, 0x10, 0x11, 0x12, 0x13, 0x30, 0x40, 0x50,
97        0x20, 0x30, 0x20, 0x30, 0x40, 0x10, 0x11, 0x12, 0x13, 0x30, 0x40, 0x50, 0x20, 0x30, 0x20,
98        0x30, 0x40, 0x10, 0x11, 0x12, 0x13, 0x30, 0x40, 0x50, 0x20, 0x30, 0x20, 0x30, 0x40, 0x10,
99        0x11, 0x12, 0x13, 0x30, 0x40, 0x50, 0x20, 0x30, 0x20, 0x30, 0x40, 0x10, 0x11, 0x12, 0x13,
100        0x30, 0x40, 0x50, 0x20, 0x30, 0x20, 0x30, 0x40,
101    ];
102
103    #[test]
104    fn from_ffi_with_unknown_event_type() {
105        let unknown_event_type = 24;
106        let ffi_value = ffi::CorsairEvent {
107            id: unknown_event_type,
108            event_union: ffi::CorsairEventUnion {
109                deviceConnectionStatusChangedEvent: ptr::null(),
110            },
111        };
112        assert_eq!(
113            CueEvent::from_ffi(ffi_value).unwrap_err(),
114            CueEventFromFfiError::UnknownEventType(unknown_event_type)
115        );
116    }
117
118    #[test]
119    fn from_ffi_device_connection_null_ptr() {
120        let ffi_value = ffi::CorsairEvent {
121            id: ffi::CorsairEventId_CEI_DeviceConnectionStatusChangedEvent,
122            event_union: ffi::CorsairEventUnion {
123                deviceConnectionStatusChangedEvent: ptr::null(),
124            },
125        };
126        assert_eq!(
127            CueEvent::from_ffi(ffi_value).unwrap_err(),
128            CueEventFromFfiError::NullPointerDeviceConnectStatusChangedEvent
129        );
130    }
131
132    #[test]
133    fn from_ffi_device_connection_all_valid() {
134        let event = ffi::CorsairDeviceConnectionStatusChangedEvent {
135            deviceId: EXAMPLE_DEVICE_ID.clone(),
136            isConnected: false,
137        };
138        let ffi_value = ffi::CorsairEvent {
139            id: ffi::CorsairEventId_CEI_DeviceConnectionStatusChangedEvent,
140            event_union: ffi::CorsairEventUnion {
141                deviceConnectionStatusChangedEvent: &event
142                    as *const ffi::CorsairDeviceConnectionStatusChangedEvent,
143            },
144        };
145        assert_eq!(
146            CueEvent::from_ffi(ffi_value).unwrap(),
147            CueEvent::DeviceConnectedStatusChangedEvent(DeviceId("0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@".to_string()), false)
148        )
149    }
150
151    #[test]
152    fn from_ffi_key_press_null_ptr() {
153        let ffi_value = ffi::CorsairEvent {
154            id: ffi::CorsairEventId_CEI_KeyEvent,
155            event_union: ffi::CorsairEventUnion {
156                keyEvent: ptr::null(),
157            },
158        };
159        assert_eq!(
160            CueEvent::from_ffi(ffi_value).unwrap_err(),
161            CueEventFromFfiError::NullPointerKeyEvent
162        );
163    }
164
165    #[test]
166    fn from_ffi_key_press_invalid_key_id() {
167        let invalid_key_id = 1300;
168        let event = ffi::CorsairKeyEvent {
169            deviceId: EXAMPLE_DEVICE_ID.clone(),
170            keyId: invalid_key_id,
171            isPressed: true,
172        };
173        let ffi_value = ffi::CorsairEvent {
174            id: ffi::CorsairEventId_CEI_KeyEvent,
175            event_union: ffi::CorsairEventUnion {
176                keyEvent: &event as *const ffi::CorsairKeyEvent,
177            },
178        };
179        assert_eq!(
180            CueEvent::from_ffi(ffi_value).unwrap_err(),
181            CueEventFromFfiError::UnknownKeyId(invalid_key_id)
182        )
183    }
184
185    #[test]
186    fn from_ffi_key_press_all_valid() {
187        let valid_key_id = ffi::CorsairKeyId_CorsairKeyKb_G2;
188        let event = ffi::CorsairKeyEvent {
189            deviceId: EXAMPLE_DEVICE_ID.clone(),
190            keyId: valid_key_id,
191            isPressed: true,
192        };
193        let ffi_value = ffi::CorsairEvent {
194            id: ffi::CorsairEventId_CEI_KeyEvent,
195            event_union: ffi::CorsairEventUnion {
196                keyEvent: &event as *const ffi::CorsairKeyEvent,
197            },
198        };
199        assert_eq!(
200            CueEvent::from_ffi(ffi_value).unwrap(),
201            CueEvent::KeyEvent(DeviceId("0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@\u{10}\u{11}\u{12}\u{13}0@P 0 0@".to_string()), KeyId::KeyboardG2, true)
202        )
203    }
204}