use std::mem::MaybeUninit;
use std::sync::mpsc;
use std::time::Duration;
use core::ffi::{c_char, c_int};
use cue_sdk_sys as ffi;
use crate::callback::{self, SessionStateChange};
use crate::device::{DeviceId, DeviceInfo, DeviceType};
use crate::error::{self, Result, SdkError};
#[cfg(feature = "async")]
use crate::event::AsyncEventSubscription;
use crate::event::{EventSubscription, MacroKeyId};
use crate::led::{LedColor, LedPosition};
use crate::property::{DataType, PropertyFlags, PropertyId, PropertyInfo, PropertyValue};
use std::ptr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Version {
pub major: i32,
pub minor: i32,
pub patch: i32,
}
impl Version {
pub(crate) fn from_ffi(v: &ffi::CorsairVersion) -> Self {
Self {
major: v.major,
minor: v.minor,
patch: v.patch,
}
}
}
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
#[derive(Debug, Clone, Copy)]
pub struct SessionDetails {
pub client_version: Version,
pub server_version: Version,
pub server_host_version: Version,
}
impl SessionDetails {
pub(crate) fn from_ffi(d: &ffi::CorsairSessionDetails) -> Self {
Self {
client_version: Version::from_ffi(&d.clientVersion),
server_version: Version::from_ffi(&d.serverVersion),
server_host_version: Version::from_ffi(&d.serverHostVersion),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SessionState {
Invalid,
Closed,
Connecting,
Timeout,
ConnectionRefused,
ConnectionLost,
Connected,
Unknown(u32),
}
impl SessionState {
pub(crate) fn from_ffi(raw: ffi::CorsairSessionState) -> Self {
match raw {
ffi::CorsairSessionState_CSS_Invalid => Self::Invalid,
ffi::CorsairSessionState_CSS_Closed => Self::Closed,
ffi::CorsairSessionState_CSS_Connecting => Self::Connecting,
ffi::CorsairSessionState_CSS_Timeout => Self::Timeout,
ffi::CorsairSessionState_CSS_ConnectionRefused => Self::ConnectionRefused,
ffi::CorsairSessionState_CSS_ConnectionLost => Self::ConnectionLost,
ffi::CorsairSessionState_CSS_Connected => Self::Connected,
other => Self::Unknown(other),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum AccessLevel {
Shared = ffi::CorsairAccessLevel_CAL_Shared,
ExclusiveLightingControl = ffi::CorsairAccessLevel_CAL_ExclusiveLightingControl,
ExclusiveKeyEventsListening = ffi::CorsairAccessLevel_CAL_ExclusiveKeyEventsListening,
ExclusiveLightingControlAndKeyEventsListening =
ffi::CorsairAccessLevel_CAL_ExclusiveLightingControlAndKeyEventsListening,
}
pub struct Session {
state_rx: mpsc::Receiver<SessionStateChange>,
}
unsafe impl Send for Session {}
unsafe impl Sync for Session {}
impl Session {
pub fn connect() -> Result<Self> {
let (tx, rx) = mpsc::channel();
callback::install_session_sender(tx);
error::check(unsafe {
ffi::CorsairConnect(Some(callback::session_state_trampoline), ptr::null_mut())
})?;
Ok(Self { state_rx: rx })
}
pub fn wait_for_connection(&self, timeout: Duration) -> Result<SessionDetails> {
let deadline = std::time::Instant::now() + timeout;
loop {
let remaining = deadline.saturating_duration_since(std::time::Instant::now());
if remaining.is_zero() {
return Err(SdkError::NotConnected);
}
match self.state_rx.recv_timeout(remaining) {
Ok(change) => {
let state = SessionState::from_ffi(change.state);
match state {
SessionState::Connected => {
return Ok(SessionDetails::from_ffi(&change.details));
}
SessionState::Connecting => continue,
_ => return Err(SdkError::NotConnected),
}
}
Err(mpsc::RecvTimeoutError::Timeout) => return Err(SdkError::NotConnected),
Err(mpsc::RecvTimeoutError::Disconnected) => return Err(SdkError::NotConnected),
}
}
}
pub fn details(&self) -> Result<SessionDetails> {
let mut raw = MaybeUninit::<ffi::CorsairSessionDetails>::uninit();
error::check(unsafe { ffi::CorsairGetSessionDetails(raw.as_mut_ptr()) })?;
Ok(SessionDetails::from_ffi(unsafe { &raw.assume_init() }))
}
pub fn get_devices(&self, filter: DeviceType) -> Result<Vec<DeviceInfo>> {
let ffi_filter = ffi::CorsairDeviceFilter {
deviceTypeMask: filter.bits() as c_int,
};
let mut buf = [MaybeUninit::<ffi::CorsairDeviceInfo>::uninit();
ffi::CORSAIR_DEVICE_COUNT_MAX as usize];
let mut count: c_int = 0;
error::check(unsafe {
ffi::CorsairGetDevices(
&ffi_filter,
buf.len() as c_int,
buf.as_mut_ptr().cast(),
&mut count,
)
})?;
let devices = (0..count as usize)
.map(|i| DeviceInfo::from_ffi(unsafe { buf[i].assume_init_ref() }))
.collect();
Ok(devices)
}
pub fn get_device_info(&self, device_id: &DeviceId) -> Result<DeviceInfo> {
let mut raw = MaybeUninit::<ffi::CorsairDeviceInfo>::uninit();
error::check(unsafe { ffi::CorsairGetDeviceInfo(device_id.as_ptr(), raw.as_mut_ptr()) })?;
Ok(DeviceInfo::from_ffi(unsafe { raw.assume_init_ref() }))
}
pub fn get_led_positions(&self, device_id: &DeviceId) -> Result<Vec<LedPosition>> {
let mut buf = [MaybeUninit::<ffi::CorsairLedPosition>::uninit();
ffi::CORSAIR_DEVICE_LEDCOUNT_MAX as usize];
let mut count: c_int = 0;
error::check(unsafe {
ffi::CorsairGetLedPositions(
device_id.as_ptr(),
buf.len() as c_int,
buf.as_mut_ptr().cast(),
&mut count,
)
})?;
let positions = (0..count as usize)
.map(|i| LedPosition::from_ffi(unsafe { buf[i].assume_init_ref() }))
.collect();
Ok(positions)
}
pub fn set_led_colors(&self, device_id: &DeviceId, colors: &[LedColor]) -> Result<()> {
error::check(unsafe {
ffi::CorsairSetLedColors(
device_id.as_ptr(),
colors.len() as c_int,
colors.as_ptr().cast(),
)
})
}
pub fn set_led_colors_buffer(&self, device_id: &DeviceId, colors: &[LedColor]) -> Result<()> {
error::check(unsafe {
ffi::CorsairSetLedColorsBuffer(
device_id.as_ptr(),
colors.len() as c_int,
colors.as_ptr().cast(),
)
})
}
pub fn flush_led_colors(&self) -> Result<()> {
let (sender, rx) = callback::flush_channel();
let ctx = callback::sender_as_context(&sender);
error::check(unsafe {
ffi::CorsairSetLedColorsFlushBufferAsync(Some(callback::flush_trampoline), ctx)
})?;
match rx.recv() {
Ok(code) => error::check(code),
Err(_) => Err(SdkError::NotConnected),
}
}
pub fn get_led_colors(&self, device_id: &DeviceId, colors: &mut [LedColor]) -> Result<()> {
error::check(unsafe {
ffi::CorsairGetLedColors(
device_id.as_ptr(),
colors.len() as c_int,
colors.as_mut_ptr().cast(),
)
})
}
pub fn get_led_luid_for_key_name(&self, device_id: &DeviceId, key_name: c_char) -> Result<u32> {
let mut luid: ffi::CorsairLedLuid = 0;
error::check(unsafe {
ffi::CorsairGetLedLuidForKeyName(device_id.as_ptr(), key_name, &mut luid)
})?;
Ok(luid)
}
pub fn set_layer_priority(&self, priority: u32) -> Result<()> {
error::check(unsafe { ffi::CorsairSetLayerPriority(priority) })
}
pub fn request_control(&self, device_id: &DeviceId, level: AccessLevel) -> Result<()> {
error::check(unsafe {
ffi::CorsairRequestControl(device_id.as_ptr(), level as ffi::CorsairAccessLevel)
})
}
pub fn release_control(&self, device_id: &DeviceId) -> Result<()> {
error::check(unsafe { ffi::CorsairReleaseControl(device_id.as_ptr()) })
}
pub fn subscribe_for_events(&self) -> Result<EventSubscription> {
let (sender, rx) = callback::event_channel();
EventSubscription::new(sender, rx)
}
#[cfg(feature = "async")]
pub fn subscribe_for_events_async(&self) -> Result<AsyncEventSubscription> {
let (sender, rx) = callback::async_event_channel();
AsyncEventSubscription::new(sender, rx)
}
#[cfg(feature = "async")]
pub async fn flush_led_colors_async(&self) -> Result<()> {
let (sender, mut rx) = callback::async_flush_channel();
let ctx = callback::async_sender_as_context(&sender);
error::check(unsafe {
ffi::CorsairSetLedColorsFlushBufferAsync(Some(callback::async_flush_trampoline), ctx)
})?;
match rx.recv().await {
Some(code) => error::check(code),
None => Err(SdkError::NotConnected),
}
}
pub fn configure_key_event(
&self,
device_id: &DeviceId,
key_id: MacroKeyId,
is_intercepted: bool,
) -> Result<()> {
let config = ffi::CorsairKeyEventConfiguration {
keyId: key_id as ffi::CorsairMacroKeyId,
isIntercepted: is_intercepted,
};
error::check(unsafe { ffi::CorsairConfigureKeyEvent(device_id.as_ptr(), &config) })
}
pub fn get_device_property_info(
&self,
device_id: &DeviceId,
property: PropertyId,
index: u32,
) -> Result<PropertyInfo> {
let mut data_type: ffi::CorsairDataType = 0;
let mut flags: u32 = 0;
error::check(unsafe {
ffi::CorsairGetDevicePropertyInfo(
device_id.as_ptr(),
property.to_ffi(),
index,
&mut data_type,
&mut flags,
)
})?;
Ok(PropertyInfo {
data_type: DataType::from_ffi(data_type).unwrap_or(DataType::Int32), flags: PropertyFlags::from_bits_truncate(flags),
})
}
pub fn read_device_property(
&self,
device_id: &DeviceId,
property: PropertyId,
index: u32,
) -> Result<PropertyValue> {
let mut prop = MaybeUninit::<ffi::CorsairProperty>::zeroed();
error::check(unsafe {
ffi::CorsairReadDeviceProperty(
device_id.as_ptr(),
property.to_ffi(),
index,
prop.as_mut_ptr(),
)
})?;
let mut prop = unsafe { prop.assume_init() };
unsafe { PropertyValue::from_ffi_and_free(&mut prop) }.ok_or(SdkError::InvalidOperation)
}
pub fn write_device_property_bool(
&self,
device_id: &DeviceId,
property: PropertyId,
index: u32,
value: bool,
) -> Result<()> {
let prop = crate::property::make_bool_property(value);
error::check(unsafe {
ffi::CorsairWriteDeviceProperty(device_id.as_ptr(), property.to_ffi(), index, &prop)
})
}
pub fn write_device_property_int32(
&self,
device_id: &DeviceId,
property: PropertyId,
index: u32,
value: i32,
) -> Result<()> {
let prop = crate::property::make_int32_property(value);
error::check(unsafe {
ffi::CorsairWriteDeviceProperty(device_id.as_ptr(), property.to_ffi(), index, &prop)
})
}
pub fn write_device_property_float64(
&self,
device_id: &DeviceId,
property: PropertyId,
index: u32,
value: f64,
) -> Result<()> {
let prop = crate::property::make_float64_property(value);
error::check(unsafe {
ffi::CorsairWriteDeviceProperty(device_id.as_ptr(), property.to_ffi(), index, &prop)
})
}
}
impl Drop for Session {
fn drop(&mut self) {
callback::clear_session_sender();
unsafe {
let _ = ffi::CorsairDisconnect();
}
}
}