mirror of
https://github.com/anotherhadi/settuings.git
synced 2026-10-05 16:18:24 +02:00
166 lines
4.0 KiB
Go
166 lines
4.0 KiB
Go
package audio
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"charm.land/bubbles/v2/key"
|
|
tea "charm.land/bubbletea/v2"
|
|
"charm.land/lipgloss/v2"
|
|
ilovetui "github.com/anotherhadi/ilovetui"
|
|
"github.com/anotherhadi/settuings/internal/audio"
|
|
"github.com/anotherhadi/settuings/internal/keys"
|
|
)
|
|
|
|
const volumeStep = 5
|
|
|
|
type outputState struct {
|
|
loading bool
|
|
err error
|
|
devices []audio.Device
|
|
cursor int
|
|
pending bool
|
|
feedback string
|
|
}
|
|
|
|
func newOutputState() outputState {
|
|
return outputState{loading: true}
|
|
}
|
|
|
|
func (m *Model) handleOutputList(msg outputListMsg) {
|
|
m.output.loading = false
|
|
m.output.err = msg.err
|
|
if msg.err != nil {
|
|
return
|
|
}
|
|
m.output.devices = msg.devices
|
|
if m.output.cursor >= len(m.output.devices) {
|
|
m.output.cursor = max(0, len(m.output.devices)-1)
|
|
}
|
|
}
|
|
|
|
func (m *Model) handleOutputAction(msg outputActionMsg) tea.Cmd {
|
|
m.output.pending = false
|
|
if msg.err != nil {
|
|
m.output.feedback = msg.err.Error()
|
|
return nil
|
|
}
|
|
return fetchOutputCmd()
|
|
}
|
|
|
|
func (m Model) selectedOutput() *audio.Device {
|
|
if m.output.cursor < 0 || m.output.cursor >= len(m.output.devices) {
|
|
return nil
|
|
}
|
|
return &m.output.devices[m.output.cursor]
|
|
}
|
|
|
|
func (m *Model) updateOutput(msg tea.Msg) tea.Cmd {
|
|
keyMsg, ok := msg.(tea.KeyPressMsg)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
g := keys.Keys.Global
|
|
a := keys.Keys.Audio
|
|
|
|
switch {
|
|
case key.Matches(keyMsg, g.Up):
|
|
if m.output.cursor > 0 {
|
|
m.output.cursor--
|
|
}
|
|
case key.Matches(keyMsg, g.Down):
|
|
if m.output.cursor < len(m.output.devices)-1 {
|
|
m.output.cursor++
|
|
}
|
|
case key.Matches(keyMsg, g.Left):
|
|
return m.adjustOutputVolume(-volumeStep)
|
|
case key.Matches(keyMsg, g.Right):
|
|
return m.adjustOutputVolume(volumeStep)
|
|
case key.Matches(keyMsg, a.Refresh):
|
|
m.output.pending = true
|
|
return fetchOutputCmd()
|
|
case key.Matches(keyMsg, a.Mute):
|
|
dev := m.selectedOutput()
|
|
if dev == nil {
|
|
return nil
|
|
}
|
|
m.output.feedback = ""
|
|
m.output.pending = true
|
|
return outputToggleMuteCmd(dev.ID)
|
|
case key.Matches(keyMsg, a.Select):
|
|
dev := m.selectedOutput()
|
|
if dev == nil {
|
|
return nil
|
|
}
|
|
if dev.Default {
|
|
m.output.feedback = "Already the default output."
|
|
return nil
|
|
}
|
|
m.output.feedback = ""
|
|
m.output.pending = true
|
|
return outputSetDefaultCmd(dev.ID)
|
|
case key.Matches(keyMsg, a.Test):
|
|
dev := m.selectedOutput()
|
|
if dev == nil {
|
|
return nil
|
|
}
|
|
return tea.Batch(outputTestToneCmd(dev.ID), notifyCmd("Audio", "Playing test sound on "+dev.Name+"…"))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// adjustOutputVolume nudges the selected device's volume by delta, updating
|
|
// it locally right away so the bar responds instantly instead of waiting on
|
|
// the round trip to wpctl and back.
|
|
func (m *Model) adjustOutputVolume(delta int) tea.Cmd {
|
|
dev := m.selectedOutput()
|
|
if dev == nil {
|
|
return nil
|
|
}
|
|
pct := dev.Volume + delta
|
|
if pct < 0 {
|
|
pct = 0
|
|
} else if pct > 100 {
|
|
pct = 100
|
|
}
|
|
dev.Volume = pct
|
|
m.output.feedback = ""
|
|
return outputSetVolumeCmd(dev.ID, pct)
|
|
}
|
|
|
|
func (m Model) renderOutput(width, height int) string {
|
|
if m.output.loading {
|
|
return ilovetui.S.Faint.Render("Loading output devices…")
|
|
}
|
|
|
|
header := lipgloss.NewStyle().Bold(true).Foreground(ilovetui.S.Subtle).Render("OUTPUT DEVICES")
|
|
if m.output.pending {
|
|
header += mutedStyle().Render(" refreshing…")
|
|
}
|
|
|
|
if m.output.err != nil {
|
|
return lipgloss.JoinVertical(lipgloss.Left, header, "", errorStyle().Render(m.output.err.Error()))
|
|
}
|
|
if len(m.output.devices) == 0 {
|
|
return lipgloss.JoinVertical(lipgloss.Left, header, "", ilovetui.S.Faint.Render("No output devices found."))
|
|
}
|
|
|
|
available := (height - 3) / deviceRowHeight
|
|
if available < 1 {
|
|
available = 1
|
|
}
|
|
p, start, end := paginate(m.output.cursor, len(m.output.devices), available)
|
|
|
|
var rows []string
|
|
for i := start; i < end; i++ {
|
|
rows = append(rows, renderDeviceRow(m.output.devices[i], i == m.output.cursor, width))
|
|
}
|
|
list := strings.Join(rows, "\n\n")
|
|
|
|
footer := renderPaginatorDots(p)
|
|
if m.output.feedback != "" {
|
|
footer = subtleStyle().Render(m.output.feedback)
|
|
}
|
|
|
|
return lipgloss.JoinVertical(lipgloss.Left, header, "", list, footer)
|
|
}
|