From a5c0e7a71c87f4549d2483e294f4264aadf6117c Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Thu, 16 Jan 2025 13:36:46 -0500 Subject: [PATCH] Added mute option. It can work per user or per channel, so covers deafen functionality too. --- config/hotkey_config.go | 25 +- config/user_config.go | 414 +++++++++++++++++----------------- gumble/gumble/user.go | 277 ++++++++++++----------- gumble/gumbleopenal/stream.go | 18 +- ui_tree.go | 316 ++++++++++++++------------ 5 files changed, 551 insertions(+), 499 deletions(-) diff --git a/config/hotkey_config.go b/config/hotkey_config.go index 5cd13ea..76b49e1 100644 --- a/config/hotkey_config.go +++ b/config/hotkey_config.go @@ -1,19 +1,20 @@ package config import ( - "git.2mb.codes/~cmb/barnard/uiterm" + "git.2mb.codes/~cmb/barnard/uiterm" ) type Hotkeys struct { - Talk *uiterm.Key - VolumeDown *uiterm.Key - VolumeUp *uiterm.Key - Exit *uiterm.Key - ToggleTimestamps *uiterm.Key - SwitchViews *uiterm.Key - ClearOutput *uiterm.Key - ScrollUp *uiterm.Key - ScrollDown *uiterm.Key - ScrollToTop *uiterm.Key - ScrollToBottom *uiterm.Key + Talk *uiterm.Key + VolumeDown *uiterm.Key + VolumeUp *uiterm.Key + MuteToggle *uiterm.Key + Exit *uiterm.Key + ToggleTimestamps *uiterm.Key + SwitchViews *uiterm.Key + ClearOutput *uiterm.Key + ScrollUp *uiterm.Key + ScrollDown *uiterm.Key + ScrollToTop *uiterm.Key + ScrollToBottom *uiterm.Key } diff --git a/config/user_config.go b/config/user_config.go index 2ea2fbb..3263650 100644 --- a/config/user_config.go +++ b/config/user_config.go @@ -1,281 +1,291 @@ package config import ( - "fmt" - "git.2mb.codes/~cmb/barnard/uiterm" - "gopkg.in/yaml.v2" - // "encoding/yaml" - "git.2mb.codes/~cmb/barnard/gumble/gumble" - "io/ioutil" - "os" - "os/user" - "strconv" - "strings" + "fmt" + "git.2mb.codes/~cmb/barnard/uiterm" + "gopkg.in/yaml.v2" + "git.2mb.codes/~cmb/barnard/gumble/gumble" + "io/ioutil" + "os" + "os/user" + "strconv" + "strings" ) type Config struct { - config *exportableConfig - fn string + config *exportableConfig + fn string } type exportableConfig struct { - Hotkeys *Hotkeys - MicVolume *float32 - InputDevice *string - OutputDevice *string - Servers []*server - DefaultServer *string - Username *string - NotifyCommand *string + Hotkeys *Hotkeys + MicVolume *float32 + InputDevice *string + OutputDevice *string + Servers []*server + DefaultServer *string + Username *string + NotifyCommand *string } type server struct { - Host string - Port int - Users []*eUser + Host string + Port int + Users []*eUser } type eUser struct { - Username string - Boost uint16 - Volume float32 + Username string + Boost uint16 + Volume float32 + LocallyMuted bool // Changed from Muted to LocallyMuted to match User struct } func (c *Config) SaveConfig() { - var data []byte - data, err := yaml.Marshal(c.config) - if err != nil { - panic(err) - } - err = ioutil.WriteFile(c.fn+".tmp", data, 0600) - if err != nil { - panic(err) - } - err = os.Rename(c.fn+".tmp", c.fn) - if err != nil { - panic(err) - } + var data []byte + data, err := yaml.Marshal(c.config) + if err != nil { + panic(err) + } + err = ioutil.WriteFile(c.fn+".tmp", data, 0600) + if err != nil { + panic(err) + } + err = os.Rename(c.fn+".tmp", c.fn) + if err != nil { + panic(err) + } } func key(k uiterm.Key) *uiterm.Key { - return &k + return &k } func (c *Config) LoadConfig() { - var jc exportableConfig - jc = exportableConfig{} - jc.Hotkeys = &Hotkeys{ - Talk: key(uiterm.KeyF1), - VolumeDown: key(uiterm.KeyF5), - VolumeUp: key(uiterm.KeyF6), - Exit: key(uiterm.KeyF10), - ToggleTimestamps: key(uiterm.KeyF3), - SwitchViews: key(uiterm.KeyTab), - ScrollUp: key(uiterm.KeyPgup), - ScrollDown: key(uiterm.KeyPgdn), - } - if fileExists(c.fn) { - var data []byte - data = readFile(c.fn) - if data != nil { - err := yaml.UnmarshalStrict(data, &jc) - if err != nil { - fmt.Fprintf(os.Stderr, "Error parsing \"%s\".\n%s\n", c.fn, err.Error()) - os.Exit(1) - } //panic - } //if data - } //if exists - c.config = &jc - if c.config.MicVolume == nil { - micvol := float32(1.0) - jc.MicVolume = &micvol - } - if c.config.InputDevice == nil { - idev := string("") - jc.InputDevice = &idev - } - if c.config.OutputDevice == nil { - odev := string("") - jc.OutputDevice = &odev - } - if c.config.DefaultServer == nil { - defaultServer := string("localhost:64738") - jc.DefaultServer = &defaultServer - } - if c.config.Username == nil { - username := string("") - jc.Username = &username - } - if c.config.NotifyCommand == nil { - ncmd := string("") - jc.NotifyCommand = &ncmd - } + var jc exportableConfig + jc = exportableConfig{} + jc.Hotkeys = &Hotkeys{ + Talk: key(uiterm.KeyF1), + VolumeDown: key(uiterm.KeyF5), + VolumeUp: key(uiterm.KeyF6), + MuteToggle: key(uiterm.KeyF7), // Added mute toggle hotkey + Exit: key(uiterm.KeyF10), + ToggleTimestamps: key(uiterm.KeyF3), + SwitchViews: key(uiterm.KeyTab), + ScrollUp: key(uiterm.KeyPgup), + ScrollDown: key(uiterm.KeyPgdn), + } + if fileExists(c.fn) { + var data []byte + data = readFile(c.fn) + if data != nil { + err := yaml.UnmarshalStrict(data, &jc) + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing \"%s\".\n%s\n", c.fn, err.Error()) + os.Exit(1) + } + } + } + c.config = &jc + if c.config.MicVolume == nil { + micvol := float32(1.0) + jc.MicVolume = &micvol + } + if c.config.InputDevice == nil { + idev := string("") + jc.InputDevice = &idev + } + if c.config.OutputDevice == nil { + odev := string("") + jc.OutputDevice = &odev + } + if c.config.DefaultServer == nil { + defaultServer := string("localhost:64738") + jc.DefaultServer = &defaultServer + } + if c.config.Username == nil { + username := string("") + jc.Username = &username + } + if c.config.NotifyCommand == nil { + ncmd := string("") + jc.NotifyCommand = &ncmd + } } func (c *Config) findServer(address string) *server { - if c.config.Servers == nil { - c.config.Servers = make([]*server, 0) - } - host, port := makeHostPort(address) - var t *server - for _, s := range c.config.Servers { - if s.Port == port && s.Host == host { - t = s - break - } - } - if t == nil { - t = &server{ - Host: host, - Port: port, - } - c.config.Servers = append(c.config.Servers, t) - } - return t + if c.config.Servers == nil { + c.config.Servers = make([]*server, 0) + } + host, port := makeHostPort(address) + var t *server + for _, s := range c.config.Servers { + if s.Port == port && s.Host == host { + t = s + break + } + } + if t == nil { + t = &server{ + Host: host, + Port: port, + } + c.config.Servers = append(c.config.Servers, t) + } + return t } func (c *Config) findUser(address string, username string) *eUser { - var s *server - s = c.findServer(address) - if s.Users == nil { - s.Users = make([]*eUser, 0) - } - var t *eUser - for _, u := range s.Users { - if u.Username == username { - t = u - break - } - } - if t == nil { - t = &eUser{ - Username: username, - Boost: uint16(1), - Volume: 1.0, - } - s.Users = append(s.Users, t) - } - return t + var s *server + s = c.findServer(address) + if s.Users == nil { + s.Users = make([]*eUser, 0) + } + var t *eUser + for _, u := range s.Users { + if u.Username == username { + t = u + break + } + } + if t == nil { + t = &eUser{ + Username: username, + Boost: uint16(1), + Volume: 1.0, + LocallyMuted: false, // Initialize local mute state + } + s.Users = append(s.Users, t) + } + return t +} + +func (c *Config) ToggleMute(u *gumble.User) { + j := c.findUser(u.GetClient().Config.Address, u.Name) + j.LocallyMuted = !j.LocallyMuted + u.LocallyMuted = j.LocallyMuted + c.SaveConfig() } func (c *Config) SetMicVolume(v float32) { - t := float32(v) - c.config.MicVolume = &t + t := float32(v) + c.config.MicVolume = &t } func (c *Config) GetHotkeys() *Hotkeys { - return c.config.Hotkeys + return c.config.Hotkeys } func (c *Config) GetNotifyCommand() *string { - return c.config.NotifyCommand + return c.config.NotifyCommand } func (c *Config) GetInputDevice() *string { - return c.config.InputDevice + return c.config.InputDevice } func (c *Config) GetOutputDevice() *string { - return c.config.OutputDevice + return c.config.OutputDevice } func (c *Config) GetDefaultServer() *string { - return c.config.DefaultServer + return c.config.DefaultServer } func (c *Config) GetUsername() *string { - return c.config.Username + return c.config.Username } func (c *Config) UpdateUser(u *gumble.User) { - var j *eUser - var uc *gumble.Client - uc = u.GetClient() - if uc != nil { - j = c.findUser(uc.Config.Address, u.Name) - u.Boost = j.Boost - u.Volume = j.Volume - if u.Boost < 1 { - u.Boost = 1 - } - } + var j *eUser + var uc *gumble.Client + uc = u.GetClient() + if uc != nil { + j = c.findUser(uc.Config.Address, u.Name) + u.Boost = j.Boost + u.Volume = j.Volume + u.LocallyMuted = j.LocallyMuted // Update LocallyMuted state from config + if u.Boost < 1 { + u.Boost = 1 + } + } } func (c *Config) UpdateConfig(u *gumble.User) { - var j *eUser - j = c.findUser(u.GetClient().Config.Address, u.Name) - j.Boost = u.Boost - j.Volume = u.Volume + var j *eUser + j = c.findUser(u.GetClient().Config.Address, u.Name) + j.Boost = u.Boost + j.Volume = u.Volume + j.LocallyMuted = u.LocallyMuted // Save LocallyMuted state to config } func NewConfig(fn *string) *Config { - var c *Config - c = &Config{} - c.fn = resolvePath(*fn) - c.LoadConfig() - return c + var c *Config + c = &Config{} + c.fn = resolvePath(*fn) + c.LoadConfig() + return c } func readFile(path string) []byte { - if !fileExists(path) { - return nil - } - dat, err := ioutil.ReadFile(path) - if err != nil { - return nil - } - return dat + if !fileExists(path) { + return nil + } + dat, err := ioutil.ReadFile(path) + if err != nil { + return nil + } + return dat } func fileExists(path string) bool { - info, err := os.Stat(path) - if os.IsNotExist(err) { - return false - } - return !info.IsDir() + info, err := os.Stat(path) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() } func resolvePath(path string) string { - if strings.HasPrefix(path, "~/") || strings.Contains(path, "$HOME") { - usr, err := user.Current() - if err != nil { - panic(err) - } - var hd = usr.HomeDir - if strings.Contains(path, "$HOME") { - path = strings.Replace(path, "$HOME", hd, 1) - } else { - path = strings.Replace(path, "~", hd, 1) - } - } - return path + if strings.HasPrefix(path, "~/") || strings.Contains(path, "$HOME") { + usr, err := user.Current() + if err != nil { + panic(err) + } + var hd = usr.HomeDir + if strings.Contains(path, "$HOME") { + path = strings.Replace(path, "$HOME", hd, 1) + } else { + path = strings.Replace(path, "~", hd, 1) + } + } + return path } func makeHostPort(addr string) (string, int) { - parts := strings.Split(addr, ":") - host := parts[0] - port, err := strconv.Atoi(parts[1]) - if err != nil { - panic(err) - } - return host, port + parts := strings.Split(addr, ":") + host := parts[0] + port, err := strconv.Atoi(parts[1]) + if err != nil { + panic(err) + } + return host, port } func Log(s string) { - log(s) + log(s) } func log(s string) { - s += "\n" - // If the file doesn't exist, create it, or append to the file - f, err := os.OpenFile("log.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - panic(err) - } - if _, err := f.Write([]byte(s)); err != nil { - panic(err) - } - if err := f.Close(); err != nil { - panic(err) - } + s += "\n" + f, err := os.OpenFile("log.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + panic(err) + } + if _, err := f.Write([]byte(s)); err != nil { + panic(err) + } + if err := f.Close(); err != nil { + panic(err) + } } diff --git a/gumble/gumble/user.go b/gumble/gumble/user.go index 42db48a..7946bac 100644 --- a/gumble/gumble/user.go +++ b/gumble/gumble/user.go @@ -1,228 +1,235 @@ package gumble import ( - "git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto" - "git.2mb.codes/~cmb/go-openal/openal" - "github.com/golang/protobuf/proto" + "git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto" + "git.2mb.codes/~cmb/go-openal/openal" + "github.com/golang/protobuf/proto" ) // User represents a user that is currently connected to the server. type User struct { - // The user's unique session ID. - Session uint32 - // The user's ID. Contains an invalid value if the user is not registered. - UserID uint32 - // The user's name. - Name string - // The channel that the user is currently in. - Channel *Channel + // The user's unique session ID. + Session uint32 + // The user's ID. Contains an invalid value if the user is not registered. + UserID uint32 + // The user's name. + Name string + // The channel that the user is currently in. + Channel *Channel - // Has the user has been muted? - Muted bool - // Has the user been deafened? - Deafened bool - // Has the user been suppressed? - Suppressed bool - // Has the user been muted by him/herself? - SelfMuted bool - // Has the user been deafened by him/herself? - SelfDeafened bool - // Is the user a priority speaker in the channel? - PrioritySpeaker bool - // Is the user recording audio? - Recording bool + // Has the user has been muted? + Muted bool + // Has the user been deafened? + Deafened bool + // Has the user been suppressed? + Suppressed bool + // Has the user been muted by him/herself? + SelfMuted bool + // Has the user been deafened by him/herself? + SelfDeafened bool + // Is the user a priority speaker in the channel? + PrioritySpeaker bool + // Is the user recording audio? + Recording bool + // Has the user been locally muted by the client? + LocallyMuted bool - // The user's comment. Contains the empty string if the user does not have a - // comment, or if the comment needs to be requested. - Comment string - // The user's comment hash. nil if User.Comment has been populated. - CommentHash []byte - // The hash of the user's certificate (can be empty). - Hash string - // The user's texture (avatar). nil if the user does not have a - // texture, or if the texture needs to be requested. - Texture []byte - // The user's texture hash. nil if User.Texture has been populated. - TextureHash []byte + // The user's comment. Contains the empty string if the user does not have a + // comment, or if the comment needs to be requested. + Comment string + // The user's comment hash. nil if User.Comment has been populated. + CommentHash []byte + // The hash of the user's certificate (can be empty). + Hash string + // The user's texture (avatar). nil if the user does not have a + // texture, or if the texture needs to be requested. + Texture []byte + // The user's texture hash. nil if User.Texture has been populated. + TextureHash []byte - // The user's stats. Contains nil if the stats have not yet been requested. - Stats *UserStats + // The user's stats. Contains nil if the stats have not yet been requested. + Stats *UserStats - client *Client - decoder AudioDecoder + client *Client + decoder AudioDecoder - AudioSource *openal.Source - Boost uint16 - Volume float32 + AudioSource *openal.Source + Boost uint16 + Volume float32 +} + +// IsMuted returns true if the user is muted either server-side or locally +func (u *User) IsMuted() bool { + return u.Muted || u.LocallyMuted } func (u *User) GetClient() *Client { - return u.client + return u.client } // SetTexture sets the user's texture. func (u *User) SetTexture(texture []byte) { - packet := MumbleProto.UserState{ - Session: &u.Session, - Texture: texture, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserState{ + Session: &u.Session, + Texture: texture, + } + u.client.Conn.WriteProto(&packet) } // SetPrioritySpeaker sets if the user is a priority speaker in the channel. func (u *User) SetPrioritySpeaker(prioritySpeaker bool) { - packet := MumbleProto.UserState{ - Session: &u.Session, - PrioritySpeaker: &prioritySpeaker, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserState{ + Session: &u.Session, + PrioritySpeaker: &prioritySpeaker, + } + u.client.Conn.WriteProto(&packet) } // SetRecording sets if the user is recording audio. func (u *User) SetRecording(recording bool) { - packet := MumbleProto.UserState{ - Session: &u.Session, - Recording: &recording, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserState{ + Session: &u.Session, + Recording: &recording, + } + u.client.Conn.WriteProto(&packet) } // IsRegistered returns true if the user's certificate has been registered with // the server. A registered user will have a valid user ID. func (u *User) IsRegistered() bool { - return u.UserID > 0 + return u.UserID > 0 } // Register will register the user with the server. If the client has // permission to do so, the user will shortly be given a UserID. func (u *User) Register() { - packet := MumbleProto.UserState{ - Session: &u.Session, - UserId: proto.Uint32(0), - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserState{ + Session: &u.Session, + UserId: proto.Uint32(0), + } + u.client.Conn.WriteProto(&packet) } // SetComment will set the user's comment to the given string. The user's // comment will be erased if the comment is set to the empty string. func (u *User) SetComment(comment string) { - packet := MumbleProto.UserState{ - Session: &u.Session, - Comment: &comment, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserState{ + Session: &u.Session, + Comment: &comment, + } + u.client.Conn.WriteProto(&packet) } // Move will move the user to the given channel. func (u *User) Move(channel *Channel) { - packet := MumbleProto.UserState{ - Session: &u.Session, - ChannelId: &channel.ID, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserState{ + Session: &u.Session, + ChannelId: &channel.ID, + } + u.client.Conn.WriteProto(&packet) } // Kick will kick the user from the server. func (u *User) Kick(reason string) { - packet := MumbleProto.UserRemove{ - Session: &u.Session, - Reason: &reason, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserRemove{ + Session: &u.Session, + Reason: &reason, + } + u.client.Conn.WriteProto(&packet) } // Ban will ban the user from the server. func (u *User) Ban(reason string) { - packet := MumbleProto.UserRemove{ - Session: &u.Session, - Reason: &reason, - Ban: proto.Bool(true), - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserRemove{ + Session: &u.Session, + Reason: &reason, + Ban: proto.Bool(true), + } + u.client.Conn.WriteProto(&packet) } // SetMuted sets whether the user can transmit audio or not. func (u *User) SetMuted(muted bool) { - packet := MumbleProto.UserState{ - Session: &u.Session, - Mute: &muted, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserState{ + Session: &u.Session, + Mute: &muted, + } + u.client.Conn.WriteProto(&packet) } // SetSuppressed sets whether the user is suppressed by the server or not. func (u *User) SetSuppressed(supressed bool) { - packet := MumbleProto.UserState{ - Session: &u.Session, - Suppress: &supressed, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserState{ + Session: &u.Session, + Suppress: &supressed, + } + u.client.Conn.WriteProto(&packet) } // SetDeafened sets whether the user can receive audio or not. func (u *User) SetDeafened(muted bool) { - packet := MumbleProto.UserState{ - Session: &u.Session, - Deaf: &muted, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserState{ + Session: &u.Session, + Deaf: &muted, + } + u.client.Conn.WriteProto(&packet) } // SetSelfMuted sets whether the user can transmit audio or not. // // This method should only be called on Client.Self(). func (u *User) SetSelfMuted(muted bool) { - packet := MumbleProto.UserState{ - Session: &u.Session, - SelfMute: &muted, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserState{ + Session: &u.Session, + SelfMute: &muted, + } + u.client.Conn.WriteProto(&packet) } // SetSelfDeafened sets whether the user can receive audio or not. // // This method should only be called on Client.Self(). func (u *User) SetSelfDeafened(muted bool) { - packet := MumbleProto.UserState{ - Session: &u.Session, - SelfDeaf: &muted, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserState{ + Session: &u.Session, + SelfDeaf: &muted, + } + u.client.Conn.WriteProto(&packet) } // RequestStats requests that the user's stats be sent to the client. func (u *User) RequestStats() { - packet := MumbleProto.UserStats{ - Session: &u.Session, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserStats{ + Session: &u.Session, + } + u.client.Conn.WriteProto(&packet) } // RequestTexture requests that the user's actual texture (i.e. non-hashed) be // sent to the client. func (u *User) RequestTexture() { - packet := MumbleProto.RequestBlob{ - SessionTexture: []uint32{u.Session}, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.RequestBlob{ + SessionTexture: []uint32{u.Session}, + } + u.client.Conn.WriteProto(&packet) } // RequestComment requests that the user's actual comment (i.e. non-hashed) be // sent to the client. func (u *User) RequestComment() { - packet := MumbleProto.RequestBlob{ - SessionComment: []uint32{u.Session}, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.RequestBlob{ + SessionComment: []uint32{u.Session}, + } + u.client.Conn.WriteProto(&packet) } // Send will send a text message to the user. func (u *User) Send(message string) { - textMessage := TextMessage{ - Users: []*User{u}, - Message: message, - } - u.client.Send(&textMessage) + textMessage := TextMessage{ + Users: []*User{u}, + Message: message, + } + u.client.Send(&textMessage) } // SetPlugin sets the user's plugin data. @@ -233,10 +240,10 @@ func (u *User) Send(message string) { // // PluginShortName + "\x00" + AdditionalContextInformation func (u *User) SetPlugin(context []byte, identity string) { - packet := MumbleProto.UserState{ - Session: &u.Session, - PluginContext: context, - PluginIdentity: &identity, - } - u.client.Conn.WriteProto(&packet) + packet := MumbleProto.UserState{ + Session: &u.Session, + PluginContext: context, + PluginIdentity: &identity, + } + u.client.Conn.WriteProto(&packet) } diff --git a/gumble/gumbleopenal/stream.go b/gumble/gumbleopenal/stream.go index 75f1658..a99b737 100644 --- a/gumble/gumbleopenal/stream.go +++ b/gumble/gumbleopenal/stream.go @@ -164,7 +164,13 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) { go func(e *gumble.AudioStreamEvent) { var source = openal.NewSource() e.User.AudioSource = &source - e.User.AudioSource.SetGain(e.User.Volume) + + // Set initial gain based on volume and mute state + if e.User.LocallyMuted { + e.User.AudioSource.SetGain(0) + } else { + e.User.AudioSource.SetGain(e.User.Volume) + } bufferCount := e.Client.Config.Buffers if bufferCount < 64 { @@ -183,6 +189,11 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) { var raw [maxBufferSize]byte for packet := range e.C { + // Skip processing if user is locally muted + if e.User.LocallyMuted { + continue + } + var boost uint16 = uint16(1) samples := len(packet.AudioBuffer) if samples > cap(raw)/2 { @@ -192,17 +203,13 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) { boost = e.User.Boost // Check if sample count suggests stereo data - // If it's not a multiple of 2, it must be mono - // If it's more than standard frameSize, it's likely stereo isStereo := samples > gumble.AudioDefaultFrameSize && samples%2 == 0 format := openal.FormatMono16 if isStereo { format = openal.FormatStereo16 - // Adjust samples to represent stereo frame count samples = samples / 2 } - // Process samples rawPtr := 0 if isStereo { // Process stereo samples as pairs @@ -244,7 +251,6 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) { buffer := emptyBufs[last] emptyBufs = emptyBufs[:last] - // Set buffer data with correct format buffer.SetData(format, raw[:rawPtr], gumble.AudioSampleRate) source.QueueBuffer(buffer) diff --git a/ui_tree.go b/ui_tree.go index 2fb02aa..b6dba5d 100644 --- a/ui_tree.go +++ b/ui_tree.go @@ -1,176 +1,204 @@ package main import ( - //"math" - // "fmt" - "git.2mb.codes/~cmb/barnard/gumble/gumble" - "git.2mb.codes/~cmb/barnard/uiterm" - "sort" + "git.2mb.codes/~cmb/barnard/gumble/gumble" + "git.2mb.codes/~cmb/barnard/uiterm" + "sort" ) type TreeItem struct { - User *gumble.User - Channel *gumble.Channel + User *gumble.User + Channel *gumble.Channel } func (ti TreeItem) String() string { - if ti.User != nil { - return ti.User.Name - } - if ti.Channel != nil { - return "#" + ti.Channel.Name - } - return "" + if ti.User != nil { + if ti.User.LocallyMuted { + return "[MUTED] " + ti.User.Name + } + return ti.User.Name + } + if ti.Channel != nil { + return "#" + ti.Channel.Name + } + return "" } func (ti TreeItem) TreeItemStyle(fg, bg uiterm.Attribute, active bool) (uiterm.Attribute, uiterm.Attribute) { - if ti.Channel != nil { - fg |= uiterm.AttrBold - } - if active { - fg, bg = bg, fg - } - return fg, bg + if ti.Channel != nil { + fg |= uiterm.AttrBold + } + if active { + fg, bg = bg, fg + } + return fg, bg } func (b *Barnard) TreeItemCharacter(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm.TreeItem, ch rune) { } func (b *Barnard) changeVolume(users []*gumble.User, change float32) { - for _, u := range users { - au := u.AudioSource - if au == nil { - continue - } - var boost uint16 - var cv float32 - var ng float32 - var curboost float32 - curboost = float32((u.Boost - 1)) / 10 - cv = au.GetGain() + curboost - ng = cv + change - boost = uint16(1) - //b.AddOutputLine(fmt.Sprintf("cv %.2f change %.2f ng %.2f",cv,change,ng)) - if ng > 1.0 { - //1.0 will give volume of one and boost of 1 - //1.1 will give volume of 1 and boost of 2 - //b.AddOutputLine(fmt.Sprintf("partperc %.2f",(ng*10))) - perc := uint16((ng * 10)) - 10 - perc += 1 - boost = perc - ng = 1.0 - } - if ng < 0 { - ng = 0.0 - } - //b.AddOutputLine(fmt.Sprintf("boost %d ng %.2f",boost,ng)) - u.Boost = boost - u.Volume = ng - au.SetGain(ng) - b.UserConfig.UpdateConfig(u) - } - b.UserConfig.SaveConfig() + for _, u := range users { + au := u.AudioSource + if au == nil { + continue + } + var boost uint16 + var cv float32 + var ng float32 + var curboost float32 + curboost = float32((u.Boost - 1)) / 10 + cv = au.GetGain() + curboost + ng = cv + change + boost = uint16(1) + if ng > 1.0 { + perc := uint16((ng * 10)) - 10 + perc += 1 + boost = perc + ng = 1.0 + } + if ng < 0 { + ng = 0.0 + } + u.Boost = boost + u.Volume = ng + if !u.LocallyMuted { + au.SetGain(ng) + } + b.UserConfig.UpdateConfig(u) + } + b.UserConfig.SaveConfig() } func makeUsersArray(users gumble.Users) []*gumble.User { - t := make([]*gumble.User, 0, len(users)) - for _, u := range users { - t = append(t, u) - } - return t + t := make([]*gumble.User, 0, len(users)) + for _, u := range users { + t = append(t, u) + } + return t } 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.selectedUser == treeItem.User { - b.SetSelectedUser(nil) - b.GotoChat() - } else { - b.SetSelectedUser(treeItem.User) - b.GotoChat() - } //select - } //if user and not selected - } //if enter key - if treeItem.Channel != nil { - var c = treeItem.Channel - if key == *b.Hotkeys.VolumeDown { - b.changeVolume(makeUsersArray(c.Users), -0.1) - } - if key == *b.Hotkeys.VolumeUp { - b.changeVolume(makeUsersArray(c.Users), 0.1) - } - } //set volume - if treeItem.User != nil { - var u = treeItem.User - if key == *b.Hotkeys.VolumeDown { - b.changeVolume([]*gumble.User{u}, -0.1) - } - if key == *b.Hotkeys.VolumeUp { - b.changeVolume([]*gumble.User{u}, 0.1) - } - } //user highlighted -} //func + 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.selectedUser == 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 { + // Toggle mute for all users in channel + users := makeUsersArray(treeItem.Channel.Users) + for _, u := range users { + b.UserConfig.ToggleMute(u) + if u.AudioSource != nil { + if u.LocallyMuted { + u.AudioSource.SetGain(0) + } else { + u.AudioSource.SetGain(u.Volume) + } + } + } + b.UiTree.Rebuild() + 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 treeItem.User != nil { + if key == *b.Hotkeys.MuteToggle { + // Toggle mute for single user + b.UserConfig.ToggleMute(treeItem.User) + if treeItem.User.AudioSource != nil { + if treeItem.User.LocallyMuted { + treeItem.User.AudioSource.SetGain(0) + } else { + treeItem.User.AudioSource.SetGain(treeItem.User.Volume) + } + } + b.UiTree.Rebuild() + 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) + } + } +} func (b *Barnard) TreeItemBuild(item uiterm.TreeItem) []uiterm.TreeItem { - if b.Client == nil { - return nil - } + if b.Client == nil { + return nil + } - var treeItem TreeItem - if ti, ok := item.(TreeItem); !ok { - root := b.Client.Channels[0] - if root == nil { - return nil - } - return []uiterm.TreeItem{ - TreeItem{ - Channel: root, - }, - } - } else { - treeItem = ti - } + var treeItem TreeItem + if ti, ok := item.(TreeItem); !ok { + root := b.Client.Channels[0] + if root == nil { + return nil + } + return []uiterm.TreeItem{ + TreeItem{ + Channel: root, + }, + } + } else { + treeItem = ti + } - if treeItem.User != nil { - return nil - } + if treeItem.User != nil { + return nil + } - users := []uiterm.TreeItem{} - ul := []*gumble.User{} - for _, user := range treeItem.Channel.Users { - ul = append(ul, user) - var u = ul[len(ul)-1] - _ = u - } - sort.Slice(ul, func(i, j int) bool { - return ul[i].Name < ul[j].Name - }) - for _, user := range ul { - users = append(users, TreeItem{ - User: user, - }) - } + users := []uiterm.TreeItem{} + ul := []*gumble.User{} + for _, user := range treeItem.Channel.Users { + ul = append(ul, user) + var u = ul[len(ul)-1] + _ = u + } + sort.Slice(ul, func(i, j int) bool { + return ul[i].Name < ul[j].Name + }) + for _, user := range ul { + users = append(users, TreeItem{ + User: user, + }) + } - channels := []uiterm.TreeItem{} - cl := []*gumble.Channel{} - for _, subchannel := range treeItem.Channel.Children { - cl = append(cl, subchannel) - } - sort.Slice(cl, func(i, j int) bool { - return cl[i].Name < cl[j].Name - }) - for _, subchannel := range cl { - channels = append(channels, TreeItem{ - Channel: subchannel, - }) - } + channels := []uiterm.TreeItem{} + cl := []*gumble.Channel{} + for _, subchannel := range treeItem.Channel.Children { + cl = append(cl, subchannel) + } + sort.Slice(cl, func(i, j int) bool { + return cl[i].Name < cl[j].Name + }) + for _, subchannel := range cl { + channels = append(channels, TreeItem{ + Channel: subchannel, + }) + } - return append(users, channels...) + return append(users, channels...) }