From cf9746c8de7488b65720fadd46ff676068775cc3 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sat, 5 Sep 2026 21:53:57 -0400 Subject: [PATCH] Add alt+d deafen keyboard shortcut. --- admin.go | 3 +- cmd/barnard-ui/main.go | 3 -- cmd/barnard-ui/main_test.go | 6 ++-- config/hotkey_config.go | 1 + config/user_config.go | 5 +++ config/user_config_test.go | 4 +++ extras/barnard-sound.sh | 8 +++++ gumble/gumble/user.go | 11 ++++++ gumble/gumble/user_self_audio_test.go | 50 +++++++++++++++++++++++++++ ui.go | 40 +++++++++++++++++++++ 10 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 gumble/gumble/user_self_audio_test.go diff --git a/admin.go b/admin.go index 7d064f3..6e02c26 100644 --- a/admin.go +++ b/admin.go @@ -240,6 +240,7 @@ func (b *Barnard) keyboardHelpItems() []uiterm.TreeItem { adminItem{label: hotkeyLabel(hotkeys.AdminMenu) + ": open actions and settings"}, adminItem{label: hotkeyLabel(hotkeys.Talk) + ": toggle transmission"}, adminItem{label: hotkeyLabel(hotkeys.SelfMuteToggle) + ": toggle self-mute"}, + adminItem{label: hotkeyLabel(hotkeys.SelfDeafenToggle) + ": toggle self-deafen"}, adminItem{label: hotkeyLabel(hotkeys.NoiseSuppressionToggle) + ": toggle noise suppression"}, adminItem{label: hotkeyLabel(hotkeys.AGCToggle) + ": toggle automatic gain control"}, adminItem{label: hotkeyLabel(hotkeys.RecordToggle) + ": toggle recording"}, @@ -276,7 +277,7 @@ func hotkeyLabel(key *uiterm.Key) string { uiterm.KeyF5: "F5", uiterm.KeyF6: "F6", uiterm.KeyF7: "F7", uiterm.KeyF8: "F8", uiterm.KeyF9: "F9", uiterm.KeyF10: "F10", uiterm.KeyF11: "F11", uiterm.KeyF12: "F12", uiterm.KeyCtrlL: "Control+L", uiterm.KeyCtrlQ: "Control+Q", uiterm.KeyCtrlR: "Control+R", uiterm.KeyCtrlT: "Control+T", - uiterm.KeyAltM: "Alt+M", uiterm.KeyTab: "Tab", uiterm.KeyHome: "Home", uiterm.KeyEnd: "End", + uiterm.KeyAltD: "Alt+D", uiterm.KeyAltM: "Alt+M", uiterm.KeyTab: "Tab", uiterm.KeyHome: "Home", uiterm.KeyEnd: "End", uiterm.KeyPgup: "Page Up", uiterm.KeyPgdn: "Page Down", uiterm.KeyArrowUp: "Up Arrow", uiterm.KeyArrowDown: "Down Arrow", uiterm.KeyArrowLeft: "Left Arrow", uiterm.KeyArrowRight: "Right Arrow", uiterm.KeyBackspace: "Backspace", uiterm.KeyBackspace2: "Backspace", uiterm.KeyM: "M", diff --git a/cmd/barnard-ui/main.go b/cmd/barnard-ui/main.go index ac0ca4b..2ee5437 100644 --- a/cmd/barnard-ui/main.go +++ b/cmd/barnard-ui/main.go @@ -600,8 +600,6 @@ func (app *App) run() error { err = app.manage_certificate() case "Logs": err = app.manage_logs() - case "About barnard-ui": - err = app.ui.message("barnard-ui is the native Go interface for managing Barnard servers, certificates, and logs. It does not require Python or GNU Dialog.") } if err != nil { return err @@ -616,7 +614,6 @@ func main_menu_options() []string { "Remove server", "Manage Certificate", "Logs", - "About barnard-ui", "Exit", } } diff --git a/cmd/barnard-ui/main_test.go b/cmd/barnard-ui/main_test.go index d8ef570..c82b351 100644 --- a/cmd/barnard-ui/main_test.go +++ b/cmd/barnard-ui/main_test.go @@ -196,13 +196,13 @@ func TestConnectionArgsNeverExposePassword(t *testing.T) { } } -func TestProgramIdentityAndAboutMenuUseBarnardUI(t *testing.T) { +func TestProgramIdentityAndMainMenuUseBarnardUI(t *testing.T) { if programName != "barnard-ui" { t.Fatalf("programName = %q", programName) } options := main_menu_options() - if !slices.Contains(options, "About barnard-ui") { - t.Fatalf("main menu lacks About barnard-ui: %v", options) + if slices.Contains(options, "About barnard-ui") { + t.Fatalf("main menu still contains About barnard-ui: %v", options) } for _, option := range options { if strings.Contains(option, "go-ui") { diff --git a/config/hotkey_config.go b/config/hotkey_config.go index d1114bb..9d3a7a7 100644 --- a/config/hotkey_config.go +++ b/config/hotkey_config.go @@ -24,4 +24,5 @@ type Hotkeys struct { NoiseSuppressionToggle *uiterm.Key AGCToggle *uiterm.Key SelfMuteToggle *uiterm.Key + SelfDeafenToggle *uiterm.Key } diff --git a/config/user_config.go b/config/user_config.go index 0b10e48..546d654 100644 --- a/config/user_config.go +++ b/config/user_config.go @@ -182,6 +182,7 @@ func (c *Config) ensureHotkeys() { NoiseSuppressionToggle: key(uiterm.KeyF9), AGCToggle: key(uiterm.KeyF12), SelfMuteToggle: key(uiterm.KeyAltM), + SelfDeafenToggle: key(uiterm.KeyAltD), } hotkeys := c.config.Hotkeys if hotkeys.Talk == nil { @@ -243,6 +244,9 @@ func (c *Config) ensureHotkeys() { if hotkeys.SelfMuteToggle == nil { hotkeys.SelfMuteToggle = defaults.SelfMuteToggle } + if hotkeys.SelfDeafenToggle == nil { + hotkeys.SelfDeafenToggle = defaults.SelfDeafenToggle + } } func hotkeyAlreadyUsed(hotkeys *Hotkeys, wanted uiterm.Key) bool { @@ -252,6 +256,7 @@ func hotkeyAlreadyUsed(hotkeys *Hotkeys, wanted uiterm.Key) bool { hotkeys.SwitchViews, hotkeys.ClearOutput, hotkeys.ScrollUp, hotkeys.ScrollDown, hotkeys.ScrollToTop, hotkeys.ScrollToBottom, hotkeys.AdminMenu, hotkeys.NoiseSuppressionToggle, hotkeys.AGCToggle, hotkeys.SelfMuteToggle, + hotkeys.SelfDeafenToggle, } for _, binding := range bindings { if binding != nil && *binding == wanted { diff --git a/config/user_config_test.go b/config/user_config_test.go index 106402e..9e0ed2b 100644 --- a/config/user_config_test.go +++ b/config/user_config_test.go @@ -49,6 +49,9 @@ func TestConfigBackfillsRecordingDefaults(t *testing.T) { if got := *cfg.GetHotkeys().RecordToggle; got != uiterm.KeyCtrlR { t.Fatalf("expected record toggle ctrl_r, got %s", got) } + if got := cfg.GetHotkeys().SelfDeafenToggle; got == nil || *got != uiterm.KeyAltD { + t.Fatalf("expected self deafen toggle alt_d, got %v", got) + } if cfg.GetHotkeys().AdminMenu == nil { t.Fatal("expected admin menu hotkey to be backfilled") } @@ -96,6 +99,7 @@ func TestNewHotkeyDefaults(t *testing.T) { {name: "volume reset", got: hotkeys.VolumeReset, want: uiterm.KeyBackspace}, {name: "tree mute", got: hotkeys.MuteToggle, want: uiterm.KeyM}, {name: "self mute", got: hotkeys.SelfMuteToggle, want: uiterm.KeyAltM}, + {name: "self deafen", got: hotkeys.SelfDeafenToggle, want: uiterm.KeyAltD}, {name: "actions menu", got: hotkeys.AdminMenu, want: uiterm.KeyF10}, {name: "keyboard help", got: hotkeys.HelpMenu, want: uiterm.KeyF1}, {name: "exit", got: hotkeys.Exit, want: uiterm.KeyCtrlQ}, diff --git a/extras/barnard-sound.sh b/extras/barnard-sound.sh index 4e156c8..842979f 100755 --- a/extras/barnard-sound.sh +++ b/extras/barnard-sound.sh @@ -72,6 +72,14 @@ unmute() { [[ $notify ]] && notify "You unmuted yourself." } +mutedeafen() { + [[ $notify ]] && notify "You muted and deafened yourself." +} + +muteundeafen() { + [[ $notify ]] && notify "You undeafened yourself. Your microphone remains muted." +} + msg() { [[ $sound ]] && play -n synth .3 sin 1290:1490 sin 1494:1294 remix - norm -8 [[ $notify ]] && notify "$1 from $2: $3" diff --git a/gumble/gumble/user.go b/gumble/gumble/user.go index fd8c5f3..e1ac51f 100644 --- a/gumble/gumble/user.go +++ b/gumble/gumble/user.go @@ -264,6 +264,17 @@ func (u *User) SetSelfDeafened(muted bool) { u.client.Conn.WriteProto(&packet) } +// SetSelfMutedAndDeafened changes both self-mute and self-deafen in one +// protocol update so observers never see an intermediate audio state. +func (u *User) SetSelfMutedAndDeafened(muted, deafened bool) { + packet := MumbleProto.UserState{ + Session: &u.Session, + SelfMute: &muted, + SelfDeaf: &deafened, + } + u.client.Conn.WriteProto(&packet) +} + // RequestStats requests that the user's stats be sent to the client. func (u *User) RequestStats() { packet := MumbleProto.UserStats{ diff --git a/gumble/gumble/user_self_audio_test.go b/gumble/gumble/user_self_audio_test.go new file mode 100644 index 0000000..084967d --- /dev/null +++ b/gumble/gumble/user_self_audio_test.go @@ -0,0 +1,50 @@ +package gumble + +import ( + "net" + "testing" + + "git.stormux.org/storm/barnard/gumble/gumble/MumbleProto" + "google.golang.org/protobuf/proto" +) + +func TestSetSelfMutedAndDeafenedWritesOneCombinedState(t *testing.T) { + for _, deafened := range []bool{true, false} { + name := "undeafen" + if deafened { + name = "deafen" + } + t.Run(name, func(t *testing.T) { + local, remote := net.Pipe() + defer local.Close() + defer remote.Close() + + client := &Client{Conn: NewConn(local)} + user := &User{Session: 7, client: client} + written := make(chan struct{}) + go func() { + user.SetSelfMutedAndDeafened(true, deafened) + close(written) + }() + + packetType, data, err := NewConn(remote).ReadPacket() + if err != nil { + t.Fatal(err) + } + if packetType != 9 { + t.Fatalf("packet type = %d, want UserState (9)", packetType) + } + var state MumbleProto.UserState + if err := proto.Unmarshal(data, &state); err != nil { + t.Fatal(err) + } + if state.SelfMute == nil || !*state.SelfMute { + t.Fatalf("self mute = %v, want true", state.SelfMute) + } + if state.SelfDeaf == nil || *state.SelfDeaf != deafened { + t.Fatalf("self deafen = %v, want %v", state.SelfDeaf, deafened) + } + <-written + }) + } +} diff --git a/ui.go b/ui.go index 3153bcb..e99dcdb 100644 --- a/ui.go +++ b/ui.go @@ -214,6 +214,10 @@ func (b *Barnard) OnSelfMuteToggle(ui *uiterm.Ui, key uiterm.Key) { b.toggleSelfMute() } +func (b *Barnard) OnSelfDeafenToggle(ui *uiterm.Ui, key uiterm.Key) { + b.toggleSelfDeafen() +} + func (b *Barnard) selfMuted() bool { if b.Client == nil || b.Client.Self == nil { return false @@ -250,6 +254,41 @@ func (b *Barnard) toggleSelfMute() { b.setSelfMute(!b.selfMuted()) } +func (b *Barnard) selfDeafened() bool { + if b.Client == nil || b.Client.Self == nil { + return false + } + deafened := false + b.Client.Do(func() { deafened = b.Client.Self.SelfDeafened }) + return deafened +} + +func (b *Barnard) setSelfDeafen(deafened bool) { + if !b.isConnected() || b.Client == nil || b.Client.Self == nil { + b.Notify("error", "me", "cannot change self deafen while disconnected") + b.UpdateGeneralStatus("cannot change self deafen while disconnected", true) + return + } + if b.selfDeafened() == deafened { + return + } + if b.isTransmitting() { + b.StopTransmission() + } + b.Client.Self.SetSelfMutedAndDeafened(true, deafened) + if deafened { + b.Notify("mutedeafen", "me", "") + b.AddOutputLine("You muted and deafened yourself") + return + } + b.Notify("muteundeafen", "me", "") + b.AddOutputLine("You undeafened yourself; microphone remains muted") +} + +func (b *Barnard) toggleSelfDeafen() { + b.setSelfDeafen(!b.selfDeafened()) +} + func (b *Barnard) CommandLog(ui *uiterm.Ui, cmd string) { b.AddOutputLine("command " + cmd) } @@ -710,6 +749,7 @@ func (b *Barnard) OnUiInitialize(ui *uiterm.Ui) { b.Ui.AddKeyListener(b.OnHelpMenuPress, b.Hotkeys.HelpMenu) b.Ui.AddKeyListener(b.OnVoiceToggle, b.Hotkeys.Talk) b.Ui.AddKeyListener(b.OnSelfMuteToggle, b.Hotkeys.SelfMuteToggle) + b.Ui.AddKeyListener(b.OnSelfDeafenToggle, b.Hotkeys.SelfDeafenToggle) b.Ui.AddKeyListener(b.OnTimestampToggle, b.Hotkeys.ToggleTimestamps) b.Ui.AddKeyListener(b.OnNoiseSuppressionToggle, b.Hotkeys.NoiseSuppressionToggle) b.Ui.AddKeyListener(b.OnAGCToggle, b.Hotkeys.AGCToggle)