First attempt didn't work.

This commit is contained in:
Storm Dragon
2026-09-06 01:13:31 -04:00
parent 2b5fe91d6c
commit 4ef8553c7d
6 changed files with 79 additions and 120 deletions
-1
View File
@@ -68,7 +68,6 @@ type Barnard struct {
MutedChannels map[uint32]bool MutedChannels map[uint32]bool
MutedChannelsMutex sync.RWMutex MutedChannelsMutex sync.RWMutex
userChannels map[uint32]*gumble.Channel userChannels map[uint32]*gumble.Channel
userAudioStates map[uint32]userAudioState
// Added for noise suppression // Added for noise suppression
NoiseSuppressor *noise.Suppressor NoiseSuppressor *noise.Suppressor
+10 -66
View File
@@ -161,7 +161,6 @@ func (b *Barnard) OnConnect(e *gumble.ConnectEvent) {
b.MutedChannels = make(map[uint32]bool) b.MutedChannels = make(map[uint32]bool)
b.MutedChannelsMutex.Unlock() b.MutedChannelsMutex.Unlock()
b.userChannels = make(map[uint32]*gumble.Channel) b.userChannels = make(map[uint32]*gumble.Channel)
b.userAudioStates = make(map[uint32]userAudioState)
b.RecordingMutex.Lock() b.RecordingMutex.Lock()
b.recordingAllowed = nil b.recordingAllowed = nil
b.recordingStarting = false b.recordingStarting = false
@@ -183,7 +182,6 @@ func (b *Barnard) OnConnect(e *gumble.ConnectEvent) {
for _, u := range users { for _, u := range users {
b.UserConfig.UpdateUser(u) b.UserConfig.UpdateUser(u)
b.rememberUserChannel(u) b.rememberUserChannel(u)
b.rememberUserAudioState(u)
} }
b.UpdateInputStatus(fmt.Sprintf("[%s]", e.Client.Self.Channel.Name)) b.UpdateInputStatus(fmt.Sprintf("[%s]", e.Client.Self.Channel.Name))
@@ -391,7 +389,6 @@ func (b *Barnard) OnUserChange(e *gumble.UserChangeEvent) {
b.AddOutputLine(formatUserStats(e.User)) b.AddOutputLine(formatUserStats(e.User))
} }
b.updateUserChannel(e) b.updateUserChannel(e)
b.updateUserAudioState(e)
b.postUI(func() { b.postUI(func() {
b.RebuildUserChannelTreePreservingSelection() b.RebuildUserChannelTreePreservingSelection()
b.Ui.Refresh() b.Ui.Refresh()
@@ -405,11 +402,6 @@ type userChangeNotification struct {
line string line string
} }
type userAudioState struct {
selfMuted bool
selfDeafened bool
}
func (b *Barnard) userChangeNotification(e *gumble.UserChangeEvent) (userChangeNotification, bool) { func (b *Barnard) userChangeNotification(e *gumble.UserChangeEvent) (userChangeNotification, bool) {
if e == nil || e.User == nil || b.Client == nil || b.Client.Self == nil || b.Client.Self.Channel == nil { if e == nil || e.User == nil || b.Client == nil || b.Client.Self == nil || b.Client.Self.Channel == nil {
return userChangeNotification{}, false return userChangeNotification{}, false
@@ -441,7 +433,7 @@ func (b *Barnard) userChangeNotification(e *gumble.UserChangeEvent) (userChangeN
} }
func (b *Barnard) userAudioChangeNotification(e *gumble.UserChangeEvent) (userChangeNotification, bool) { func (b *Barnard) userAudioChangeNotification(e *gumble.UserChangeEvent) (userChangeNotification, bool) {
if e == nil || e.User == nil || !e.Type.Has(gumble.UserChangeAudio) || b.Client == nil || b.Client.Self == nil || b.Client.Self.Channel == nil { if e == nil || e.User == nil || (e.SelfMute == nil && e.SelfDeaf == nil) || b.Client == nil || b.Client.Self == nil || b.Client.Self.Channel == nil {
return userChangeNotification{}, false return userChangeNotification{}, false
} }
// Barnard announces its own hotkey actions immediately. The protocol echo // Barnard announces its own hotkey actions immediately. The protocol echo
@@ -449,53 +441,29 @@ func (b *Barnard) userAudioChangeNotification(e *gumble.UserChangeEvent) (userCh
if e.User.Session == b.Client.Self.Session { if e.User.Session == b.Client.Self.Session {
return userChangeNotification{}, false return userChangeNotification{}, false
} }
previous, known := b.userAudioStates[e.User.Session] if !sameChannel(e.User.Channel, b.Client.Self.Channel) {
if !known || !sameChannel(e.User.Channel, b.Client.Self.Channel) {
return userChangeNotification{}, false return userChangeNotification{}, false
} }
muteEvent, muteVerb := "", "" event, state := "unmute", "unmuted"
if previous.selfMuted != e.User.SelfMuted { if e.User.SelfDeafened {
if e.User.SelfMuted { event, state = "mutedeafen", "muted and deafened"
muteEvent, muteVerb = "mute", "muted" } else if e.User.SelfMuted {
} else { event, state = "mute", "muted"
muteEvent, muteVerb = "unmute", "unmuted"
}
} }
deafenEvent, deafenVerb := "", ""
if previous.selfDeafened != e.User.SelfDeafened {
if e.User.SelfDeafened {
deafenEvent, deafenVerb = "deafen", "deafened"
} else {
deafenEvent, deafenVerb = "undeafen", "undeafened"
}
}
event := muteEvent + deafenEvent
verbs := make([]string, 0, 2)
if muteVerb != "" {
verbs = append(verbs, muteVerb)
}
if deafenVerb != "" {
verbs = append(verbs, deafenVerb)
}
if event == "" {
return userChangeNotification{}, false
}
verb := strings.Join(verbs, " and ")
return userChangeNotification{ return userChangeNotification{
event: event, event: event,
who: e.User.Name, who: e.User.Name,
what: e.User.Channel.Name, what: e.User.Channel.Name,
line: fmt.Sprintf("%s %s", e.User.Name, verb), line: fmt.Sprintf("%s is now %s", e.User.Name, state),
}, true }, true
} }
func (b *Barnard) restoreTransmissionAfterUndeafen(e *gumble.UserChangeEvent) { func (b *Barnard) restoreTransmissionAfterUndeafen(e *gumble.UserChangeEvent) {
if e == nil || e.User == nil || !e.Type.Has(gumble.UserChangeAudio) || b.Client == nil || b.Client.Self == nil { if e == nil || e.User == nil || e.SelfDeaf == nil || b.Client == nil || b.Client.Self == nil {
return return
} }
if e.User.Session != b.Client.Self.Session || e.User.SelfDeafened || !b.shouldTransmitOnUndeafen() { if e.User.Session != b.Client.Self.Session || *e.SelfDeaf || !b.shouldTransmitOnUndeafen() {
return return
} }
b.setTransmitOnUndeafen(false) b.setTransmitOnUndeafen(false)
@@ -548,30 +516,6 @@ func (b *Barnard) updateUserChannel(e *gumble.UserChangeEvent) {
b.rememberUserChannel(e.User) b.rememberUserChannel(e.User)
} }
func (b *Barnard) rememberUserAudioState(user *gumble.User) {
if user == nil {
return
}
if b.userAudioStates == nil {
b.userAudioStates = make(map[uint32]userAudioState)
}
b.userAudioStates[user.Session] = userAudioState{
selfMuted: user.SelfMuted,
selfDeafened: user.SelfDeafened,
}
}
func (b *Barnard) updateUserAudioState(e *gumble.UserChangeEvent) {
if e == nil || e.User == nil {
return
}
if e.Type.Has(gumble.UserChangeDisconnected) {
delete(b.userAudioStates, e.User.Session)
return
}
b.rememberUserAudioState(e.User)
}
func (b *Barnard) OnChannelChange(e *gumble.ChannelChangeEvent) { func (b *Barnard) OnChannelChange(e *gumble.ChannelChangeEvent) {
b.UpdateInputStatus(fmt.Sprintf("[%s]", e.Channel.Name)) b.UpdateInputStatus(fmt.Sprintf("[%s]", e.Channel.Name))
if e.Type.Has(gumble.ChannelChangeDescription) { if e.Type.Has(gumble.ChannelChangeDescription) {
+19 -53
View File
@@ -195,65 +195,42 @@ func TestUserAudioChangeNotification(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
user *gumble.User user *gumble.User
previous userAudioState selfMute *bool
remember bool selfDeaf *bool
change gumble.UserChangeType
want userChangeNotification want userChangeNotification
wantOK bool wantOK bool
}{ }{
{ {
name: "user mutes", user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true}, name: "user mutes", user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true},
remember: true, change: gumble.UserChangeAudio, selfMute: boolPointer(true),
want: userChangeNotification{event: "mute", who: "Guest", what: "Current", line: "Guest muted"}, wantOK: true, want: userChangeNotification{event: "mute", who: "Guest", what: "Current", line: "Guest is now muted"}, wantOK: true,
}, },
{ {
name: "user unmutes", user: &gumble.User{Session: 2, Name: "Guest", Channel: current}, name: "user unmutes", user: &gumble.User{Session: 2, Name: "Guest", Channel: current},
previous: userAudioState{selfMuted: true}, remember: true, change: gumble.UserChangeAudio, selfMute: boolPointer(false),
want: userChangeNotification{event: "unmute", who: "Guest", what: "Current", line: "Guest unmuted"}, wantOK: true, want: userChangeNotification{event: "unmute", who: "Guest", what: "Current", line: "Guest is now unmuted"}, wantOK: true,
},
{
name: "user deafens", user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true, SelfDeafened: true},
previous: userAudioState{selfMuted: true}, remember: true, change: gumble.UserChangeAudio,
want: userChangeNotification{event: "deafen", who: "Guest", what: "Current", line: "Guest deafened"}, wantOK: true,
},
{
name: "user undeafens", user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true},
previous: userAudioState{selfMuted: true, selfDeafened: true}, remember: true, change: gumble.UserChangeAudio,
want: userChangeNotification{event: "undeafen", who: "Guest", what: "Current", line: "Guest undeafened"}, wantOK: true,
}, },
{ {
name: "user mutes and deafens", user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true, SelfDeafened: true}, name: "user mutes and deafens", user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true, SelfDeafened: true},
remember: true, change: gumble.UserChangeAudio, selfMute: boolPointer(true), selfDeaf: boolPointer(true),
want: userChangeNotification{event: "mutedeafen", who: "Guest", what: "Current", line: "Guest muted and deafened"}, wantOK: true, want: userChangeNotification{event: "mutedeafen", who: "Guest", what: "Current", line: "Guest is now muted and deafened"}, wantOK: true,
}, },
{ {
name: "user unmutes and undeafens", user: &gumble.User{Session: 2, Name: "Guest", Channel: current}, name: "server includes both fields for a mute", user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true},
previous: userAudioState{selfMuted: true, selfDeafened: true}, remember: true, change: gumble.UserChangeAudio, selfMute: boolPointer(true), selfDeaf: boolPointer(false),
want: userChangeNotification{event: "unmuteundeafen", who: "Guest", what: "Current", line: "Guest unmuted and undeafened"}, wantOK: true, want: userChangeNotification{event: "mute", who: "Guest", what: "Current", line: "Guest is now muted"}, wantOK: true,
}, },
{ {name: "self protocol echo is ignored", user: &gumble.User{Session: 1, Name: "Username", Channel: current, SelfMuted: true}, selfMute: boolPointer(true)},
name: "user unmutes while deafening", user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfDeafened: true}, {name: "other channel is ignored", user: &gumble.User{Session: 2, Name: "Guest", Channel: other, SelfMuted: true}, selfMute: boolPointer(true)},
previous: userAudioState{selfMuted: true}, remember: true, change: gumble.UserChangeAudio, {name: "packet without self state is ignored", user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true}},
want: userChangeNotification{event: "unmutedeafen", who: "Guest", what: "Current", line: "Guest unmuted and deafened"}, wantOK: true,
},
{
name: "user mutes while undeafening", user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true},
previous: userAudioState{selfDeafened: true}, remember: true, change: gumble.UserChangeAudio,
want: userChangeNotification{event: "muteundeafen", who: "Guest", what: "Current", line: "Guest muted and undeafened"}, wantOK: true,
},
{name: "self protocol echo is ignored", user: &gumble.User{Session: 1, Name: "Username", Channel: current, SelfMuted: true}, remember: true, change: gumble.UserChangeAudio},
{name: "other channel is ignored", user: &gumble.User{Session: 2, Name: "Guest", Channel: other, SelfMuted: true}, remember: true, change: gumble.UserChangeAudio},
{name: "unknown initial state is ignored", user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true}, change: gumble.UserChangeConnected | gumble.UserChangeAudio},
{name: "unrelated change is ignored", user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true}, remember: true, change: gumble.UserChangeComment},
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
b := &Barnard{Client: &gumble.Client{Self: self}} b := &Barnard{Client: &gumble.Client{Self: self}}
if test.remember { got, ok := b.userAudioChangeNotification(&gumble.UserChangeEvent{
b.userAudioStates = map[uint32]userAudioState{test.user.Session: test.previous} Client: b.Client, User: test.user, SelfMute: test.selfMute, SelfDeaf: test.selfDeaf,
} })
got, ok := b.userAudioChangeNotification(&gumble.UserChangeEvent{Client: b.Client, Type: test.change, User: test.user})
if ok != test.wantOK || got != test.want { if ok != test.wantOK || got != test.want {
t.Fatalf("got (%#v, %t), want (%#v, %t)", got, ok, test.want, test.wantOK) t.Fatalf("got (%#v, %t), want (%#v, %t)", got, ok, test.want, test.wantOK)
} }
@@ -261,24 +238,13 @@ func TestUserAudioChangeNotification(t *testing.T) {
} }
} }
func TestUpdateUserAudioState(t *testing.T) { func boolPointer(value bool) *bool { return &value }
user := &gumble.User{Session: 2, SelfMuted: true, SelfDeafened: true}
b := &Barnard{}
b.updateUserAudioState(&gumble.UserChangeEvent{User: user})
if got := b.userAudioStates[user.Session]; got != (userAudioState{selfMuted: true, selfDeafened: true}) {
t.Fatalf("unexpected remembered audio state: %#v", got)
}
b.updateUserAudioState(&gumble.UserChangeEvent{Type: gumble.UserChangeDisconnected, User: user})
if _, ok := b.userAudioStates[user.Session]; ok {
t.Fatal("disconnected user audio state was retained")
}
}
func TestUndeafenConsumesRememberedTransmission(t *testing.T) { func TestUndeafenConsumesRememberedTransmission(t *testing.T) {
self := &gumble.User{Session: 1} self := &gumble.User{Session: 1}
b := &Barnard{Client: &gumble.Client{Self: self}} b := &Barnard{Client: &gumble.Client{Self: self}}
b.setTransmitOnUndeafen(true) b.setTransmitOnUndeafen(true)
b.restoreTransmissionAfterUndeafen(&gumble.UserChangeEvent{Type: gumble.UserChangeAudio, User: self}) b.restoreTransmissionAfterUndeafen(&gumble.UserChangeEvent{User: self, SelfDeaf: boolPointer(false)})
if b.shouldTransmitOnUndeafen() { if b.shouldTransmitOnUndeafen() {
t.Fatal("undeafen did not consume remembered transmission state") t.Fatal("undeafen did not consume remembered transmission state")
} }
+5
View File
@@ -94,6 +94,11 @@ type UserChangeEvent struct {
Type UserChangeType Type UserChangeType
User *User User *User
Actor *User Actor *User
// SelfMute and SelfDeaf preserve the optional fields from the UserState
// packet. A nil pointer means the server did not include that state in
// this update.
SelfMute *bool
SelfDeaf *bool
String string String string
} }
+2
View File
@@ -752,12 +752,14 @@ func (c *Client) handleUserState(buffer []byte) error {
user.Suppressed = *packet.Suppress user.Suppressed = *packet.Suppress
} }
if packet.SelfMute != nil { if packet.SelfMute != nil {
event.SelfMute = packet.SelfMute
if *packet.SelfMute != user.SelfMuted { if *packet.SelfMute != user.SelfMuted {
event.Type |= UserChangeAudio event.Type |= UserChangeAudio
} }
user.SelfMuted = *packet.SelfMute user.SelfMuted = *packet.SelfMute
} }
if packet.SelfDeaf != nil { if packet.SelfDeaf != nil {
event.SelfDeaf = packet.SelfDeaf
if *packet.SelfDeaf != user.SelfDeafened { if *packet.SelfDeaf != user.SelfDeafened {
event.Type |= UserChangeAudio event.Type |= UserChangeAudio
} }
+43
View File
@@ -26,3 +26,46 @@ func TestUserStateUnknownChannelDoesNotDeadlock(t *testing.T) {
t.Fatal("volatile lock was left locked") t.Fatal("volatile lock was left locked")
} }
} }
type userStateListener struct {
EventListener
event *UserChangeEvent
}
func (l *userStateListener) OnUserChange(event *UserChangeEvent) {
l.event = event
}
func TestUserStateEventPreservesSelfMuteAndDeafFields(t *testing.T) {
channel := &Channel{ID: 0, Users: make(Users)}
c := &Client{
Config: NewConfig(),
Users: make(Users),
Channels: Channels{0: channel},
state: uint32(StateSynced),
}
user := c.Users.create(1)
user.Channel = channel
channel.Users[user.Session] = user
listener := &userStateListener{}
c.Config.Attach(listener)
session, selfMute, selfDeaf := uint32(1), true, false
data, err := proto.Marshal(&MumbleProto.UserState{
Session: &session,
SelfMute: &selfMute,
SelfDeaf: &selfDeaf,
})
if err != nil {
t.Fatal(err)
}
if err := c.handleUserState(data); err != nil {
t.Fatal(err)
}
if listener.event == nil || listener.event.SelfMute == nil || listener.event.SelfDeaf == nil {
t.Fatalf("self state fields were not preserved: %#v", listener.event)
}
if !*listener.event.SelfMute || *listener.event.SelfDeaf {
t.Fatalf("unexpected self state values: mute=%t deaf=%t", *listener.event.SelfMute, *listener.event.SelfDeaf)
}
}