Route network UI callbacks through UI queue

This commit is contained in:
Brandon McGinty (chatgpt)
2026-08-09 19:47:14 -04:00
committed by Brandon McGinty
parent 12761f29ca
commit d50c6726ad
3 changed files with 30 additions and 22 deletions
+21 -21
View File
@@ -120,9 +120,11 @@ func (b *Barnard) OnConnect(e *gumble.ConnectEvent) {
b.recordingStarting = false
b.RecordingMutex.Unlock()
b.Ui.SetActive(uiViewInput)
b.UiTree.Rebuild()
b.Ui.Refresh()
b.postUI(func() {
b.Ui.SetActive(uiViewInput)
b.UiTree.Rebuild()
b.Ui.Refresh()
})
var users []*gumble.User
b.Client.Do(func() {
@@ -146,7 +148,6 @@ func (b *Barnard) OnConnect(e *gumble.ConnectEvent) {
if wmsg != "" {
b.AddOutputLine(fmt.Sprintf("Welcome message: %s", wmsg))
}
b.Ui.Refresh()
// Auto-start transmission if requested
if b.AutoTransmit && b.Stream != nil && !b.Tx {
@@ -197,8 +198,10 @@ func (b *Barnard) OnDisconnect(e *gumble.DisconnectEvent) {
}
b.Tx = false
b.Connected = false
b.UiTree.Rebuild()
b.Ui.Refresh()
b.postUI(func() {
b.UiTree.Rebuild()
b.Ui.Refresh()
})
go b.reconnectGoroutine()
}
@@ -285,8 +288,10 @@ func (b *Barnard) OnUserChange(e *gumble.UserChangeEvent) {
b.AddOutputLine(formatUserStats(e.User))
}
b.updateUserChannel(e)
b.RebuildUserChannelTreePreservingSelection()
b.Ui.Refresh()
b.postUI(func() {
b.RebuildUserChannelTreePreservingSelection()
b.Ui.Refresh()
})
}
type userChangeNotification struct {
@@ -386,8 +391,10 @@ func (b *Barnard) OnChannelChange(e *gumble.ChannelChangeEvent) {
b.AddOutputLine(fmt.Sprintf("Channel permissions for %s: %s", e.Channel.Name, permissionList(*permission)))
}
}
b.RebuildUserChannelTreePreservingSelection()
b.Ui.Refresh()
b.postUI(func() {
b.RebuildUserChannelTreePreservingSelection()
b.Ui.Refresh()
})
}
func formatUserStats(user *gumble.User) string {
@@ -465,34 +472,27 @@ func (b *Barnard) OnPermissionDenied(e *gumble.PermissionDeniedEvent) {
}
func (b *Barnard) OnUserList(e *gumble.UserListEvent) {
b.adminUserList = e.UserList
b.AddOutputLine(fmt.Sprintf("Admin: received %d registered users", len(e.UserList)))
b.UiAdmin.Rebuild()
b.Ui.Refresh()
b.postUI(func() { b.adminUserList = e.UserList; b.UiAdmin.Rebuild(); b.Ui.Refresh() })
}
func (b *Barnard) OnACL(e *gumble.ACLEvent) {
b.adminACL = e.ACL
if e.ACL != nil && e.ACL.Channel != nil {
b.AddOutputLine(fmt.Sprintf("Admin: received ACLs for %s", e.ACL.Channel.Name))
}
b.UiAdmin.Rebuild()
b.Ui.Refresh()
b.postUI(func() { b.adminACL = e.ACL; b.UiAdmin.Rebuild(); b.Ui.Refresh() })
}
func (b *Barnard) OnBanList(e *gumble.BanListEvent) {
b.adminBanList = e.BanList
b.AddOutputLine(fmt.Sprintf("Admin: received %d bans", len(e.BanList)))
b.UiAdmin.Rebuild()
b.Ui.Refresh()
b.postUI(func() { b.adminBanList = e.BanList; b.UiAdmin.Rebuild(); b.Ui.Refresh() })
}
func (b *Barnard) OnContextActionChange(e *gumble.ContextActionChangeEvent) {
if e.ContextAction != nil {
b.AddOutputLine(fmt.Sprintf("Admin: context action updated: %s", e.ContextAction.Name))
}
b.UiAdmin.Rebuild()
b.Ui.Refresh()
b.postUI(func() { b.UiAdmin.Rebuild(); b.Ui.Refresh() })
}
func (b *Barnard) OnServerConfig(e *gumble.ServerConfigEvent) {
+1 -1
View File
@@ -51,7 +51,7 @@ Priority 0: security and crashers
Add a binding test that creates and deletes a single buffer and checks
openal.Err().
6. UI writes are concurrent and termbox is not protected
[x] 6. UI writes are concurrent and termbox is not protected
Files: ui.go, client.go, gumble/gumbleopenal/stream.go, uiterm/*.go
Network callbacks, audio capture error callbacks, and reconnect goroutines
directly update Ui, Textview, Tree, Label, and termbox while Ui.Run updates
+8
View File
@@ -37,6 +37,14 @@ func esc(str string) string {
return sanitize.HTML(clean)
}
// postUI is the only path network and audio callbacks use to touch terminal
// widgets. Work is dropped during shutdown or queue overload.
func (b *Barnard) postUI(fn func()) {
if b.Ui != nil {
b.Ui.Post(fn)
}
}
func (b *Barnard) Notify(event string, who string, what string) {
// Notifications are best-effort: a slow external command must not block a
// UI or network callback. New events are dropped once the bounded queue is full.