1use std::mem::MaybeUninit;
2use std::sync::mpsc;
3use std::time::Duration;
4
5use core::ffi::{c_char, c_int};
6use cue_sdk_sys as ffi;
7
8use crate::callback::{self, SessionStateChange};
9use crate::device::{DeviceId, DeviceInfo, DeviceType};
10use crate::error::{self, Result, SdkError};
11#[cfg(feature = "async")]
12use crate::event::AsyncEventSubscription;
13use crate::event::{EventSubscription, MacroKeyId};
14use crate::led::{LedColor, LedPosition};
15use crate::property::{DataType, PropertyFlags, PropertyId, PropertyInfo, PropertyValue};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct Version {
24 pub major: i32,
25 pub minor: i32,
26 pub patch: i32,
27}
28
29impl Version {
30 pub(crate) fn from_ffi(v: &ffi::CorsairVersion) -> Self {
31 Self {
32 major: v.major,
33 minor: v.minor,
34 patch: v.patch,
35 }
36 }
37}
38
39impl std::fmt::Display for Version {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
42 }
43}
44
45#[derive(Debug, Clone, Copy)]
51pub struct SessionDetails {
52 pub client_version: Version,
53 pub server_version: Version,
54 pub server_host_version: Version,
55}
56
57impl SessionDetails {
58 pub(crate) fn from_ffi(d: &ffi::CorsairSessionDetails) -> Self {
59 Self {
60 client_version: Version::from_ffi(&d.clientVersion),
61 server_version: Version::from_ffi(&d.serverVersion),
62 server_host_version: Version::from_ffi(&d.serverHostVersion),
63 }
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum SessionState {
74 Invalid,
75 Closed,
76 Connecting,
77 Timeout,
78 ConnectionRefused,
79 ConnectionLost,
80 Connected,
81 Unknown(u32),
82}
83
84impl SessionState {
85 pub(crate) fn from_ffi(raw: ffi::CorsairSessionState) -> Self {
86 match raw {
87 ffi::CorsairSessionState_CSS_Invalid => Self::Invalid,
88 ffi::CorsairSessionState_CSS_Closed => Self::Closed,
89 ffi::CorsairSessionState_CSS_Connecting => Self::Connecting,
90 ffi::CorsairSessionState_CSS_Timeout => Self::Timeout,
91 ffi::CorsairSessionState_CSS_ConnectionRefused => Self::ConnectionRefused,
92 ffi::CorsairSessionState_CSS_ConnectionLost => Self::ConnectionLost,
93 ffi::CorsairSessionState_CSS_Connected => Self::Connected,
94 other => Self::Unknown(other),
95 }
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105#[repr(u32)]
106pub enum AccessLevel {
107 Shared = ffi::CorsairAccessLevel_CAL_Shared,
108 ExclusiveLightingControl = ffi::CorsairAccessLevel_CAL_ExclusiveLightingControl,
109 ExclusiveKeyEventsListening = ffi::CorsairAccessLevel_CAL_ExclusiveKeyEventsListening,
110 ExclusiveLightingControlAndKeyEventsListening =
111 ffi::CorsairAccessLevel_CAL_ExclusiveLightingControlAndKeyEventsListening,
112}
113
114pub struct Session {
125 state_rx: mpsc::Receiver<SessionStateChange>,
126 _state_sender: callback::SessionStateSender,
128}
129
130unsafe impl Send for Session {}
134unsafe impl Sync for Session {}
135
136impl Session {
137 pub fn connect() -> Result<Self> {
143 let (sender, rx) = callback::session_state_channel();
144 let ctx = callback::sender_as_context(&sender);
145
146 error::check(unsafe {
149 ffi::CorsairConnect(Some(callback::session_state_trampoline), ctx)
150 })?;
151
152 Ok(Self {
153 state_rx: rx,
154 _state_sender: sender,
155 })
156 }
157
158 pub fn wait_for_connection(&self, timeout: Duration) -> Result<SessionDetails> {
167 let deadline = std::time::Instant::now() + timeout;
168 loop {
169 let remaining = deadline.saturating_duration_since(std::time::Instant::now());
170 if remaining.is_zero() {
171 return Err(SdkError::NotConnected);
172 }
173 match self.state_rx.recv_timeout(remaining) {
174 Ok(change) => {
175 let state = SessionState::from_ffi(change.state);
176 match state {
177 SessionState::Connected => {
178 return Ok(SessionDetails::from_ffi(&change.details));
179 }
180 SessionState::Connecting => continue,
181 _ => return Err(SdkError::NotConnected),
182 }
183 }
184 Err(mpsc::RecvTimeoutError::Timeout) => return Err(SdkError::NotConnected),
185 Err(mpsc::RecvTimeoutError::Disconnected) => return Err(SdkError::NotConnected),
186 }
187 }
188 }
189
190 pub fn details(&self) -> Result<SessionDetails> {
192 let mut raw = MaybeUninit::<ffi::CorsairSessionDetails>::uninit();
193 error::check(unsafe { ffi::CorsairGetSessionDetails(raw.as_mut_ptr()) })?;
196 Ok(SessionDetails::from_ffi(unsafe { &raw.assume_init() }))
198 }
199
200 pub fn get_devices(&self, filter: DeviceType) -> Result<Vec<DeviceInfo>> {
204 let ffi_filter = ffi::CorsairDeviceFilter {
205 deviceTypeMask: filter.bits() as c_int,
206 };
207 let mut buf = [MaybeUninit::<ffi::CorsairDeviceInfo>::uninit();
208 ffi::CORSAIR_DEVICE_COUNT_MAX as usize];
209 let mut count: c_int = 0;
210
211 error::check(unsafe {
214 ffi::CorsairGetDevices(
215 &ffi_filter,
216 buf.len() as c_int,
217 buf.as_mut_ptr().cast(),
218 &mut count,
219 )
220 })?;
221
222 let devices = (0..count as usize)
223 .map(|i| DeviceInfo::from_ffi(unsafe { buf[i].assume_init_ref() }))
225 .collect();
226 Ok(devices)
227 }
228
229 pub fn get_device_info(&self, device_id: &DeviceId) -> Result<DeviceInfo> {
231 let mut raw = MaybeUninit::<ffi::CorsairDeviceInfo>::uninit();
232 error::check(unsafe { ffi::CorsairGetDeviceInfo(device_id.as_ptr(), raw.as_mut_ptr()) })?;
235 Ok(DeviceInfo::from_ffi(unsafe { raw.assume_init_ref() }))
237 }
238
239 pub fn get_led_positions(&self, device_id: &DeviceId) -> Result<Vec<LedPosition>> {
243 let mut buf = [MaybeUninit::<ffi::CorsairLedPosition>::uninit();
244 ffi::CORSAIR_DEVICE_LEDCOUNT_MAX as usize];
245 let mut count: c_int = 0;
246
247 error::check(unsafe {
250 ffi::CorsairGetLedPositions(
251 device_id.as_ptr(),
252 buf.len() as c_int,
253 buf.as_mut_ptr().cast(),
254 &mut count,
255 )
256 })?;
257
258 let positions = (0..count as usize)
259 .map(|i| LedPosition::from_ffi(unsafe { buf[i].assume_init_ref() }))
261 .collect();
262 Ok(positions)
263 }
264
265 pub fn set_led_colors(&self, device_id: &DeviceId, colors: &[LedColor]) -> Result<()> {
270 error::check(unsafe {
274 ffi::CorsairSetLedColors(
275 device_id.as_ptr(),
276 colors.len() as c_int,
277 colors.as_ptr().cast(),
278 )
279 })
280 }
281
282 pub fn set_led_colors_buffer(&self, device_id: &DeviceId, colors: &[LedColor]) -> Result<()> {
285 error::check(unsafe {
287 ffi::CorsairSetLedColorsBuffer(
288 device_id.as_ptr(),
289 colors.len() as c_int,
290 colors.as_ptr().cast(),
291 )
292 })
293 }
294
295 pub fn flush_led_colors(&self) -> Result<()> {
300 let (sender, rx) = callback::flush_channel();
301 let ctx = callback::sender_as_context(&sender);
302
303 error::check(unsafe {
307 ffi::CorsairSetLedColorsFlushBufferAsync(Some(callback::flush_trampoline), ctx)
308 })?;
309
310 match rx.recv() {
312 Ok(code) => error::check(code),
313 Err(_) => Err(SdkError::NotConnected),
314 }
315 }
316
317 pub fn get_led_colors(&self, device_id: &DeviceId, colors: &mut [LedColor]) -> Result<()> {
322 error::check(unsafe {
325 ffi::CorsairGetLedColors(
326 device_id.as_ptr(),
327 colors.len() as c_int,
328 colors.as_mut_ptr().cast(),
329 )
330 })
331 }
332
333 pub fn get_led_luid_for_key_name(&self, device_id: &DeviceId, key_name: c_char) -> Result<u32> {
335 let mut luid: ffi::CorsairLedLuid = 0;
336 error::check(unsafe {
338 ffi::CorsairGetLedLuidForKeyName(device_id.as_ptr(), key_name, &mut luid)
339 })?;
340 Ok(luid)
341 }
342
343 pub fn set_layer_priority(&self, priority: u32) -> Result<()> {
345 error::check(unsafe { ffi::CorsairSetLayerPriority(priority) })
347 }
348
349 pub fn request_control(&self, device_id: &DeviceId, level: AccessLevel) -> Result<()> {
353 error::check(unsafe {
355 ffi::CorsairRequestControl(device_id.as_ptr(), level as ffi::CorsairAccessLevel)
356 })
357 }
358
359 pub fn release_control(&self, device_id: &DeviceId) -> Result<()> {
361 error::check(unsafe { ffi::CorsairReleaseControl(device_id.as_ptr()) })
363 }
364
365 pub fn subscribe_for_events(&self) -> Result<EventSubscription> {
371 let (sender, rx) = callback::event_channel();
372 EventSubscription::new(sender, rx)
373 }
374
375 #[cfg(feature = "async")]
382 pub fn subscribe_for_events_async(&self) -> Result<AsyncEventSubscription> {
383 let (sender, rx) = callback::async_event_channel();
384 AsyncEventSubscription::new(sender, rx)
385 }
386
387 #[cfg(feature = "async")]
394 pub async fn flush_led_colors_async(&self) -> Result<()> {
395 let (sender, mut rx) = callback::async_flush_channel();
396 let ctx = callback::async_sender_as_context(&sender);
397
398 error::check(unsafe {
403 ffi::CorsairSetLedColorsFlushBufferAsync(Some(callback::async_flush_trampoline), ctx)
404 })?;
405
406 match rx.recv().await {
407 Some(code) => error::check(code),
408 None => Err(SdkError::NotConnected),
409 }
410 }
411
412 pub fn configure_key_event(
414 &self,
415 device_id: &DeviceId,
416 key_id: MacroKeyId,
417 is_intercepted: bool,
418 ) -> Result<()> {
419 let config = ffi::CorsairKeyEventConfiguration {
420 keyId: key_id as ffi::CorsairMacroKeyId,
421 isIntercepted: is_intercepted,
422 };
423 error::check(unsafe { ffi::CorsairConfigureKeyEvent(device_id.as_ptr(), &config) })
425 }
426
427 pub fn get_device_property_info(
431 &self,
432 device_id: &DeviceId,
433 property: PropertyId,
434 index: u32,
435 ) -> Result<PropertyInfo> {
436 let mut data_type: ffi::CorsairDataType = 0;
437 let mut flags: u32 = 0;
438
439 error::check(unsafe {
441 ffi::CorsairGetDevicePropertyInfo(
442 device_id.as_ptr(),
443 property.to_ffi(),
444 index,
445 &mut data_type,
446 &mut flags,
447 )
448 })?;
449
450 Ok(PropertyInfo {
451 data_type: DataType::from_ffi(data_type).unwrap_or(DataType::Int32), flags: PropertyFlags::from_bits_truncate(flags),
453 })
454 }
455
456 pub fn read_device_property(
461 &self,
462 device_id: &DeviceId,
463 property: PropertyId,
464 index: u32,
465 ) -> Result<PropertyValue> {
466 let mut prop = MaybeUninit::<ffi::CorsairProperty>::zeroed();
467
468 error::check(unsafe {
471 ffi::CorsairReadDeviceProperty(
472 device_id.as_ptr(),
473 property.to_ffi(),
474 index,
475 prop.as_mut_ptr(),
476 )
477 })?;
478
479 let mut prop = unsafe { prop.assume_init() };
481 unsafe { PropertyValue::from_ffi_and_free(&mut prop) }.ok_or(SdkError::InvalidOperation)
485 }
486
487 pub fn write_device_property_bool(
489 &self,
490 device_id: &DeviceId,
491 property: PropertyId,
492 index: u32,
493 value: bool,
494 ) -> Result<()> {
495 let prop = crate::property::make_bool_property(value);
496 error::check(unsafe {
499 ffi::CorsairWriteDeviceProperty(device_id.as_ptr(), property.to_ffi(), index, &prop)
500 })
501 }
502
503 pub fn write_device_property_int32(
505 &self,
506 device_id: &DeviceId,
507 property: PropertyId,
508 index: u32,
509 value: i32,
510 ) -> Result<()> {
511 let prop = crate::property::make_int32_property(value);
512 error::check(unsafe {
514 ffi::CorsairWriteDeviceProperty(device_id.as_ptr(), property.to_ffi(), index, &prop)
515 })
516 }
517
518 pub fn write_device_property_float64(
520 &self,
521 device_id: &DeviceId,
522 property: PropertyId,
523 index: u32,
524 value: f64,
525 ) -> Result<()> {
526 let prop = crate::property::make_float64_property(value);
527 error::check(unsafe {
529 ffi::CorsairWriteDeviceProperty(device_id.as_ptr(), property.to_ffi(), index, &prop)
530 })
531 }
532}
533
534impl Drop for Session {
535 fn drop(&mut self) {
536 unsafe {
540 let _ = ffi::CorsairDisconnect();
541 }
542 }
543}