Skip to main content

openseries/devices/
mod.rs

1use crate::protocol::{HidTransport, Identity};
2use crate::{OpenSeriesError, Result};
3use bitflags::bitflags;
4
5#[path = "headsets/mod.rs"]
6pub(crate) mod headset_models;
7#[path = "mice/mod.rs"]
8pub(crate) mod mouse_models;
9
10pub mod headsets {
11    pub use super::{
12        BatteryInfo, BatteryStatus, BluetoothCallVolumeMode, ChatmixInfo, EqualizerFilterType,
13        EqualizerInfo, EqualizerPreset, Headset, HeadsetStatus, ParametricEqualizerBand,
14        ParametricEqualizerInfo,
15    };
16}
17
18pub mod mice {
19    pub use super::{
20        BatteryInfo, BatteryStatus, Mouse, MouseSensitivityInfo, MouseZone, Persistence, RgbColor,
21    };
22}
23
24bitflags! {
25    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
26    pub struct Capabilities: u32 {
27        const SIDETONE = 1 << 0;
28        const BATTERY_STATUS = 1 << 1;
29        const CHATMIX = 1 << 2;
30        const INACTIVE_TIME = 1 << 3;
31        const EQUALIZER = 1 << 4;
32        const EQUALIZER_PRESET = 1 << 5;
33        const MOUSE_SENSITIVITY = 1 << 6;
34        const POLLING_RATE = 1 << 7;
35        const ILLUMINATION = 1 << 8;
36        const SLEEP_TIMER = 1 << 9;
37        const MICROPHONE_VOLUME = 1 << 10;
38        const MICROPHONE_MUTE_LED_BRIGHTNESS = 1 << 11;
39        const VOLUME_LIMITER = 1 << 12;
40        const PARAMETRIC_EQUALIZER = 1 << 13;
41        const BLUETOOTH_WHEN_POWERED_ON = 1 << 14;
42        const BLUETOOTH_CALL_VOLUME = 1 << 15;
43    }
44}
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47pub struct DeviceMetadata<'a> {
48    pub id: &'a str,
49    pub name: &'a str,
50    pub product_id: u16,
51    pub capabilities: Capabilities,
52}
53
54impl Capabilities {
55    pub const ALL: [(Capabilities, &'static str); 16] = [
56        (Self::SIDETONE, "Sidetone"),
57        (Self::BATTERY_STATUS, "BatteryStatus"),
58        (Self::CHATMIX, "ChatMix"),
59        (Self::INACTIVE_TIME, "InactiveTime"),
60        (Self::EQUALIZER, "Equalizer"),
61        (Self::EQUALIZER_PRESET, "EqualizerPreset"),
62        (Self::MOUSE_SENSITIVITY, "MouseSensitivity"),
63        (Self::POLLING_RATE, "PollingRate"),
64        (Self::ILLUMINATION, "Illumination"),
65        (Self::SLEEP_TIMER, "SleepTimer"),
66        (Self::MICROPHONE_VOLUME, "MicrophoneVolume"),
67        (
68            Self::MICROPHONE_MUTE_LED_BRIGHTNESS,
69            "MicrophoneMuteLedBrightness",
70        ),
71        (Self::VOLUME_LIMITER, "VolumeLimiter"),
72        (Self::PARAMETRIC_EQUALIZER, "ParametricEqualizer"),
73        (Self::BLUETOOTH_WHEN_POWERED_ON, "BluetoothWhenPoweredOn"),
74        (Self::BLUETOOTH_CALL_VOLUME, "BluetoothCallVolume"),
75    ];
76}
77
78#[derive(Clone, Copy, Debug, Eq, PartialEq)]
79pub enum BatteryStatus {
80    Disconnected,
81    Discharging,
82    Charging,
83    Charged,
84}
85
86impl std::fmt::Display for BatteryStatus {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        write!(f, "{self:?}")
89    }
90}
91
92#[derive(Clone, Debug, Eq, PartialEq)]
93pub struct BatteryInfo {
94    pub level_percentage: u16,
95    pub status: BatteryStatus,
96}
97
98#[derive(Clone, Copy, Debug, Eq, PartialEq)]
99pub struct ChatmixInfo {
100    pub level: u16,
101    pub game_volume_percentage: u16,
102    pub chat_volume_percentage: u16,
103}
104
105/// A single headset status response decoded into its supported values.
106#[derive(Clone, Debug, Eq, PartialEq)]
107pub struct HeadsetStatus {
108    pub battery: Option<BatteryInfo>,
109    pub chatmix: Option<ChatmixInfo>,
110}
111
112#[derive(Clone, Copy, Debug, PartialEq)]
113pub struct EqualizerInfo {
114    pub band_count: usize,
115    pub minimum: f32,
116    pub maximum: f32,
117    pub step: f32,
118}
119
120#[derive(Clone, Debug, PartialEq)]
121pub struct EqualizerPreset {
122    pub name: &'static str,
123    pub bands: &'static [f32; 10],
124}
125
126#[derive(Clone, Copy, Debug, Eq, PartialEq)]
127pub enum EqualizerFilterType {
128    Peaking,
129    LowPass,
130    HighPass,
131    LowShelf,
132    HighShelf,
133}
134
135#[derive(Clone, Copy, Debug, PartialEq)]
136pub struct ParametricEqualizerBand {
137    pub frequency: u16,
138    pub gain: f32,
139    pub q_factor: f32,
140    pub filter: EqualizerFilterType,
141}
142
143#[derive(Clone, Debug, PartialEq)]
144pub struct ParametricEqualizerInfo {
145    pub maximum_band_count: u8,
146    pub minimum_frequency: u16,
147    pub maximum_frequency: u16,
148    pub minimum_gain: f32,
149    pub maximum_gain: f32,
150    pub gain_step: f32,
151    pub minimum_q_factor: f32,
152    pub maximum_q_factor: f32,
153    pub supported_filters: &'static [EqualizerFilterType],
154}
155
156#[derive(Clone, Copy, Debug, Eq, PartialEq)]
157pub enum BluetoothCallVolumeMode {
158    Unchanged,
159    LowerBy12Decibels,
160    MuteGame,
161}
162
163#[derive(Clone, Copy, Debug, Eq, PartialEq)]
164pub struct MouseSensitivityInfo {
165    pub minimum: u16,
166    pub maximum: u16,
167    pub step: u16,
168    pub maximum_preset_count: u8,
169}
170
171#[derive(Clone, Copy, Debug, Eq, PartialEq)]
172pub struct RgbColor {
173    pub red: u8,
174    pub green: u8,
175    pub blue: u8,
176}
177
178#[derive(Clone, Copy, Debug, Eq, PartialEq)]
179#[repr(u8)]
180pub enum MouseZone {
181    Top,
182    Middle,
183    Bottom,
184    Logo,
185    Wheel,
186}
187
188impl std::fmt::Display for MouseZone {
189    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190        write!(f, "{self:?}")
191    }
192}
193
194#[derive(Clone, Copy, Debug, Eq, PartialEq)]
195pub enum Persistence {
196    Temporary,
197    Save,
198}
199
200pub(crate) struct DeviceContext {
201    pub(crate) identity: Identity,
202    pub(crate) transport: HidTransport,
203}
204
205impl DeviceContext {
206    pub(crate) fn new(identity: Identity, transport: HidTransport) -> Self {
207        Self {
208            identity,
209            transport,
210        }
211    }
212}
213
214pub(crate) trait DeviceProtocol: Send {
215    fn id(&self) -> &str;
216    fn name(&self) -> &str;
217    fn product_id(&self) -> u16;
218    fn supported_features(&self) -> Capabilities;
219}
220
221pub(crate) trait HeadsetProtocol: DeviceProtocol {
222    fn equalizer_info(&self) -> Result<EqualizerInfo> {
223        Err(unsupported(self, "equalizer"))
224    }
225    fn equalizer_presets(&self) -> Result<&'static [EqualizerPreset]> {
226        Err(unsupported(self, "equalizer presets"))
227    }
228    fn parametric_equalizer_info(&self) -> Option<ParametricEqualizerInfo> {
229        None
230    }
231    fn get_battery(&mut self) -> Result<BatteryInfo> {
232        Err(unsupported(self, "battery status"))
233    }
234    fn get_chatmix(&mut self) -> Result<ChatmixInfo> {
235        Err(unsupported(self, "ChatMix"))
236    }
237    fn get_status(&mut self) -> Result<HeadsetStatus> {
238        let features = self.supported_features();
239        Ok(HeadsetStatus {
240            battery: if features.contains(Capabilities::BATTERY_STATUS) {
241                Some(self.get_battery()?)
242            } else {
243                None
244            },
245            chatmix: if features.contains(Capabilities::CHATMIX) {
246                Some(self.get_chatmix()?)
247            } else {
248                None
249            },
250        })
251    }
252    fn set_sidetone(&mut self, _level: u8) -> Result<()> {
253        Err(unsupported(self, "sidetone control"))
254    }
255    fn set_inactive_time(&mut self, _minutes: u16) -> Result<()> {
256        Err(unsupported(self, "inactive time control"))
257    }
258    fn set_equalizer(&mut self, _bands: &[f32]) -> Result<()> {
259        Err(unsupported(self, "equalizer"))
260    }
261    fn set_equalizer_preset(&mut self, _preset: usize) -> Result<()> {
262        Err(unsupported(self, "equalizer presets"))
263    }
264    fn set_microphone_volume(&mut self, _volume: u8) -> Result<()> {
265        Err(unsupported(self, "microphone volume control"))
266    }
267    fn set_microphone_mute_led_brightness(&mut self, _brightness: u8) -> Result<()> {
268        Err(unsupported(self, "microphone mute LED brightness control"))
269    }
270    fn set_volume_limiter(&mut self, _enabled: bool) -> Result<()> {
271        Err(unsupported(self, "volume limiter control"))
272    }
273    fn set_parametric_equalizer(&mut self, _bands: &[ParametricEqualizerBand]) -> Result<()> {
274        Err(unsupported(self, "parametric equalizer"))
275    }
276    fn set_bluetooth_when_powered_on(&mut self, _enabled: bool) -> Result<()> {
277        Err(unsupported(self, "Bluetooth power-on control"))
278    }
279    fn set_bluetooth_call_volume(&mut self, _mode: BluetoothCallVolumeMode) -> Result<()> {
280        Err(unsupported(self, "Bluetooth call volume control"))
281    }
282}
283
284pub(crate) trait MouseProtocol: DeviceProtocol {
285    fn sensitivity_info(&self) -> Result<MouseSensitivityInfo> {
286        Err(unsupported(self, "sensitivity control"))
287    }
288    fn supported_polling_rates(&self) -> Result<&'static [u16]> {
289        Err(unsupported(self, "polling rate control"))
290    }
291    fn supported_illumination_zones(&self) -> Result<&'static [MouseZone]> {
292        Err(unsupported(self, "illumination control"))
293    }
294    fn set_sensitivity(&mut self, _dpi_presets: &[u16]) -> Result<()> {
295        Err(unsupported(self, "sensitivity control"))
296    }
297    fn set_polling_rate(&mut self, _polling_rate: u16) -> Result<()> {
298        Err(unsupported(self, "polling rate control"))
299    }
300    fn set_illumination(
301        &mut self,
302        _zone: MouseZone,
303        _color: RgbColor,
304        _persistence: Persistence,
305    ) -> Result<()> {
306        Err(unsupported(self, "illumination control"))
307    }
308    fn set_sleep_timer(&mut self, _minutes: u8) -> Result<()> {
309        Err(unsupported(self, "sleep timer"))
310    }
311    fn get_battery(&mut self) -> Result<BatteryInfo> {
312        Err(unsupported(self, "battery status"))
313    }
314}
315
316fn unsupported<T: DeviceProtocol + ?Sized>(device: &T, feature: &str) -> OpenSeriesError {
317    OpenSeriesError::Unsupported(format!("{} does not support {feature}.", device.name()))
318}
319
320pub struct Headset {
321    inner: Box<dyn HeadsetProtocol>,
322}
323
324impl Headset {
325    pub(crate) fn new(inner: Box<dyn HeadsetProtocol>) -> Self {
326        Self { inner }
327    }
328
329    pub fn id(&self) -> &str {
330        self.inner.id()
331    }
332    pub fn name(&self) -> &str {
333        self.inner.name()
334    }
335    pub fn product_id(&self) -> u16 {
336        self.inner.product_id()
337    }
338    pub fn capabilities(&self) -> Capabilities {
339        self.inner.supported_features()
340    }
341    pub fn metadata(&self) -> DeviceMetadata<'_> {
342        DeviceMetadata {
343            id: self.id(),
344            name: self.name(),
345            product_id: self.product_id(),
346            capabilities: self.capabilities(),
347        }
348    }
349    pub fn equalizer_info(&self) -> Result<EqualizerInfo> {
350        self.inner.equalizer_info()
351    }
352    pub fn equalizer_presets(&self) -> Result<&'static [EqualizerPreset]> {
353        self.inner.equalizer_presets()
354    }
355    pub fn parametric_equalizer_info(&self) -> Option<ParametricEqualizerInfo> {
356        self.inner.parametric_equalizer_info()
357    }
358    pub fn get_battery(&mut self) -> Result<BatteryInfo> {
359        self.inner.get_battery()
360    }
361    pub fn get_chatmix(&mut self) -> Result<ChatmixInfo> {
362        self.inner.get_chatmix()
363    }
364    pub fn get_status(&mut self) -> Result<HeadsetStatus> {
365        self.inner.get_status()
366    }
367    pub fn set_sidetone(&mut self, level: u8) -> Result<()> {
368        self.inner.set_sidetone(level)
369    }
370    pub fn set_inactive_time(&mut self, minutes: u16) -> Result<()> {
371        self.inner.set_inactive_time(minutes)
372    }
373    pub fn set_equalizer(&mut self, bands: &[f32; 10]) -> Result<()> {
374        self.inner.set_equalizer(bands)
375    }
376    pub fn set_equalizer_preset(&mut self, preset: usize) -> Result<()> {
377        self.inner.set_equalizer_preset(preset)
378    }
379    pub fn set_microphone_volume(&mut self, volume: u8) -> Result<()> {
380        self.inner.set_microphone_volume(volume)
381    }
382    pub fn set_microphone_mute_led_brightness(&mut self, brightness: u8) -> Result<()> {
383        self.inner.set_microphone_mute_led_brightness(brightness)
384    }
385    pub fn set_volume_limiter(&mut self, enabled: bool) -> Result<()> {
386        self.inner.set_volume_limiter(enabled)
387    }
388    pub fn set_parametric_equalizer(&mut self, bands: &[ParametricEqualizerBand]) -> Result<()> {
389        self.inner.set_parametric_equalizer(bands)
390    }
391    pub fn set_bluetooth_when_powered_on(&mut self, enabled: bool) -> Result<()> {
392        self.inner.set_bluetooth_when_powered_on(enabled)
393    }
394    pub fn set_bluetooth_call_volume(&mut self, mode: BluetoothCallVolumeMode) -> Result<()> {
395        self.inner.set_bluetooth_call_volume(mode)
396    }
397}
398
399pub struct Mouse {
400    inner: Box<dyn MouseProtocol>,
401}
402
403impl Mouse {
404    pub(crate) fn new(inner: Box<dyn MouseProtocol>) -> Self {
405        Self { inner }
406    }
407    pub fn id(&self) -> &str {
408        self.inner.id()
409    }
410    pub fn name(&self) -> &str {
411        self.inner.name()
412    }
413    pub fn product_id(&self) -> u16 {
414        self.inner.product_id()
415    }
416    pub fn capabilities(&self) -> Capabilities {
417        self.inner.supported_features()
418    }
419    pub fn metadata(&self) -> DeviceMetadata<'_> {
420        DeviceMetadata {
421            id: self.id(),
422            name: self.name(),
423            product_id: self.product_id(),
424            capabilities: self.capabilities(),
425        }
426    }
427    pub fn sensitivity_info(&self) -> Result<MouseSensitivityInfo> {
428        self.inner.sensitivity_info()
429    }
430    pub fn supported_polling_rates(&self) -> Result<&'static [u16]> {
431        self.inner.supported_polling_rates()
432    }
433    pub fn supported_illumination_zones(&self) -> Result<&'static [MouseZone]> {
434        self.inner.supported_illumination_zones()
435    }
436    pub fn set_sensitivity(&mut self, values: &[u16]) -> Result<()> {
437        self.inner.set_sensitivity(values)
438    }
439    pub fn set_polling_rate(&mut self, rate: u16) -> Result<()> {
440        self.inner.set_polling_rate(rate)
441    }
442    pub fn set_illumination(
443        &mut self,
444        zone: MouseZone,
445        color: RgbColor,
446        persistence: Persistence,
447    ) -> Result<()> {
448        self.inner.set_illumination(zone, color, persistence)
449    }
450    pub fn set_sleep_timer(&mut self, minutes: u8) -> Result<()> {
451        self.inner.set_sleep_timer(minutes)
452    }
453    pub fn get_battery(&mut self) -> Result<BatteryInfo> {
454        self.inner.get_battery()
455    }
456}
457
458pub enum Device {
459    Headset(Headset),
460    Mouse(Mouse),
461}
462
463impl Device {
464    pub fn id(&self) -> &str {
465        match self {
466            Self::Headset(d) => d.id(),
467            Self::Mouse(d) => d.id(),
468        }
469    }
470    pub fn name(&self) -> &str {
471        match self {
472            Self::Headset(d) => d.name(),
473            Self::Mouse(d) => d.name(),
474        }
475    }
476    pub fn product_id(&self) -> u16 {
477        match self {
478            Self::Headset(d) => d.product_id(),
479            Self::Mouse(d) => d.product_id(),
480        }
481    }
482    pub fn capabilities(&self) -> Capabilities {
483        match self {
484            Self::Headset(d) => d.capabilities(),
485            Self::Mouse(d) => d.capabilities(),
486        }
487    }
488    pub fn metadata(&self) -> DeviceMetadata<'_> {
489        DeviceMetadata {
490            id: self.id(),
491            name: self.name(),
492            product_id: self.product_id(),
493            capabilities: self.capabilities(),
494        }
495    }
496
497    pub fn as_headset_mut(&mut self) -> Option<&mut Headset> {
498        match self {
499            Self::Headset(device) => Some(device),
500            Self::Mouse(_) => None,
501        }
502    }
503    pub fn as_mouse_mut(&mut self) -> Option<&mut Mouse> {
504        match self {
505            Self::Mouse(device) => Some(device),
506            Self::Headset(_) => None,
507        }
508    }
509}