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#[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}