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};
16use std::ptr;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct Version {
25 pub major: i32,
26 pub minor: i32,
27 pub patch: i32,
28}
29
30impl Version {
31 pub(crate) fn from_ffi(v: &ffi::CorsairVersion) -> Self {
32 Self {
33 major: v.major,
34 minor: v.minor,
35 patch: v.patch,
36 }
37 }
38}
39
40impl std::fmt::Display for Version {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
43 }
44}
45
46#[derive(Debug, Clone, Copy)]
52pub struct SessionDetails {
53 pub client_version: Version,
54 pub server_version: Version,
55 pub server_host_version: Version,
56}
57
58impl SessionDetails {
59 pub(crate) fn from_ffi(d: &ffi::CorsairSessionDetails) -> Self {
60 Self {
61 client_version: Version::from_ffi(&d.clientVersion),
62 server_version: Version::from_ffi(&d.serverVersion),
63 server_host_version: Version::from_ffi(&d.serverHostVersion),
64 }
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum SessionState {
75 Invalid,
76 Closed,
77 Connecting,
78 Timeout,
79 ConnectionRefused,
80 ConnectionLost,
81 Connected,
82 Unknown(u32),
83}
84
85impl SessionState {
86 pub(crate) fn from_ffi(raw: ffi::CorsairSessionState) -> Self {
87 match raw {
88 ffi::CorsairSessionState_CSS_Invalid => Self::Invalid,
89 ffi::CorsairSessionState_CSS_Closed => Self::Closed,
90 ffi::CorsairSessionState_CSS_Connecting => Self::Connecting,
91 ffi::CorsairSessionState_CSS_Timeout => Self::Timeout,
92 ffi::CorsairSessionState_CSS_ConnectionRefused => Self::ConnectionRefused,
93 ffi::CorsairSessionState_CSS_ConnectionLost => Self::ConnectionLost,
94 ffi::CorsairSessionState_CSS_Connected => Self::Connected,
95 other => Self::Unknown(other),
96 }
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106#[repr(u32)]
107pub enum AccessLevel {
108 Shared = ffi::CorsairAccessLevel_CAL_Shared,
109 ExclusiveLightingControl = ffi::CorsairAccessLevel_CAL_ExclusiveLightingControl,
110 ExclusiveKeyEventsListening = ffi::CorsairAccessLevel_CAL_ExclusiveKeyEventsListening,
111 ExclusiveLightingControlAndKeyEventsListening =
112 ffi::CorsairAccessLevel_CAL_ExclusiveLightingControlAndKeyEventsListening,
113}
114
115pub struct Session {
126 state_rx: mpsc::Receiver<SessionStateChange>,
127}
128
129unsafe impl Send for Session {}
133unsafe impl Sync for Session {}
134
135impl Session {
136 pub fn connect() -> Result<Self> {
142 let (tx, rx) = mpsc::channel();
143 callback::install_session_sender(tx);
144
145 error::check(unsafe {
149 ffi::CorsairConnect(Some(callback::session_state_trampoline), ptr::null_mut())
150 })?;
151
152 Ok(Self { state_rx: rx })
153 }
154
155 pub fn wait_for_connection(&self, timeout: Duration) -> Result<SessionDetails> {
164 let deadline = std::time::Instant::now() + timeout;
165 loop {
166 let remaining = deadline.saturating_duration_since(std::time::Instant::now());
167 if remaining.is_zero() {
168 return Err(SdkError::NotConnected);
169 }
170 match self.state_rx.recv_timeout(remaining) {
171 Ok(change) => {
172 let state = SessionState::from_ffi(change.state);
173 match state {
174 SessionState::Connected => {
175 return Ok(SessionDetails::from_ffi(&change.details));
176 }
177 SessionState::Connecting => continue,
178 _ => return Err(SdkError::NotConnected),
179 }
180 }
181 Err(mpsc::RecvTimeoutError::Timeout) => return Err(SdkError::NotConnected),
182 Err(mpsc::RecvTimeoutError::Disconnected) => return Err(SdkError::NotConnected),
183 }
184 }
185 }
186
187 pub fn details(&self) -> Result<SessionDetails> {
189 let mut raw = MaybeUninit::<ffi::CorsairSessionDetails>::uninit();
190 error::check(unsafe { ffi::CorsairGetSessionDetails(raw.as_mut_ptr()) })?;
193 Ok(SessionDetails::from_ffi(unsafe { &raw.assume_init() }))
195 }
196
197 pub fn get_devices(&self, filter: DeviceType) -> Result<Vec<DeviceInfo>> {
201 let ffi_filter = ffi::CorsairDeviceFilter {
202 deviceTypeMask: filter.bits() as c_int,
203 };
204 let mut buf = [MaybeUninit::<ffi::CorsairDeviceInfo>::uninit();
205 ffi::CORSAIR_DEVICE_COUNT_MAX as usize];
206 let mut count: c_int = 0;
207
208 error::check(unsafe {
211 ffi::CorsairGetDevices(
212 &ffi_filter,
213 buf.len() as c_int,
214 buf.as_mut_ptr().cast(),
215 &mut count,
216 )
217 })?;
218
219 let devices = (0..count as usize)
220 .map(|i| DeviceInfo::from_ffi(unsafe { buf[i].assume_init_ref() }))
222 .collect();
223 Ok(devices)
224 }
225
226 pub fn get_device_info(&self, device_id: &DeviceId) -> Result<DeviceInfo> {
228 let mut raw = MaybeUninit::<ffi::CorsairDeviceInfo>::uninit();
229 error::check(unsafe { ffi::CorsairGetDeviceInfo(device_id.as_ptr(), raw.as_mut_ptr()) })?;
232 Ok(DeviceInfo::from_ffi(unsafe { raw.assume_init_ref() }))
234 }
235
236 pub fn get_led_positions(&self, device_id: &DeviceId) -> Result<Vec<LedPosition>> {
240 let mut buf = [MaybeUninit::<ffi::CorsairLedPosition>::uninit();
241 ffi::CORSAIR_DEVICE_LEDCOUNT_MAX as usize];
242 let mut count: c_int = 0;
243
244 error::check(unsafe {
247 ffi::CorsairGetLedPositions(
248 device_id.as_ptr(),
249 buf.len() as c_int,
250 buf.as_mut_ptr().cast(),
251 &mut count,
252 )
253 })?;
254
255 let positions = (0..count as usize)
256 .map(|i| LedPosition::from_ffi(unsafe { buf[i].assume_init_ref() }))
258 .collect();
259 Ok(positions)
260 }
261
262 pub fn set_led_colors(&self, device_id: &DeviceId, colors: &[LedColor]) -> Result<()> {
267 error::check(unsafe {
271 ffi::CorsairSetLedColors(
272 device_id.as_ptr(),
273 colors.len() as c_int,
274 colors.as_ptr().cast(),
275 )
276 })
277 }
278
279 pub fn set_led_colors_buffer(&self, device_id: &DeviceId, colors: &[LedColor]) -> Result<()> {
282 error::check(unsafe {
284 ffi::CorsairSetLedColorsBuffer(
285 device_id.as_ptr(),
286 colors.len() as c_int,
287 colors.as_ptr().cast(),
288 )
289 })
290 }
291
292 pub fn flush_led_colors(&self) -> Result<()> {
297 let (sender, rx) = callback::flush_channel();
298 let ctx = callback::sender_as_context(&sender);
299
300 error::check(unsafe {
304 ffi::CorsairSetLedColorsFlushBufferAsync(Some(callback::flush_trampoline), ctx)
305 })?;
306
307 match rx.recv() {
309 Ok(code) => error::check(code),
310 Err(_) => Err(SdkError::NotConnected),
311 }
312 }
313
314 pub fn get_led_colors(&self, device_id: &DeviceId, colors: &mut [LedColor]) -> Result<()> {
319 error::check(unsafe {
322 ffi::CorsairGetLedColors(
323 device_id.as_ptr(),
324 colors.len() as c_int,
325 colors.as_mut_ptr().cast(),
326 )
327 })
328 }
329
330 pub fn get_led_luid_for_key_name(&self, device_id: &DeviceId, key_name: c_char) -> Result<u32> {
332 let mut luid: ffi::CorsairLedLuid = 0;
333 error::check(unsafe {
335 ffi::CorsairGetLedLuidForKeyName(device_id.as_ptr(), key_name, &mut luid)
336 })?;
337 Ok(luid)
338 }
339
340 pub fn set_layer_priority(&self, priority: u32) -> Result<()> {
342 error::check(unsafe { ffi::CorsairSetLayerPriority(priority) })
344 }
345
346 pub fn request_control(&self, device_id: &DeviceId, level: AccessLevel) -> Result<()> {
350 error::check(unsafe {
352 ffi::CorsairRequestControl(device_id.as_ptr(), level as ffi::CorsairAccessLevel)
353 })
354 }
355
356 pub fn release_control(&self, device_id: &DeviceId) -> Result<()> {
358 error::check(unsafe { ffi::CorsairReleaseControl(device_id.as_ptr()) })
360 }
361
362 pub fn subscribe_for_events(&self) -> Result<EventSubscription> {
368 let (sender, rx) = callback::event_channel();
369 EventSubscription::new(sender, rx)
370 }
371
372 #[cfg(feature = "async")]
379 pub fn subscribe_for_events_async(&self) -> Result<AsyncEventSubscription> {
380 let (sender, rx) = callback::async_event_channel();
381 AsyncEventSubscription::new(sender, rx)
382 }
383
384 #[cfg(feature = "async")]
391 pub async fn flush_led_colors_async(&self) -> Result<()> {
392 let (sender, mut rx) = callback::async_flush_channel();
393 let ctx = callback::async_sender_as_context(&sender);
394
395 error::check(unsafe {
400 ffi::CorsairSetLedColorsFlushBufferAsync(Some(callback::async_flush_trampoline), ctx)
401 })?;
402
403 match rx.recv().await {
404 Some(code) => error::check(code),
405 None => Err(SdkError::NotConnected),
406 }
407 }
408
409 pub fn configure_key_event(
411 &self,
412 device_id: &DeviceId,
413 key_id: MacroKeyId,
414 is_intercepted: bool,
415 ) -> Result<()> {
416 let config = ffi::CorsairKeyEventConfiguration {
417 keyId: key_id as ffi::CorsairMacroKeyId,
418 isIntercepted: is_intercepted,
419 };
420 error::check(unsafe { ffi::CorsairConfigureKeyEvent(device_id.as_ptr(), &config) })
422 }
423
424 pub fn get_device_property_info(
428 &self,
429 device_id: &DeviceId,
430 property: PropertyId,
431 index: u32,
432 ) -> Result<PropertyInfo> {
433 let mut data_type: ffi::CorsairDataType = 0;
434 let mut flags: u32 = 0;
435
436 error::check(unsafe {
438 ffi::CorsairGetDevicePropertyInfo(
439 device_id.as_ptr(),
440 property.to_ffi(),
441 index,
442 &mut data_type,
443 &mut flags,
444 )
445 })?;
446
447 Ok(PropertyInfo {
448 data_type: DataType::from_ffi(data_type).unwrap_or(DataType::Int32), flags: PropertyFlags::from_bits_truncate(flags),
450 })
451 }
452
453 pub fn read_device_property(
458 &self,
459 device_id: &DeviceId,
460 property: PropertyId,
461 index: u32,
462 ) -> Result<PropertyValue> {
463 let mut prop = MaybeUninit::<ffi::CorsairProperty>::zeroed();
464
465 error::check(unsafe {
468 ffi::CorsairReadDeviceProperty(
469 device_id.as_ptr(),
470 property.to_ffi(),
471 index,
472 prop.as_mut_ptr(),
473 )
474 })?;
475
476 let mut prop = unsafe { prop.assume_init() };
478 unsafe { PropertyValue::from_ffi_and_free(&mut prop) }.ok_or(SdkError::InvalidOperation)
482 }
483
484 pub fn write_device_property_bool(
486 &self,
487 device_id: &DeviceId,
488 property: PropertyId,
489 index: u32,
490 value: bool,
491 ) -> Result<()> {
492 let prop = crate::property::make_bool_property(value);
493 error::check(unsafe {
496 ffi::CorsairWriteDeviceProperty(device_id.as_ptr(), property.to_ffi(), index, &prop)
497 })
498 }
499
500 pub fn write_device_property_int32(
502 &self,
503 device_id: &DeviceId,
504 property: PropertyId,
505 index: u32,
506 value: i32,
507 ) -> Result<()> {
508 let prop = crate::property::make_int32_property(value);
509 error::check(unsafe {
511 ffi::CorsairWriteDeviceProperty(device_id.as_ptr(), property.to_ffi(), index, &prop)
512 })
513 }
514
515 pub fn write_device_property_float64(
517 &self,
518 device_id: &DeviceId,
519 property: PropertyId,
520 index: u32,
521 value: f64,
522 ) -> Result<()> {
523 let prop = crate::property::make_float64_property(value);
524 error::check(unsafe {
526 ffi::CorsairWriteDeviceProperty(device_id.as_ptr(), property.to_ffi(), index, &prop)
527 })
528 }
529}
530
531impl Drop for Session {
532 fn drop(&mut self) {
533 callback::clear_session_sender();
536
537 unsafe {
541 let _ = ffi::CorsairDisconnect();
542 }
543 }
544}