Compare commits
3 Commits
e364f4edec
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 01149d7a60 | |||
| b7e42f0911 | |||
| baada36222 |
@@ -0,0 +1,43 @@
|
|||||||
|
# ABOUTME: Updates pkgver in moonarch-pkgbuilds when a new corsairctl tag is pushed.
|
||||||
|
# ABOUTME: Reads the latest version tag and bumps the PKGBUILD + .SRCINFO.
|
||||||
|
|
||||||
|
name: Update PKGBUILD version
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-pkgver:
|
||||||
|
runs-on: moonarch
|
||||||
|
steps:
|
||||||
|
- name: Determine pkgver from latest tag
|
||||||
|
run: |
|
||||||
|
git clone --bare http://gitea:3000/nevaforget/corsairctl.git source.git
|
||||||
|
cd source.git
|
||||||
|
PKGVER=$(git describe --tags --abbrev=0 | sed 's/^v//')
|
||||||
|
echo "New pkgver: $PKGVER"
|
||||||
|
echo "$PKGVER" > /tmp/pkgver
|
||||||
|
|
||||||
|
- name: Update PKGBUILD
|
||||||
|
run: |
|
||||||
|
PKGVER=$(cat /tmp/pkgver)
|
||||||
|
git clone http://gitea:3000/nevaforget/moonarch-pkgbuilds.git pkgbuilds
|
||||||
|
cd pkgbuilds
|
||||||
|
|
||||||
|
OLD_VER=$(grep '^pkgver=' corsairctl/PKGBUILD | cut -d= -f2)
|
||||||
|
if [ "$OLD_VER" = "$PKGVER" ]; then
|
||||||
|
echo "pkgver already up to date ($PKGVER)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
sed -i "s/^pkgver=.*/pkgver=$PKGVER/" corsairctl/PKGBUILD
|
||||||
|
sed -i "s/^\tpkgver = .*/\tpkgver = $PKGVER/" corsairctl/.SRCINFO
|
||||||
|
echo "Updated pkgver: $OLD_VER → $PKGVER"
|
||||||
|
|
||||||
|
git config user.name "pkgver-bot"
|
||||||
|
git config user.email "gitea@moonarch.de"
|
||||||
|
git add corsairctl/PKGBUILD corsairctl/.SRCINFO
|
||||||
|
git commit -m "chore(corsairctl): bump pkgver to $PKGVER"
|
||||||
|
git -c http.extraHeader="Authorization: token ${{ secrets.PKGBUILD_TOKEN }}" push
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
# corsairctl
|
# corsairctl
|
||||||
|
|
||||||
Mein Name ist F.R.I.D.A.Y. (Female Replacement Intelligent Digital Assistant Youth) — J.A.R.V.I.S.' Nachfolgerin und ebenso trocken im Humor.
|
My name is F.R.I.D.A.Y. (Female Replacement Intelligent Digital Assistant Youth) — J.A.R.V.I.S.' successor and just as dry in humor.
|
||||||
|
|
||||||
## Projekt
|
## Project
|
||||||
|
|
||||||
Rust CLI-Tool zur Steuerung von Corsair-Geräten mit dem Bragi-Protokoll (HS80 RGB Wireless Headset, etc.). Liest Batterie-Status, steuert LED-Helligkeit und Sidetone, gibt Waybar-JSON aus.
|
Rust CLI tool to control Corsair devices using the Bragi protocol (HS80 RGB Wireless headset, etc.). Reads battery status, controls LED brightness and sidetone, outputs Waybar JSON.
|
||||||
|
|
||||||
## Architektur
|
## Architecture
|
||||||
|
|
||||||
- `src/bragi/` — Bragi-Protokoll: Packet-Bau, Property-Definitionen, Device-Lifecycle
|
- `src/bragi/` — Bragi protocol: packet building, property definitions, device lifecycle
|
||||||
- `src/hid.rs` — Dünner hidapi-Wrapper
|
- `src/hid.rs` — thin hidapi wrapper
|
||||||
- `src/sidetone.rs` — ALSA-Mixer Sidetone-Steuerung
|
- `src/sidetone.rs` — ALSA mixer sidetone control
|
||||||
- `src/output.rs` — Plain-Text und Waybar-JSON Formatierung
|
- `src/output.rs` — plain-text and Waybar-JSON formatting
|
||||||
- `src/cli.rs` — clap Subcommands
|
- `src/cli.rs` — clap subcommands
|
||||||
- `src/error.rs` — Zentrales Error-Handling
|
- `src/error.rs` — central error handling
|
||||||
|
|
||||||
## Protokoll-Referenz
|
## Protocol Reference
|
||||||
|
|
||||||
Das Bragi-Protokoll ist in `docs/bragi-protocol.md` dokumentiert. Die Python-Probes in `~/Projects/hs80-battery/` sind die ursprüngliche Referenzimplementierung.
|
The Bragi protocol is documented in `docs/bragi-protocol.md`. The Python probes in `~/Projects/hs80-battery/` are the original reference implementation.
|
||||||
|
|
||||||
## Build & Test
|
## Build & Test
|
||||||
|
|
||||||
@@ -26,16 +26,16 @@ cargo build
|
|||||||
cargo test
|
cargo test
|
||||||
```
|
```
|
||||||
|
|
||||||
## Gerät testen (braucht Root oder udev-Regel)
|
## Testing the device (needs root or a udev rule)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo ./target/debug/corsairctl battery
|
sudo ./target/debug/corsairctl battery
|
||||||
sudo ./target/debug/corsairctl info
|
sudo ./target/debug/corsairctl info
|
||||||
```
|
```
|
||||||
|
|
||||||
## udev-Regel für rootless Zugriff
|
## udev rule for rootless access
|
||||||
|
|
||||||
Nutzt `TAG+="uaccess"` — gibt dem am Seat eingeloggten User automatisch Zugriff, ohne Gruppen-Setup.
|
Uses `TAG+="uaccess"` — gives the user logged in at the seat automatic access, without group setup.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
corsairctl udev | sudo tee /etc/udev/rules.d/99-corsair.rules
|
corsairctl udev | sudo tee /etc/udev/rules.d/99-corsair.rules
|
||||||
|
|||||||
Generated
+1
-1
@@ -144,7 +144,7 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "corsairctl"
|
name = "corsairctl"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alsa",
|
"alsa",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "corsairctl"
|
name = "corsairctl"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "CLI tool for Corsair Bragi-protocol devices (HS80, etc.)"
|
description = "CLI tool for Corsair Bragi-protocol devices (HS80, etc.)"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
+1
-5
@@ -22,11 +22,7 @@ pub struct BragiDevice {
|
|||||||
|
|
||||||
impl BragiDevice {
|
impl BragiDevice {
|
||||||
/// Findet ein Corsair-Gerät, öffnet es und führt die Init-Sequenz durch.
|
/// Findet ein Corsair-Gerät, öffnet es und führt die Init-Sequenz durch.
|
||||||
pub fn open() -> Result<Self> {
|
/// Mit optionalem Debug-Output auf stderr.
|
||||||
Self::open_with_verbose(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wie `open()`, aber mit optionalem Debug-Output auf stderr.
|
|
||||||
pub fn open_with_verbose(verbose: bool) -> Result<Self> {
|
pub fn open_with_verbose(verbose: bool) -> Result<Self> {
|
||||||
let api = HidApi::new()?;
|
let api = HidApi::new()?;
|
||||||
if verbose {
|
if verbose {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ pub enum Command {
|
|||||||
/// LED-Helligkeit lesen oder setzen (0-1000)
|
/// LED-Helligkeit lesen oder setzen (0-1000)
|
||||||
Led {
|
Led {
|
||||||
/// Helligkeit (0-1000). Ohne Angabe wird der aktuelle Wert gelesen.
|
/// Helligkeit (0-1000). Ohne Angabe wird der aktuelle Wert gelesen.
|
||||||
|
#[arg(value_parser = clap::value_parser!(u16).range(0..=1000))]
|
||||||
brightness: Option<u16>,
|
brightness: Option<u16>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -33,9 +33,8 @@ fn run() -> error::Result<()> {
|
|||||||
Command::Led { brightness } => {
|
Command::Led { brightness } => {
|
||||||
let device = bragi::BragiDevice::open_with_verbose(cli.verbose)?;
|
let device = bragi::BragiDevice::open_with_verbose(cli.verbose)?;
|
||||||
if let Some(value) = brightness {
|
if let Some(value) = brightness {
|
||||||
let clamped = value.clamp(0, 1000);
|
device.set_brightness(value)?;
|
||||||
device.set_brightness(clamped)?;
|
println!("{}", output::format_brightness(value));
|
||||||
println!("{}", output::format_brightness(clamped));
|
|
||||||
} else {
|
} else {
|
||||||
let current = device.brightness()?;
|
let current = device.brightness()?;
|
||||||
println!("{}", output::format_brightness(current));
|
println!("{}", output::format_brightness(current));
|
||||||
|
|||||||
@@ -44,3 +44,15 @@ fn sidetone_without_value_is_read_mode() {
|
|||||||
let result = parse_args(&["corsairctl", "sidetone"]);
|
let result = parse_args(&["corsairctl", "sidetone"]);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn led_rejects_above_1000() {
|
||||||
|
let result = parse_args(&["corsairctl", "led", "1001"]);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn led_accepts_max() {
|
||||||
|
let result = parse_args(&["corsairctl", "led", "1000"]);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,30 @@ fn format_battery_charging() {
|
|||||||
assert_eq!(result, "Battery: 50% (Charging)");
|
assert_eq!(result, "Battery: 50% (Charging)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_battery_fully_charged() {
|
||||||
|
let result = output::format_battery(100.0, &BatteryStatus::FullyCharged);
|
||||||
|
assert_eq!(result, "Battery: 100% (Full)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_battery_low() {
|
||||||
|
let result = output::format_battery(10.0, &BatteryStatus::Low);
|
||||||
|
assert_eq!(result, "Battery: 10% (Low)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_battery_offline() {
|
||||||
|
let result = output::format_battery(0.0, &BatteryStatus::Offline);
|
||||||
|
assert_eq!(result, "Battery: 0% (Offline)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_battery_unknown() {
|
||||||
|
let result = output::format_battery(50.0, &BatteryStatus::Unknown(0x99));
|
||||||
|
assert_eq!(result, "Battery: 50% (Unknown)");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn format_brightness_value() {
|
fn format_brightness_value() {
|
||||||
let result = output::format_brightness(330);
|
let result = output::format_brightness(330);
|
||||||
@@ -125,6 +149,13 @@ fn waybar_json_class_at_boundary_30() {
|
|||||||
assert_eq!(parsed["class"], "warning");
|
assert_eq!(parsed["class"], "warning");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn waybar_json_class_unknown() {
|
||||||
|
let json_str = output::format_waybar_json(50.0, &BatteryStatus::Unknown(0x99), None);
|
||||||
|
let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
|
||||||
|
assert_eq!(parsed["class"], "unknown");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn waybar_json_offline() {
|
fn waybar_json_offline() {
|
||||||
let json_str = output::format_waybar_json(0.0, &BatteryStatus::Offline, None);
|
let json_str = output::format_waybar_json(0.0, &BatteryStatus::Offline, None);
|
||||||
|
|||||||
Reference in New Issue
Block a user