Skip to main content

cue_sdk/
property.rs

1use core::ffi::c_int;
2use std::ffi::CStr;
3
4use bitflags::bitflags;
5use cue_sdk_sys as ffi;
6
7// ---------------------------------------------------------------------------
8// PropertyId
9// ---------------------------------------------------------------------------
10
11/// Identifier for a device property.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[repr(u32)]
14pub enum PropertyId {
15    PropertyArray = ffi::CorsairDevicePropertyId_CDPI_PropertyArray,
16    MicEnabled = ffi::CorsairDevicePropertyId_CDPI_MicEnabled,
17    SurroundSoundEnabled = ffi::CorsairDevicePropertyId_CDPI_SurroundSoundEnabled,
18    SidetoneEnabled = ffi::CorsairDevicePropertyId_CDPI_SidetoneEnabled,
19    EqualizerPreset = ffi::CorsairDevicePropertyId_CDPI_EqualizerPreset,
20    PhysicalLayout = ffi::CorsairDevicePropertyId_CDPI_PhysicalLayout,
21    LogicalLayout = ffi::CorsairDevicePropertyId_CDPI_LogicalLayout,
22    MacroKeyArray = ffi::CorsairDevicePropertyId_CDPI_MacroKeyArray,
23    BatteryLevel = ffi::CorsairDevicePropertyId_CDPI_BatteryLevel,
24    ChannelLedCount = ffi::CorsairDevicePropertyId_CDPI_ChannelLedCount,
25    ChannelDeviceCount = ffi::CorsairDevicePropertyId_CDPI_ChannelDeviceCount,
26    ChannelDeviceLedCountArray = ffi::CorsairDevicePropertyId_CDPI_ChannelDeviceLedCountArray,
27    ChannelDeviceTypeArray = ffi::CorsairDevicePropertyId_CDPI_ChannelDeviceTypeArray,
28}
29
30impl PropertyId {
31    /// Convert to the FFI constant.
32    pub(crate) fn to_ffi(self) -> ffi::CorsairDevicePropertyId {
33        self as ffi::CorsairDevicePropertyId
34    }
35}
36
37// ---------------------------------------------------------------------------
38// PropertyFlags
39// ---------------------------------------------------------------------------
40
41bitflags! {
42    /// Flags describing what operations are allowed on a property.
43    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
44    pub struct PropertyFlags: u32 {
45        const CAN_READ  = ffi::CorsairPropertyFlag_CPF_CanRead;
46        const CAN_WRITE = ffi::CorsairPropertyFlag_CPF_CanWrite;
47        const INDEXED   = ffi::CorsairPropertyFlag_CPF_Indexed;
48    }
49}
50
51// ---------------------------------------------------------------------------
52// DataType
53// ---------------------------------------------------------------------------
54
55/// The data type of a property value.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum DataType {
58    Boolean,
59    Int32,
60    Float64,
61    String,
62    BooleanArray,
63    Int32Array,
64    Float64Array,
65    StringArray,
66}
67
68impl DataType {
69    pub(crate) fn from_ffi(raw: ffi::CorsairDataType) -> Option<Self> {
70        match raw {
71            ffi::CorsairDataType_CT_Boolean => Some(Self::Boolean),
72            ffi::CorsairDataType_CT_Int32 => Some(Self::Int32),
73            ffi::CorsairDataType_CT_Float64 => Some(Self::Float64),
74            ffi::CorsairDataType_CT_String => Some(Self::String),
75            ffi::CorsairDataType_CT_Boolean_Array => Some(Self::BooleanArray),
76            ffi::CorsairDataType_CT_Int32_Array => Some(Self::Int32Array),
77            ffi::CorsairDataType_CT_Float64_Array => Some(Self::Float64Array),
78            ffi::CorsairDataType_CT_String_Array => Some(Self::StringArray),
79            _ => None,
80        }
81    }
82}
83
84// ---------------------------------------------------------------------------
85// PropertyInfo
86// ---------------------------------------------------------------------------
87
88/// Metadata about a device property (type and flags).
89#[derive(Debug, Clone, Copy)]
90pub struct PropertyInfo {
91    pub data_type: DataType,
92    pub flags: PropertyFlags,
93}
94
95// ---------------------------------------------------------------------------
96// PropertyValue
97// ---------------------------------------------------------------------------
98
99/// An owned copy of a property value read from the SDK.
100///
101/// The SDK-allocated memory is freed immediately after the value is copied out,
102/// so there are no dangling pointers.
103#[derive(Debug, Clone)]
104pub enum PropertyValue {
105    Boolean(bool),
106    Int32(i32),
107    Float64(f64),
108    String(std::string::String),
109    BooleanArray(Vec<bool>),
110    Int32Array(Vec<i32>),
111    Float64Array(Vec<f64>),
112    StringArray(Vec<std::string::String>),
113}
114
115impl PropertyValue {
116    /// Extract an owned value from the raw FFI property, then free it.
117    ///
118    /// # Safety
119    ///
120    /// `prop` must point to a valid `CorsairProperty` returned by
121    /// `CorsairReadDeviceProperty`, whose `value` union variant matches `type_`.
122    /// After this call the SDK memory is freed via `CorsairFreeProperty`.
123    pub(crate) unsafe fn from_ffi_and_free(prop: &mut ffi::CorsairProperty) -> Option<Self> {
124        // In each arm below, we access the union variant that corresponds to
125        // `prop.type_`.  This is safe because the caller guarantees that the
126        // property was just initialised by the SDK with a matching type.
127        let val = match prop.type_ {
128            ffi::CorsairDataType_CT_Boolean => {
129                // SAFETY: `type_` is `CT_Boolean`, so `value.boolean` is active.
130                Some(PropertyValue::Boolean(unsafe { prop.value.boolean }))
131            }
132            ffi::CorsairDataType_CT_Int32 => {
133                // SAFETY: `type_` is `CT_Int32`, so `value.int32` is active.
134                Some(PropertyValue::Int32(unsafe { prop.value.int32 }))
135            }
136            ffi::CorsairDataType_CT_Float64 => {
137                // SAFETY: `type_` is `CT_Float64`, so `value.float64` is active.
138                Some(PropertyValue::Float64(unsafe { prop.value.float64 }))
139            }
140            ffi::CorsairDataType_CT_String => {
141                // SAFETY: `type_` is `CT_String`, so `value.string` is active.
142                let ptr = unsafe { prop.value.string };
143                if ptr.is_null() {
144                    Some(PropertyValue::String(std::string::String::new()))
145                } else {
146                    // SAFETY: The SDK returns a valid null-terminated C string.
147                    let s = unsafe { CStr::from_ptr(ptr) }
148                        .to_string_lossy()
149                        .into_owned();
150                    Some(PropertyValue::String(s))
151                }
152            }
153            ffi::CorsairDataType_CT_Boolean_Array => {
154                // SAFETY: `type_` is `CT_Boolean_Array`, so `value.boolean_array` is active.
155                let arr = unsafe { prop.value.boolean_array };
156                let slice = if arr.items.is_null() || arr.count == 0 {
157                    &[]
158                } else {
159                    // SAFETY: The SDK guarantees `items` points to `count` valid bools.
160                    unsafe { std::slice::from_raw_parts(arr.items, arr.count as usize) }
161                };
162                Some(PropertyValue::BooleanArray(slice.to_vec()))
163            }
164            ffi::CorsairDataType_CT_Int32_Array => {
165                // SAFETY: `type_` is `CT_Int32_Array`, so `value.int32_array` is active.
166                let arr = unsafe { prop.value.int32_array };
167                let slice = if arr.items.is_null() || arr.count == 0 {
168                    &[]
169                } else {
170                    // SAFETY: The SDK guarantees `items` points to `count` valid i32s.
171                    unsafe { std::slice::from_raw_parts(arr.items, arr.count as usize) }
172                };
173                Some(PropertyValue::Int32Array(slice.to_vec()))
174            }
175            ffi::CorsairDataType_CT_Float64_Array => {
176                // SAFETY: `type_` is `CT_Float64_Array`, so `value.float64_array` is active.
177                let arr = unsafe { prop.value.float64_array };
178                let slice = if arr.items.is_null() || arr.count == 0 {
179                    &[]
180                } else {
181                    // SAFETY: The SDK guarantees `items` points to `count` valid f64s.
182                    unsafe { std::slice::from_raw_parts(arr.items, arr.count as usize) }
183                };
184                Some(PropertyValue::Float64Array(slice.to_vec()))
185            }
186            ffi::CorsairDataType_CT_String_Array => {
187                // SAFETY: `type_` is `CT_String_Array`, so `value.string_array` is active.
188                let arr = unsafe { prop.value.string_array };
189                let ptrs = if arr.items.is_null() || arr.count == 0 {
190                    &[]
191                } else {
192                    // SAFETY: The SDK guarantees `items` points to `count` valid char pointers.
193                    unsafe { std::slice::from_raw_parts(arr.items, arr.count as usize) }
194                };
195                let strings = ptrs
196                    .iter()
197                    .map(|&p| {
198                        if p.is_null() {
199                            std::string::String::new()
200                        } else {
201                            // SAFETY: Each non-null pointer is a valid C string from the SDK.
202                            unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned()
203                        }
204                    })
205                    .collect();
206                Some(PropertyValue::StringArray(strings))
207            }
208            _ => None,
209        };
210
211        // SAFETY: `CorsairFreeProperty` releases SDK-allocated memory inside
212        // the property.  It is safe to call on any property returned by
213        // `CorsairReadDeviceProperty`, and must be called exactly once.
214        unsafe {
215            let _ = ffi::CorsairFreeProperty(prop as *mut ffi::CorsairProperty);
216        }
217
218        val
219    }
220}
221
222/// Create a `CorsairProperty` for writing a boolean value.
223pub(crate) fn make_bool_property(value: bool) -> ffi::CorsairProperty {
224    // SAFETY: `CorsairProperty` is `#[repr(C)]` with no padding requirements
225    // that zero-init would violate.  We immediately overwrite `type_` and `value`.
226    let mut prop: ffi::CorsairProperty = unsafe { std::mem::zeroed() };
227    prop.type_ = ffi::CorsairDataType_CT_Boolean;
228    prop.value = ffi::CorsairDataValue { boolean: value };
229    prop
230}
231
232/// Create a `CorsairProperty` for writing an i32 value.
233pub(crate) fn make_int32_property(value: i32) -> ffi::CorsairProperty {
234    // SAFETY: Same as `make_bool_property`.
235    let mut prop: ffi::CorsairProperty = unsafe { std::mem::zeroed() };
236    prop.type_ = ffi::CorsairDataType_CT_Int32;
237    prop.value = ffi::CorsairDataValue {
238        int32: value as c_int,
239    };
240    prop
241}
242
243/// Create a `CorsairProperty` for writing an f64 value.
244pub(crate) fn make_float64_property(value: f64) -> ffi::CorsairProperty {
245    // SAFETY: Same as `make_bool_property`.
246    let mut prop: ffi::CorsairProperty = unsafe { std::mem::zeroed() };
247    prop.type_ = ffi::CorsairDataType_CT_Float64;
248    prop.value = ffi::CorsairDataValue { float64: value };
249    prop
250}