feat: initiale Implementierung von corsairctl

Rust CLI-Tool für Corsair Bragi-Geräte (HS80 RGB Wireless, etc.).
Implementiert Protokoll-Kern, HID-Kommunikation, BragiDevice mit
RAII-Lifecycle, CLI-Subcommands (battery, sidetone, led, info, json,
udev), ALSA-Sidetone-Steuerung und Waybar-JSON-Output.

24 Unit-Tests für Packet-Bau, Response-Parsing und Property-Enums.
This commit is contained in:
2026-03-27 17:34:37 +01:00
commit c5f8625345
19 changed files with 1501 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
// ABOUTME: ALSA-Mixer Integration für Sidetone-Steuerung.
// ABOUTME: Liest und setzt den Sidetone-Wert über den ALSA-Mixer der Corsair-Soundkarte.
use alsa::mixer::{Mixer, SelemChannelId, SelemId};
use crate::error::Result;
const CARD_NAME: &str = "Gaming";
const SIDETONE_VOLUME: &str = "Sidetone Playback Volume";
const SIDETONE_SWITCH: &str = "Sidetone Playback Switch";
/// Findet die ALSA-Card-Nummer für die Corsair Gaming Soundkarte.
fn find_card() -> Result<String> {
// ALSA-Cards durchiterieren
for card_idx in 0..32 {
let name = format!("hw:{card_idx}");
if let Ok(ctl) = alsa::Ctl::new(&name, false) {
if let Ok(info) = ctl.card_info() {
let card_name = info.get_name().unwrap_or_default();
if card_name.contains(CARD_NAME) {
return Ok(name);
}
}
}
}
Err(crate::error::CorsairError::DeviceNotFound)
}
/// Liest den aktuellen Sidetone-Level (0-23).
pub fn get_level() -> Result<i64> {
let card = find_card()?;
let mixer = Mixer::new(&card, false)?;
let selem_id = SelemId::new(SIDETONE_VOLUME, 0);
let selem = mixer
.find_selem(&selem_id)
.ok_or(crate::error::CorsairError::DeviceNotFound)?;
let value = selem.get_playback_volume(SelemChannelId::FrontLeft)?;
Ok(value)
}
/// Setzt den Sidetone-Level (0-23).
pub fn set_level(level: i64) -> Result<()> {
let card = find_card()?;
let mixer = Mixer::new(&card, false)?;
// Volume setzen
let volume_id = SelemId::new(SIDETONE_VOLUME, 0);
let volume_elem = mixer
.find_selem(&volume_id)
.ok_or(crate::error::CorsairError::DeviceNotFound)?;
volume_elem.set_playback_volume_all(level)?;
// Switch aktivieren falls Level > 0
let switch_id = SelemId::new(SIDETONE_SWITCH, 0);
if let Some(switch_elem) = mixer.find_selem(&switch_id) {
let enabled = if level > 0 { 1 } else { 0 };
let _ = switch_elem.set_playback_switch_all(enabled);
}
Ok(())
}