1use 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#[derive(Debug, Clone, PartialEq)]
22#[repr(u32)]
23pub enum CueEvent {
24 DeviceConnectedStatusChangedEvent(DeviceId, bool),
25 KeyEvent(DeviceId, KeyId, bool),
26}
27
28#[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}