From 55772fa4591cf3010e581af32ba4741d0827ac7a Mon Sep 17 00:00:00 2001 From: "Brandon McGinty (chatgpt)" Date: Mon, 10 Aug 2026 00:45:22 -0400 Subject: [PATCH] Synchronize connection and transmission state --- barnard.go | 31 ++++++++++++++++++++++++++++--- client.go | 14 +++++++------- client_notification_test.go | 16 ++++++++++++++++ ui.go | 26 +++++++++++++------------- 4 files changed, 64 insertions(+), 23 deletions(-) diff --git a/barnard.go b/barnard.go index c4305c7..20665d3 100644 --- a/barnard.go +++ b/barnard.go @@ -31,6 +31,7 @@ type Barnard struct { Tx bool AutoTransmit bool // auto-start transmission on connect Connected bool + stateMutex sync.RWMutex Ui *uiterm.Ui UiOutput uiterm.Textview @@ -149,10 +150,34 @@ func (b *Barnard) setSelectedUserValue(user *gumble.User) { b.selectedUserMutex.Unlock() } +func (b *Barnard) isTransmitting() bool { + b.stateMutex.RLock() + defer b.stateMutex.RUnlock() + return b.Tx +} + +func (b *Barnard) setTransmitting(transmitting bool) { + b.stateMutex.Lock() + b.Tx = transmitting + b.stateMutex.Unlock() +} + +func (b *Barnard) isConnected() bool { + b.stateMutex.RLock() + defer b.stateMutex.RUnlock() + return b.Connected +} + +func (b *Barnard) setConnected(connected bool) { + b.stateMutex.Lock() + b.Connected = connected + b.stateMutex.Unlock() +} + func (b *Barnard) StopTransmission() { - if b.Tx { + if b.isTransmitting() { b.Notify("micdown", "me", "") - b.Tx = false + b.setTransmitting(false) b.UpdateGeneralStatus(" Idle ", false) if b.ToneTest { // Stop the tone generator. @@ -209,7 +234,7 @@ func (b *Barnard) TreeItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm // Update channel mute state b.setChannelMuted(treeItem.Channel.ID, channelWillBeMuted) - if channelWillBeMuted && b.Client.Self.Channel.ID == treeItem.Channel.ID && b.Tx { + if channelWillBeMuted && b.Client.Self.Channel.ID == treeItem.Channel.ID && b.isTransmitting() { b.StopTransmission() } diff --git a/client.go b/client.go index 3e78575..ca6ebff 100644 --- a/client.go +++ b/client.go @@ -70,11 +70,11 @@ func (b *Barnard) connect(reconnect bool) bool { b.toneTestSaver = saver b.toneTestSaverDetach = b.Client.Config.AttachAudio(saver) - b.Connected = true + b.setConnected(true) if b.toneTestAutoTransmit() { b.toneTestStop = make(chan struct{}) go StartToneGenerator(b.Client, b.toneTestStop) - b.Tx = true + b.setTransmitting(true) b.UpdateGeneralStatus(" Tx ", true) b.AddOutputLine("Tone test transmission started") } @@ -112,7 +112,7 @@ func (b *Barnard) connect(reconnect bool) bool { b.Stream.SetFilePlayer(b.FileStream) b.FileStreamMutex.Unlock() - b.Connected = true + b.setConnected(true) // Dial delivers OnConnect before connect creates the OpenAL stream, so // start auto-transmit here as well for initial connections and reconnects. b.startAutoTransmit() @@ -165,14 +165,14 @@ func (b *Barnard) OnConnect(e *gumble.ConnectEvent) { } func (b *Barnard) startAutoTransmit() { - if !b.AutoTransmit || b.Stream == nil || b.Tx { + if !b.AutoTransmit || b.Stream == nil || b.isTransmitting() { return } if err := b.Stream.StartSource(b.UserConfig.GetInputDevice()); err != nil { b.AddOutputLine(fmt.Sprintf("auto-transmit failed: %s", err.Error())) return } - b.Tx = true + b.setTransmitting(true) b.UpdateGeneralStatus(" AutoTx ", true) b.AddOutputLine("Auto-transmit started") } @@ -208,8 +208,8 @@ func (b *Barnard) OnDisconnect(e *gumble.DisconnectEvent) { } else { b.AddOutputLine("Disconnected: " + reason) } - b.Tx = false - b.Connected = false + b.setTransmitting(false) + b.setConnected(false) b.postUI(func() { b.UiTree.Rebuild() b.Ui.Refresh() diff --git a/client_notification_test.go b/client_notification_test.go index 392236d..f82f36e 100644 --- a/client_notification_test.go +++ b/client_notification_test.go @@ -84,6 +84,22 @@ func TestServerAddressDefaultsPortWithoutBreakingIPv6(t *testing.T) { } } +func TestConcurrentConnectionStateAccess(t *testing.T) { + b := &Barnard{} + var wg sync.WaitGroup + for i := 0; i < 20; i++ { + wg.Add(1) + go func(value bool) { + defer wg.Done() + b.setConnected(value) + b.setTransmitting(value) + _ = b.isConnected() + _ = b.isTransmitting() + }(i%2 == 0) + } + wg.Wait() +} + func TestConcurrentSelectedUserAccess(t *testing.T) { b := &Barnard{} user := &gumble.User{Session: 1} diff --git a/ui.go b/ui.go index fab2ab2..5930ff5 100644 --- a/ui.go +++ b/ui.go @@ -236,7 +236,7 @@ func (b *Barnard) CommandPlayFile(ui *uiterm.Ui, cmd string) { } } - if !b.Connected { + if !b.isConnected() { b.AddOutputLine("Not connected to server") return } @@ -267,7 +267,7 @@ func (b *Barnard) CommandPlayFile(ui *uiterm.Ui, cmd string) { b.Client.EnableStereoEncoder() // Auto-start transmission if not already transmitting - if !b.Tx && b.Stream != nil { + if !b.isTransmitting() && b.Stream != nil { err := b.Stream.StartSource(b.UserConfig.GetInputDevice()) if err != nil { b.AddOutputLine(fmt.Sprintf("Error starting transmission: %s", err.Error())) @@ -275,7 +275,7 @@ func (b *Barnard) CommandPlayFile(ui *uiterm.Ui, cmd string) { b.Client.DisableStereoEncoder() return } - b.Tx = true + b.setTransmitting(true) b.UpdateGeneralStatus(" File ", true) } @@ -312,15 +312,15 @@ func (b *Barnard) CommandStopFile(ui *uiterm.Ui, cmd string) { } func (b *Barnard) setTransmit(ui *uiterm.Ui, val int) { - if b.Tx && val == 1 { + if b.isTransmitting() && val == 1 { return } - if b.Tx == false && val == 0 { + if !b.isTransmitting() && val == 0 { return } - if b.Tx { + if b.isTransmitting() { b.Notify("micdown", "me", "") - b.Tx = false + b.setTransmitting(false) b.UpdateGeneralStatus(" Idle ", false) if b.ToneTest { if b.toneTestStop != nil { @@ -330,17 +330,17 @@ func (b *Barnard) setTransmit(ui *uiterm.Ui, val int) { } else if b.Stream != nil { b.Stream.StopSource() } - } else if b.Connected == false { + } else if !b.isConnected() { b.Notify("error", "me", "no tx while disconnected") - b.Tx = false + b.setTransmitting(false) b.UpdateGeneralStatus("no tx while disconnected", true) } else if b.isChannelMuted(b.Client.Self.Channel.ID) { // Check if current channel is muted b.Notify("error", "me", "cannot transmit in muted channel") - b.Tx = false + b.setTransmitting(false) b.UpdateGeneralStatus("cannot transmit in muted channel", true) } else { - b.Tx = true + b.setTransmitting(true) if b.ToneTest { b.toneTestStop = make(chan struct{}) go StartToneGenerator(b.Client, b.toneTestStop) @@ -349,7 +349,7 @@ func (b *Barnard) setTransmit(ui *uiterm.Ui, val int) { } else { err := b.Stream.StartSource(b.UserConfig.GetInputDevice()) if err != nil { - b.Tx = false + b.setTransmitting(false) if fatalAudioOpenError(err) { // A missing capture device cannot recover through normal // transmission controls; exit so option 1 reports it on stderr. @@ -405,7 +405,7 @@ func (b *Barnard) CommandExit(ui *uiterm.Ui, cmd string) { } func (b *Barnard) CommandStatus(ui *uiterm.Ui, cmd string) { - if b.Tx { + if b.isTransmitting() { b.Notify("status", "me", "transmitting") } else { b.Notify("status", "me", "not transmitting")