Skip to main content

cue_sdk/
event.rs

1use std::sync::mpsc;
2
3use cue_sdk_sys as ffi;
4
5use crate::callback;
6use crate::device::DeviceId;
7use crate::error::{self, Result};
8
9// ---------------------------------------------------------------------------
10// MacroKeyId
11// ---------------------------------------------------------------------------
12
13/// Identifier for a G/M/S macro key.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15#[repr(u32)]
16pub enum MacroKeyId {
17    Key1 = ffi::CorsairMacroKeyId_CMKI_1,
18    Key2 = ffi::CorsairMacroKeyId_CMKI_2,
19    Key3 = ffi::CorsairMacroKeyId_CMKI_3,
20    Key4 = ffi::CorsairMacroKeyId_CMKI_4,
21    Key5 = ffi::CorsairMacroKeyId_CMKI_5,
22    Key6 = ffi::CorsairMacroKeyId_CMKI_6,
23    Key7 = ffi::CorsairMacroKeyId_CMKI_7,
24    Key8 = ffi::CorsairMacroKeyId_CMKI_8,
25    Key9 = ffi::CorsairMacroKeyId_CMKI_9,
26    Key10 = ffi::CorsairMacroKeyId_CMKI_10,
27    Key11 = ffi::CorsairMacroKeyId_CMKI_11,
28    Key12 = ffi::CorsairMacroKeyId_CMKI_12,
29    Key13 = ffi::CorsairMacroKeyId_CMKI_13,
30    Key14 = ffi::CorsairMacroKeyId_CMKI_14,
31    Key15 = ffi::CorsairMacroKeyId_CMKI_15,
32    Key16 = ffi::CorsairMacroKeyId_CMKI_16,
33    Key17 = ffi::CorsairMacroKeyId_CMKI_17,
34    Key18 = ffi::CorsairMacroKeyId_CMKI_18,
35    Key19 = ffi::CorsairMacroKeyId_CMKI_19,
36    Key20 = ffi::CorsairMacroKeyId_CMKI_20,
37}
38
39impl MacroKeyId {
40    pub(crate) fn from_ffi(raw: ffi::CorsairMacroKeyId) -> Option<Self> {
41        match raw {
42            ffi::CorsairMacroKeyId_CMKI_1 => Some(Self::Key1),
43            ffi::CorsairMacroKeyId_CMKI_2 => Some(Self::Key2),
44            ffi::CorsairMacroKeyId_CMKI_3 => Some(Self::Key3),
45            ffi::CorsairMacroKeyId_CMKI_4 => Some(Self::Key4),
46            ffi::CorsairMacroKeyId_CMKI_5 => Some(Self::Key5),
47            ffi::CorsairMacroKeyId_CMKI_6 => Some(Self::Key6),
48            ffi::CorsairMacroKeyId_CMKI_7 => Some(Self::Key7),
49            ffi::CorsairMacroKeyId_CMKI_8 => Some(Self::Key8),
50            ffi::CorsairMacroKeyId_CMKI_9 => Some(Self::Key9),
51            ffi::CorsairMacroKeyId_CMKI_10 => Some(Self::Key10),
52            ffi::CorsairMacroKeyId_CMKI_11 => Some(Self::Key11),
53            ffi::CorsairMacroKeyId_CMKI_12 => Some(Self::Key12),
54            ffi::CorsairMacroKeyId_CMKI_13 => Some(Self::Key13),
55            ffi::CorsairMacroKeyId_CMKI_14 => Some(Self::Key14),
56            ffi::CorsairMacroKeyId_CMKI_15 => Some(Self::Key15),
57            ffi::CorsairMacroKeyId_CMKI_16 => Some(Self::Key16),
58            ffi::CorsairMacroKeyId_CMKI_17 => Some(Self::Key17),
59            ffi::CorsairMacroKeyId_CMKI_18 => Some(Self::Key18),
60            ffi::CorsairMacroKeyId_CMKI_19 => Some(Self::Key19),
61            ffi::CorsairMacroKeyId_CMKI_20 => Some(Self::Key20),
62            _ => None,
63        }
64    }
65}
66
67// ---------------------------------------------------------------------------
68// Event
69// ---------------------------------------------------------------------------
70
71/// An event received from the iCUE SDK.
72#[derive(Debug, Clone)]
73pub enum Event {
74    /// A device was connected or disconnected.
75    DeviceConnectionChanged {
76        device_id: DeviceId,
77        is_connected: bool,
78    },
79    /// A macro key was pressed or released.
80    KeyEvent {
81        device_id: DeviceId,
82        key_id: MacroKeyId,
83        is_pressed: bool,
84    },
85}
86
87impl Event {
88    /// Parse a raw FFI event.  Returns `None` for unrecognised event IDs.
89    pub(crate) fn from_ffi(raw: &ffi::CorsairEvent) -> Option<Self> {
90        match raw.id {
91            ffi::CorsairEventId_CEI_DeviceConnectionStatusChangedEvent => {
92                // SAFETY: The event `id` field is `CEI_DeviceConnectionStatusChangedEvent`,
93                // so the `deviceConnectionStatusChangedEvent` union variant is active
94                // and the pointer is valid for the callback's lifetime.
95                let inner = unsafe { &*raw.event_union.deviceConnectionStatusChangedEvent };
96                Some(Event::DeviceConnectionChanged {
97                    device_id: DeviceId::from_ffi(inner.deviceId),
98                    is_connected: inner.isConnected,
99                })
100            }
101            ffi::CorsairEventId_CEI_KeyEvent => {
102                // SAFETY: The event `id` field is `CEI_KeyEvent`, so the `keyEvent`
103                // union variant is active and the pointer is valid.
104                let inner = unsafe { &*raw.event_union.keyEvent };
105                let key_id = MacroKeyId::from_ffi(inner.keyId)?;
106                Some(Event::KeyEvent {
107                    device_id: DeviceId::from_ffi(inner.deviceId),
108                    key_id,
109                    is_pressed: inner.isPressed,
110                })
111            }
112            _ => None,
113        }
114    }
115}
116
117// ---------------------------------------------------------------------------
118// EventSubscription
119// ---------------------------------------------------------------------------
120
121/// An active event subscription.  Events can be received via [`recv`](Self::recv)
122/// or [`try_recv`](Self::try_recv).
123///
124/// When this value is dropped the subscription is automatically cancelled by
125/// calling `CorsairUnsubscribeFromEvents`.
126pub struct EventSubscription {
127    rx: mpsc::Receiver<Event>,
128    // Prevent the sender from being dropped while the SDK holds the pointer.
129    _sender: callback::EventSender,
130}
131
132impl EventSubscription {
133    /// Create a new subscription.  Called by `Session::subscribe_for_events`.
134    pub(crate) fn new(sender: callback::EventSender, rx: mpsc::Receiver<Event>) -> Result<Self> {
135        let ctx = callback::sender_as_context(&sender);
136        // SAFETY: We pass a valid function pointer and a context pointer derived
137        // from a pinned boxed sender that we keep alive in the returned struct.
138        error::check(unsafe {
139            ffi::CorsairSubscribeForEvents(Some(callback::event_trampoline), ctx)
140        })?;
141        Ok(Self {
142            rx,
143            _sender: sender,
144        })
145    }
146
147    /// Block until the next event arrives.
148    pub fn recv(&self) -> Option<Event> {
149        self.rx.recv().ok()
150    }
151
152    /// Non-blocking receive.
153    pub fn try_recv(&self) -> Option<Event> {
154        self.rx.try_recv().ok()
155    }
156
157    /// Returns an iterator that blocks on each event.
158    pub fn iter(&self) -> impl Iterator<Item = Event> + '_ {
159        self.rx.iter()
160    }
161}
162
163impl Drop for EventSubscription {
164    fn drop(&mut self) {
165        // SAFETY: `CorsairUnsubscribeFromEvents` is safe to call at any time
166        // and will stop the SDK from invoking the callback, after which the
167        // pinned sender can be safely dropped.
168        unsafe {
169            let _ = ffi::CorsairUnsubscribeFromEvents();
170        }
171    }
172}
173
174// ---------------------------------------------------------------------------
175// AsyncEventSubscription (feature = "async")
176// ---------------------------------------------------------------------------
177
178/// An active event subscription with an async receiver.
179///
180/// Events can be received via [`recv`](Self::recv).  When this value is
181/// dropped the subscription is automatically cancelled by calling
182/// `CorsairUnsubscribeFromEvents`.
183///
184/// Requires the `async` feature.
185#[cfg(feature = "async")]
186pub struct AsyncEventSubscription {
187    rx: tokio::sync::mpsc::UnboundedReceiver<Event>,
188    // Prevent the sender from being dropped while the SDK holds the pointer.
189    _sender: callback::AsyncEventSender,
190}
191
192#[cfg(feature = "async")]
193impl AsyncEventSubscription {
194    /// Create a new async subscription.  Called by
195    /// `Session::subscribe_for_events_async`.
196    pub(crate) fn new(
197        sender: callback::AsyncEventSender,
198        rx: tokio::sync::mpsc::UnboundedReceiver<Event>,
199    ) -> Result<Self> {
200        let ctx = callback::async_sender_as_context(&sender);
201        // SAFETY: We pass a valid function pointer and a context pointer
202        // derived from a pinned boxed sender that we keep alive in the
203        // returned struct.
204        error::check(unsafe {
205            ffi::CorsairSubscribeForEvents(Some(callback::async_event_trampoline), ctx)
206        })?;
207        Ok(Self {
208            rx,
209            _sender: sender,
210        })
211    }
212
213    /// Await the next event from the SDK.
214    ///
215    /// Returns `None` if the sender is dropped (should not happen while the
216    /// subscription is alive).
217    pub async fn recv(&mut self) -> Option<Event> {
218        self.rx.recv().await
219    }
220}
221
222#[cfg(feature = "async")]
223impl Drop for AsyncEventSubscription {
224    fn drop(&mut self) {
225        // SAFETY: `CorsairUnsubscribeFromEvents` is safe to call at any time
226        // and will stop the SDK from invoking the callback, after which the
227        // pinned sender can be safely dropped.
228        unsafe {
229            let _ = ffi::CorsairUnsubscribeFromEvents();
230        }
231    }
232}