diff --git a/barnard.go b/barnard.go index 161e259..603b84f 100644 --- a/barnard.go +++ b/barnard.go @@ -32,12 +32,13 @@ type Barnard struct { Address string TLSConfig tls.Config - Stream *gumbleopenal.Stream - connectionMutex sync.RWMutex - Tx bool - AutoTransmit bool // auto-start transmission on connect - Connected bool - stateMutex sync.RWMutex + Stream *gumbleopenal.Stream + connectionMutex sync.RWMutex + Tx bool + AutoTransmit bool // auto-start transmission on connect + Connected bool + unmuteOnUndeafen bool + stateMutex sync.RWMutex Ui *uiterm.Ui UiOutput uiterm.Textview @@ -213,6 +214,21 @@ func (b *Barnard) isConnected() bool { func (b *Barnard) setConnected(connected bool) { b.stateMutex.Lock() b.Connected = connected + if !connected { + b.unmuteOnUndeafen = false + } + b.stateMutex.Unlock() +} + +func (b *Barnard) shouldUnmuteOnUndeafen() bool { + b.stateMutex.RLock() + defer b.stateMutex.RUnlock() + return b.unmuteOnUndeafen +} + +func (b *Barnard) setUnmuteOnUndeafen(unmute bool) { + b.stateMutex.Lock() + b.unmuteOnUndeafen = unmute b.stateMutex.Unlock() } diff --git a/client_notification_test.go b/client_notification_test.go index 4d3b909..59e421e 100644 --- a/client_notification_test.go +++ b/client_notification_test.go @@ -150,6 +150,39 @@ func TestTransmitDoesNotStartWhileSelfMuted(t *testing.T) { } } +func TestSelfDeafenRestoresPriorMuteState(t *testing.T) { + tests := []struct { + name string + deafened bool + currentlyMuted bool + unmuteOnUndeafen bool + wantMuted bool + wantRememberUnmute bool + }{ + {name: "deafen while unmuted", deafened: true, wantMuted: true, wantRememberUnmute: true}, + {name: "deafen while already muted", deafened: true, currentlyMuted: true, wantMuted: true}, + {name: "undeafen automatic mute", unmuteOnUndeafen: true}, + {name: "undeafen prior mute", wantMuted: true}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + muted, rememberUnmute := selfDeafenTarget(test.deafened, test.currentlyMuted, test.unmuteOnUndeafen) + if muted != test.wantMuted || rememberUnmute != test.wantRememberUnmute { + t.Fatalf("got muted=%t rememberUnmute=%t; want muted=%t rememberUnmute=%t", muted, rememberUnmute, test.wantMuted, test.wantRememberUnmute) + } + }) + } +} + +func TestDisconnectForgetsAutomaticDeafenMute(t *testing.T) { + b := &Barnard{} + b.setUnmuteOnUndeafen(true) + b.setConnected(false) + if b.shouldUnmuteOnUndeafen() { + t.Fatal("disconnect retained automatic deafen mute state") + } +} + func TestConcurrentSelectedUserAccess(t *testing.T) { b := &Barnard{} user := &gumble.User{Session: 1} diff --git a/extras/barnard-sound.sh b/extras/barnard-sound.sh index 842979f..cf5974a 100755 --- a/extras/barnard-sound.sh +++ b/extras/barnard-sound.sh @@ -76,10 +76,18 @@ mutedeafen() { [[ $notify ]] && notify "You muted and deafened yourself." } +deafen() { + [[ $notify ]] && notify "You deafened yourself." +} + muteundeafen() { [[ $notify ]] && notify "You undeafened yourself. Your microphone remains muted." } +unmuteundeafen() { + [[ $notify ]] && notify "You unmuted and undeafened yourself." +} + 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_self_audio_test.go b/gumble/gumble/user_self_audio_test.go index 084967d..4c0474e 100644 --- a/gumble/gumble/user_self_audio_test.go +++ b/gumble/gumble/user_self_audio_test.go @@ -9,12 +9,17 @@ import ( ) func TestSetSelfMutedAndDeafenedWritesOneCombinedState(t *testing.T) { - for _, deafened := range []bool{true, false} { - name := "undeafen" - if deafened { - name = "deafen" - } - t.Run(name, func(t *testing.T) { + tests := []struct { + name string + muted bool + deafened bool + }{ + {name: "deafen", muted: true, deafened: true}, + {name: "undeafen and remain muted", muted: true}, + {name: "undeafen and unmute"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { local, remote := net.Pipe() defer local.Close() defer remote.Close() @@ -23,7 +28,7 @@ func TestSetSelfMutedAndDeafenedWritesOneCombinedState(t *testing.T) { user := &User{Session: 7, client: client} written := make(chan struct{}) go func() { - user.SetSelfMutedAndDeafened(true, deafened) + user.SetSelfMutedAndDeafened(test.muted, test.deafened) close(written) }() @@ -38,11 +43,11 @@ func TestSetSelfMutedAndDeafenedWritesOneCombinedState(t *testing.T) { 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.SelfMute == nil || *state.SelfMute != test.muted { + t.Fatalf("self mute = %v, want %v", state.SelfMute, test.muted) } - if state.SelfDeaf == nil || *state.SelfDeaf != deafened { - t.Fatalf("self deafen = %v, want %v", state.SelfDeaf, deafened) + if state.SelfDeaf == nil || *state.SelfDeaf != test.deafened { + t.Fatalf("self deafen = %v, want %v", state.SelfDeaf, test.deafened) } <-written }) diff --git a/ui.go b/ui.go index e99dcdb..e7c1280 100644 --- a/ui.go +++ b/ui.go @@ -233,6 +233,13 @@ func (b *Barnard) setSelfMute(muted bool) { b.UpdateGeneralStatus("cannot change self mute while disconnected", true) return } + if !muted && b.selfDeafened() { + b.setUnmuteOnUndeafen(false) + b.Client.Self.SetSelfMutedAndDeafened(false, false) + b.Notify("unmuteundeafen", "me", "") + b.AddOutputLine("You unmuted and undeafened yourself") + return + } if b.selfMuted() == muted { return } @@ -272,17 +279,36 @@ func (b *Barnard) setSelfDeafen(deafened bool) { if b.selfDeafened() == deafened { return } - if b.isTransmitting() { + if deafened && b.isTransmitting() { b.StopTransmission() } - b.Client.Self.SetSelfMutedAndDeafened(true, deafened) + muted, unmuteOnUndeafen := selfDeafenTarget(deafened, b.selfMuted(), b.shouldUnmuteOnUndeafen()) + b.setUnmuteOnUndeafen(unmuteOnUndeafen) + b.Client.Self.SetSelfMutedAndDeafened(muted, deafened) if deafened { - b.Notify("mutedeafen", "me", "") - b.AddOutputLine("You muted and deafened yourself") + if unmuteOnUndeafen { + b.Notify("mutedeafen", "me", "") + b.AddOutputLine("You muted and deafened yourself") + } else { + b.Notify("deafen", "me", "") + b.AddOutputLine("You deafened yourself") + } return } - b.Notify("muteundeafen", "me", "") - b.AddOutputLine("You undeafened yourself; microphone remains muted") + if muted { + b.Notify("muteundeafen", "me", "") + b.AddOutputLine("You undeafened yourself; microphone remains muted") + } else { + b.Notify("unmuteundeafen", "me", "") + b.AddOutputLine("You unmuted and undeafened yourself") + } +} + +func selfDeafenTarget(deafened, currentlyMuted, unmuteOnUndeafen bool) (muted, rememberUnmute bool) { + if deafened { + return true, !currentlyMuted + } + return !unmuteOnUndeafen, false } func (b *Barnard) toggleSelfDeafen() {