324 lines
8.0 KiB
Go
324 lines
8.0 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"sync"
|
|
|
|
"git.stormux.org/storm/barnard/config"
|
|
"git.stormux.org/storm/barnard/fileplayback"
|
|
"git.stormux.org/storm/barnard/gumble/gumble"
|
|
"git.stormux.org/storm/barnard/gumble/gumbleopenal"
|
|
"git.stormux.org/storm/barnard/noise"
|
|
"git.stormux.org/storm/barnard/recording"
|
|
"git.stormux.org/storm/barnard/uiterm"
|
|
)
|
|
|
|
type TreeItem struct {
|
|
User *gumble.User
|
|
Channel *gumble.Channel
|
|
display string
|
|
userSession uint32
|
|
channelID uint32
|
|
snapshot bool
|
|
}
|
|
|
|
type Barnard struct {
|
|
Config *gumble.Config
|
|
UserConfig *config.Config
|
|
Hotkeys *config.Hotkeys
|
|
Client *gumble.Client
|
|
|
|
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
|
|
|
|
Ui *uiterm.Ui
|
|
UiOutput uiterm.Textview
|
|
UiInput uiterm.Textbox
|
|
UiStatus uiterm.Label
|
|
UiTree uiterm.Tree
|
|
UiAdmin uiterm.Tree
|
|
UiInputStatus uiterm.Label
|
|
SelectedChannel *gumble.Channel
|
|
selectedUser *gumble.User
|
|
selectedUserMutex sync.RWMutex
|
|
adminTargetUser *gumble.User
|
|
adminTargetChan *gumble.Channel
|
|
adminReturnItem uiterm.TreeItem
|
|
statusText string
|
|
statusNotice bool
|
|
|
|
notifyChannel chan []string
|
|
|
|
exitStatus int
|
|
exitMessage string
|
|
|
|
// Added for channel muting
|
|
MutedChannels map[uint32]bool
|
|
MutedChannelsMutex sync.RWMutex
|
|
userChannels map[uint32]*gumble.Channel
|
|
|
|
// Added for noise suppression
|
|
NoiseSuppressor *noise.Suppressor
|
|
|
|
// Added for file playback
|
|
FileStream *fileplayback.Player
|
|
FileStreamMutex sync.Mutex
|
|
|
|
// Added for tone test mode (bypasses all soundcard/OpenAL)
|
|
ToneTest bool
|
|
ToneTestOutput string
|
|
toneTestStop chan struct{}
|
|
toneTestSaver *AudioFileSaver
|
|
toneTestSaverDetach gumble.Detacher
|
|
|
|
// Added for recording
|
|
RecordingMutex sync.Mutex
|
|
Recorder *recording.Recorder
|
|
recordingStarting bool
|
|
recordingAllowed *bool
|
|
|
|
pendingAdminPrompt *adminPrompt
|
|
adminBanList gumble.BanList
|
|
adminUserList gumble.RegisteredUsers
|
|
adminACL *gumble.ACL
|
|
|
|
reconnectStop chan struct{}
|
|
reconnectStopOnce sync.Once
|
|
}
|
|
|
|
// cleanupConnectionAudio releases connection-owned audio resources before a
|
|
// reconnect replaces them. It is intentionally idempotent for repeated
|
|
// disconnect notifications.
|
|
func (b *Barnard) cleanupConnectionAudio() {
|
|
// Connection audio operations that use both resources take FileStreamMutex
|
|
// before connectionMutex, so cleanup follows that order as well.
|
|
b.FileStreamMutex.Lock()
|
|
if b.FileStream != nil {
|
|
_ = b.FileStream.Stop()
|
|
b.FileStream = nil
|
|
}
|
|
b.FileStreamMutex.Unlock()
|
|
b.connectionMutex.Lock()
|
|
if b.Stream != nil {
|
|
stream := b.Stream
|
|
b.Stream = nil
|
|
stream.Destroy()
|
|
}
|
|
b.connectionMutex.Unlock()
|
|
}
|
|
|
|
func (b *Barnard) cleanupToneTestAudio() {
|
|
if b.toneTestSaverDetach != nil {
|
|
b.toneTestSaverDetach.Detach()
|
|
b.toneTestSaverDetach = nil
|
|
}
|
|
if b.toneTestSaver != nil {
|
|
b.toneTestSaver.Stop()
|
|
b.toneTestSaver = nil
|
|
}
|
|
}
|
|
|
|
func (b *Barnard) updateUserGain(user *gumble.User) {
|
|
b.withStream(func(stream *gumbleopenal.Stream) {
|
|
stream.UpdateUserGain(user)
|
|
})
|
|
}
|
|
|
|
// withStream keeps a connection-owned stream alive for the complete operation.
|
|
// Reconnect cleanup takes the write lock before destroying or replacing it.
|
|
func (b *Barnard) withStream(action func(*gumbleopenal.Stream)) bool {
|
|
b.connectionMutex.RLock()
|
|
defer b.connectionMutex.RUnlock()
|
|
if b.Stream == nil {
|
|
return false
|
|
}
|
|
action(b.Stream)
|
|
return true
|
|
}
|
|
|
|
func (b *Barnard) isChannelMuted(channelID uint32) bool {
|
|
b.MutedChannelsMutex.RLock()
|
|
defer b.MutedChannelsMutex.RUnlock()
|
|
return b.MutedChannels[channelID]
|
|
}
|
|
|
|
func (b *Barnard) setChannelMuted(channelID uint32, muted bool) {
|
|
b.MutedChannelsMutex.Lock()
|
|
defer b.MutedChannelsMutex.Unlock()
|
|
if b.MutedChannels == nil {
|
|
b.MutedChannels = make(map[uint32]bool)
|
|
}
|
|
if muted {
|
|
b.MutedChannels[channelID] = true
|
|
} else {
|
|
delete(b.MutedChannels, channelID)
|
|
}
|
|
}
|
|
|
|
func (b *Barnard) selectedUserValue() *gumble.User {
|
|
b.selectedUserMutex.RLock()
|
|
defer b.selectedUserMutex.RUnlock()
|
|
return b.selectedUser
|
|
}
|
|
|
|
func (b *Barnard) setSelectedUserValue(user *gumble.User) {
|
|
b.selectedUserMutex.Lock()
|
|
b.selectedUser = 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) stopReconnects() {
|
|
b.reconnectStopOnce.Do(func() {
|
|
if b.reconnectStop != nil {
|
|
close(b.reconnectStop)
|
|
}
|
|
})
|
|
}
|
|
|
|
func (b *Barnard) reconnectCanceled() bool {
|
|
if b.reconnectStop == nil {
|
|
return false
|
|
}
|
|
select {
|
|
case <-b.reconnectStop:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (b *Barnard) StopTransmission() {
|
|
if b.isTransmitting() {
|
|
b.Notify("micdown", "me", "")
|
|
b.setTransmitting(false)
|
|
b.UpdateGeneralStatus(" Idle ", false)
|
|
if b.ToneTest {
|
|
// Stop the tone generator.
|
|
if b.toneTestStop != nil {
|
|
close(b.toneTestStop)
|
|
b.toneTestStop = nil
|
|
}
|
|
} else {
|
|
b.withStream(func(stream *gumbleopenal.Stream) { _ = stream.StopSource() })
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *Barnard) TreeItemCharacter(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm.TreeItem, ch rune) {
|
|
}
|
|
|
|
func (b *Barnard) TreeItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm.TreeItem, key uiterm.Key) {
|
|
treeItem := item.(TreeItem)
|
|
if key == uiterm.KeyEnter {
|
|
if treeItem.Channel != nil {
|
|
b.Client.Self.Move(treeItem.Channel)
|
|
b.SetSelectedUser(nil)
|
|
b.GotoChat()
|
|
}
|
|
if treeItem.User != nil {
|
|
if b.selectedUserValue() == treeItem.User {
|
|
b.SetSelectedUser(nil)
|
|
b.GotoChat()
|
|
} else {
|
|
b.SetSelectedUser(treeItem.User)
|
|
b.GotoChat()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle mute toggle
|
|
if treeItem.Channel != nil {
|
|
if key == *b.Hotkeys.MuteToggle {
|
|
// Determine new channel mute state
|
|
channelWillBeMuted := !b.isChannelMuted(treeItem.Channel.ID)
|
|
|
|
// Set all users in channel to the same mute state
|
|
users := makeUsersArray(treeItem.Channel.Users)
|
|
for _, u := range users {
|
|
// Explicitly set user mute state to match channel state
|
|
if channelWillBeMuted && !u.LocallyMuted() {
|
|
if err := b.UserConfig.ToggleMute(u); err != nil {
|
|
b.AddOutputLine("Mute: could not save setting: " + err.Error())
|
|
}
|
|
} else if !channelWillBeMuted && u.LocallyMuted() {
|
|
if err := b.UserConfig.ToggleMute(u); err != nil {
|
|
b.AddOutputLine("Mute: could not save setting: " + err.Error())
|
|
}
|
|
}
|
|
|
|
b.updateUserGain(u)
|
|
}
|
|
|
|
// Update channel mute state
|
|
b.setChannelMuted(treeItem.Channel.ID, channelWillBeMuted)
|
|
if channelWillBeMuted && b.Client.Self.Channel.ID == treeItem.Channel.ID && b.isTransmitting() {
|
|
b.StopTransmission()
|
|
}
|
|
|
|
b.RebuildUserChannelTreePreservingSelection()
|
|
b.Ui.Refresh()
|
|
}
|
|
if key == *b.Hotkeys.VolumeDown {
|
|
b.changeVolume(makeUsersArray(treeItem.Channel.Users), -0.1)
|
|
}
|
|
if key == *b.Hotkeys.VolumeUp {
|
|
b.changeVolume(makeUsersArray(treeItem.Channel.Users), 0.1)
|
|
}
|
|
if key == *b.Hotkeys.VolumeReset {
|
|
b.resetVolume(makeUsersArray(treeItem.Channel.Users))
|
|
}
|
|
}
|
|
|
|
if treeItem.User != nil {
|
|
if key == *b.Hotkeys.MuteToggle {
|
|
// Toggle mute for single user
|
|
if err := b.UserConfig.ToggleMute(treeItem.User); err != nil {
|
|
b.AddOutputLine("Mute: could not save setting: " + err.Error())
|
|
}
|
|
b.updateUserGain(treeItem.User)
|
|
b.RebuildUserChannelTreePreservingSelection()
|
|
b.Ui.Refresh()
|
|
}
|
|
if key == *b.Hotkeys.VolumeDown {
|
|
b.changeVolume([]*gumble.User{treeItem.User}, -0.1)
|
|
}
|
|
if key == *b.Hotkeys.VolumeUp {
|
|
b.changeVolume([]*gumble.User{treeItem.User}, 0.1)
|
|
}
|
|
if key == *b.Hotkeys.VolumeReset {
|
|
b.resetVolume([]*gumble.User{treeItem.User})
|
|
}
|
|
}
|
|
}
|