Files
settuings/internal/audio/status.go
T
Hadi 59b31e5ca3 init
Signed-off-by: Hadi <[email protected]>
2026-08-13 00:58:37 +02:00

98 lines
2.6 KiB
Go

package audio
import (
"regexp"
"strconv"
"strings"
)
// statusEntry is one line parsed out of a `wpctl status` section (Sinks,
// Sources, or Streams).
type statusEntry struct {
ID string
Default bool
Name string
Volume int // 0-100, only meaningful when HasVolume is true
Muted bool
HasVolume bool
}
// entryLineRe matches one tree entry, e.g.:
//
// " │ * 57. Ryzen HD Audio Controller Speaker [vol: 0.90 MUTED]"
// " 110. pw-play"
//
// Leading tree-drawing characters and the optional "*" (default marker) are
// stripped; the remainder is the id and the free-form rest of the line.
var entryLineRe = regexp.MustCompile(`^[\s│├└─]*(\*)?\s*(\d+)\.\s+(.+)$`)
var volBracketRe = regexp.MustCompile(`^(.*?)\s*\[vol:\s*([0-9.]+)(\s+MUTED)?\]$`)
// status returns the raw output of `wpctl status`.
func status() (string, error) {
return run("status")
}
// parseSection extracts the entries listed under headerLine (e.g. "Sinks:",
// "Sources:", "Streams:") within the "Audio" domain block of `wpctl status`
// output, stopping at the first blank line. Port/link sub-lines (which
// contain " > ") are tree children of a stream entry, not entries of their
// own, and are skipped.
func parseSection(out, headerLine string) []statusEntry {
lines := strings.Split(out, "\n")
audioStart := -1
for i, l := range lines {
if strings.TrimSpace(l) == "Audio" {
audioStart = i
break
}
}
if audioStart == -1 {
return nil
}
sectionStart := -1
for i := audioStart; i < len(lines); i++ {
if strings.HasSuffix(strings.TrimSpace(lines[i]), headerLine) {
sectionStart = i + 1
break
}
}
if sectionStart == -1 {
return nil
}
var entries []statusEntry
for i := sectionStart; i < len(lines); i++ {
line := lines[i]
trimmed := strings.TrimSpace(line)
// A blank line ends the whole Audio block (e.g. after Streams:); a
// line ending in ":" is the next section header (e.g. "Sources:").
// Either marks the end of this section.
if trimmed == "" || strings.HasSuffix(trimmed, ":") {
break
}
m := entryLineRe.FindStringSubmatch(line)
if m == nil {
continue
}
id, isDefault, rest := m[2], m[1] == "*", strings.TrimSpace(m[3])
if strings.Contains(rest, " > ") {
continue // a port/link sub-line, not an entry
}
entry := statusEntry{ID: id, Default: isDefault, Name: rest}
if vm := volBracketRe.FindStringSubmatch(rest); vm != nil {
entry.Name = strings.TrimSpace(vm[1])
if v, err := strconv.ParseFloat(vm[2], 64); err == nil {
entry.Volume = int(v*100 + 0.5)
entry.HasVolume = true
}
entry.Muted = vm[3] != ""
}
entries = append(entries, entry)
}
return entries
}