Added mute option. It can work per user or per channel, so covers deafen functionality too.

This commit is contained in:
Storm Dragon 2025-01-16 13:36:46 -05:00
parent 46d0cbf8f5
commit a5c0e7a71c
5 changed files with 551 additions and 499 deletions

View File

@ -1,19 +1,20 @@
package config package config
import ( import (
"git.2mb.codes/~cmb/barnard/uiterm" "git.2mb.codes/~cmb/barnard/uiterm"
) )
type Hotkeys struct { type Hotkeys struct {
Talk *uiterm.Key Talk *uiterm.Key
VolumeDown *uiterm.Key VolumeDown *uiterm.Key
VolumeUp *uiterm.Key VolumeUp *uiterm.Key
Exit *uiterm.Key MuteToggle *uiterm.Key
ToggleTimestamps *uiterm.Key Exit *uiterm.Key
SwitchViews *uiterm.Key ToggleTimestamps *uiterm.Key
ClearOutput *uiterm.Key SwitchViews *uiterm.Key
ScrollUp *uiterm.Key ClearOutput *uiterm.Key
ScrollDown *uiterm.Key ScrollUp *uiterm.Key
ScrollToTop *uiterm.Key ScrollDown *uiterm.Key
ScrollToBottom *uiterm.Key ScrollToTop *uiterm.Key
ScrollToBottom *uiterm.Key
} }

View File

@ -1,281 +1,291 @@
package config package config
import ( import (
"fmt" "fmt"
"git.2mb.codes/~cmb/barnard/uiterm" "git.2mb.codes/~cmb/barnard/uiterm"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
// "encoding/yaml" "git.2mb.codes/~cmb/barnard/gumble/gumble"
"git.2mb.codes/~cmb/barnard/gumble/gumble" "io/ioutil"
"io/ioutil" "os"
"os" "os/user"
"os/user" "strconv"
"strconv" "strings"
"strings"
) )
type Config struct { type Config struct {
config *exportableConfig config *exportableConfig
fn string fn string
} }
type exportableConfig struct { type exportableConfig struct {
Hotkeys *Hotkeys Hotkeys *Hotkeys
MicVolume *float32 MicVolume *float32
InputDevice *string InputDevice *string
OutputDevice *string OutputDevice *string
Servers []*server Servers []*server
DefaultServer *string DefaultServer *string
Username *string Username *string
NotifyCommand *string NotifyCommand *string
} }
type server struct { type server struct {
Host string Host string
Port int Port int
Users []*eUser Users []*eUser
} }
type eUser struct { type eUser struct {
Username string Username string
Boost uint16 Boost uint16
Volume float32 Volume float32
LocallyMuted bool // Changed from Muted to LocallyMuted to match User struct
} }
func (c *Config) SaveConfig() { func (c *Config) SaveConfig() {
var data []byte var data []byte
data, err := yaml.Marshal(c.config) data, err := yaml.Marshal(c.config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = ioutil.WriteFile(c.fn+".tmp", data, 0600) err = ioutil.WriteFile(c.fn+".tmp", data, 0600)
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = os.Rename(c.fn+".tmp", c.fn) err = os.Rename(c.fn+".tmp", c.fn)
if err != nil { if err != nil {
panic(err) panic(err)
} }
} }
func key(k uiterm.Key) *uiterm.Key { func key(k uiterm.Key) *uiterm.Key {
return &k return &k
} }
func (c *Config) LoadConfig() { func (c *Config) LoadConfig() {
var jc exportableConfig var jc exportableConfig
jc = exportableConfig{} jc = exportableConfig{}
jc.Hotkeys = &Hotkeys{ jc.Hotkeys = &Hotkeys{
Talk: key(uiterm.KeyF1), Talk: key(uiterm.KeyF1),
VolumeDown: key(uiterm.KeyF5), VolumeDown: key(uiterm.KeyF5),
VolumeUp: key(uiterm.KeyF6), VolumeUp: key(uiterm.KeyF6),
Exit: key(uiterm.KeyF10), MuteToggle: key(uiterm.KeyF7), // Added mute toggle hotkey
ToggleTimestamps: key(uiterm.KeyF3), Exit: key(uiterm.KeyF10),
SwitchViews: key(uiterm.KeyTab), ToggleTimestamps: key(uiterm.KeyF3),
ScrollUp: key(uiterm.KeyPgup), SwitchViews: key(uiterm.KeyTab),
ScrollDown: key(uiterm.KeyPgdn), ScrollUp: key(uiterm.KeyPgup),
} ScrollDown: key(uiterm.KeyPgdn),
if fileExists(c.fn) { }
var data []byte if fileExists(c.fn) {
data = readFile(c.fn) var data []byte
if data != nil { data = readFile(c.fn)
err := yaml.UnmarshalStrict(data, &jc) if data != nil {
if err != nil { err := yaml.UnmarshalStrict(data, &jc)
fmt.Fprintf(os.Stderr, "Error parsing \"%s\".\n%s\n", c.fn, err.Error()) if err != nil {
os.Exit(1) fmt.Fprintf(os.Stderr, "Error parsing \"%s\".\n%s\n", c.fn, err.Error())
} //panic os.Exit(1)
} //if data }
} //if exists }
c.config = &jc }
if c.config.MicVolume == nil { c.config = &jc
micvol := float32(1.0) if c.config.MicVolume == nil {
jc.MicVolume = &micvol micvol := float32(1.0)
} jc.MicVolume = &micvol
if c.config.InputDevice == nil { }
idev := string("") if c.config.InputDevice == nil {
jc.InputDevice = &idev idev := string("")
} jc.InputDevice = &idev
if c.config.OutputDevice == nil { }
odev := string("") if c.config.OutputDevice == nil {
jc.OutputDevice = &odev odev := string("")
} jc.OutputDevice = &odev
if c.config.DefaultServer == nil { }
defaultServer := string("localhost:64738") if c.config.DefaultServer == nil {
jc.DefaultServer = &defaultServer defaultServer := string("localhost:64738")
} jc.DefaultServer = &defaultServer
if c.config.Username == nil { }
username := string("") if c.config.Username == nil {
jc.Username = &username username := string("")
} jc.Username = &username
if c.config.NotifyCommand == nil { }
ncmd := string("") if c.config.NotifyCommand == nil {
jc.NotifyCommand = &ncmd ncmd := string("")
} jc.NotifyCommand = &ncmd
}
} }
func (c *Config) findServer(address string) *server { func (c *Config) findServer(address string) *server {
if c.config.Servers == nil { if c.config.Servers == nil {
c.config.Servers = make([]*server, 0) c.config.Servers = make([]*server, 0)
} }
host, port := makeHostPort(address) host, port := makeHostPort(address)
var t *server var t *server
for _, s := range c.config.Servers { for _, s := range c.config.Servers {
if s.Port == port && s.Host == host { if s.Port == port && s.Host == host {
t = s t = s
break break
} }
} }
if t == nil { if t == nil {
t = &server{ t = &server{
Host: host, Host: host,
Port: port, Port: port,
} }
c.config.Servers = append(c.config.Servers, t) c.config.Servers = append(c.config.Servers, t)
} }
return t return t
} }
func (c *Config) findUser(address string, username string) *eUser { func (c *Config) findUser(address string, username string) *eUser {
var s *server var s *server
s = c.findServer(address) s = c.findServer(address)
if s.Users == nil { if s.Users == nil {
s.Users = make([]*eUser, 0) s.Users = make([]*eUser, 0)
} }
var t *eUser var t *eUser
for _, u := range s.Users { for _, u := range s.Users {
if u.Username == username { if u.Username == username {
t = u t = u
break break
} }
} }
if t == nil { if t == nil {
t = &eUser{ t = &eUser{
Username: username, Username: username,
Boost: uint16(1), Boost: uint16(1),
Volume: 1.0, Volume: 1.0,
} LocallyMuted: false, // Initialize local mute state
s.Users = append(s.Users, t) }
} s.Users = append(s.Users, t)
return 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) { func (c *Config) SetMicVolume(v float32) {
t := float32(v) t := float32(v)
c.config.MicVolume = &t c.config.MicVolume = &t
} }
func (c *Config) GetHotkeys() *Hotkeys { func (c *Config) GetHotkeys() *Hotkeys {
return c.config.Hotkeys return c.config.Hotkeys
} }
func (c *Config) GetNotifyCommand() *string { func (c *Config) GetNotifyCommand() *string {
return c.config.NotifyCommand return c.config.NotifyCommand
} }
func (c *Config) GetInputDevice() *string { func (c *Config) GetInputDevice() *string {
return c.config.InputDevice return c.config.InputDevice
} }
func (c *Config) GetOutputDevice() *string { func (c *Config) GetOutputDevice() *string {
return c.config.OutputDevice return c.config.OutputDevice
} }
func (c *Config) GetDefaultServer() *string { func (c *Config) GetDefaultServer() *string {
return c.config.DefaultServer return c.config.DefaultServer
} }
func (c *Config) GetUsername() *string { func (c *Config) GetUsername() *string {
return c.config.Username return c.config.Username
} }
func (c *Config) UpdateUser(u *gumble.User) { func (c *Config) UpdateUser(u *gumble.User) {
var j *eUser var j *eUser
var uc *gumble.Client var uc *gumble.Client
uc = u.GetClient() uc = u.GetClient()
if uc != nil { if uc != nil {
j = c.findUser(uc.Config.Address, u.Name) j = c.findUser(uc.Config.Address, u.Name)
u.Boost = j.Boost u.Boost = j.Boost
u.Volume = j.Volume u.Volume = j.Volume
if u.Boost < 1 { u.LocallyMuted = j.LocallyMuted // Update LocallyMuted state from config
u.Boost = 1 if u.Boost < 1 {
} u.Boost = 1
} }
}
} }
func (c *Config) UpdateConfig(u *gumble.User) { func (c *Config) UpdateConfig(u *gumble.User) {
var j *eUser var j *eUser
j = c.findUser(u.GetClient().Config.Address, u.Name) j = c.findUser(u.GetClient().Config.Address, u.Name)
j.Boost = u.Boost j.Boost = u.Boost
j.Volume = u.Volume j.Volume = u.Volume
j.LocallyMuted = u.LocallyMuted // Save LocallyMuted state to config
} }
func NewConfig(fn *string) *Config { func NewConfig(fn *string) *Config {
var c *Config var c *Config
c = &Config{} c = &Config{}
c.fn = resolvePath(*fn) c.fn = resolvePath(*fn)
c.LoadConfig() c.LoadConfig()
return c return c
} }
func readFile(path string) []byte { func readFile(path string) []byte {
if !fileExists(path) { if !fileExists(path) {
return nil return nil
} }
dat, err := ioutil.ReadFile(path) dat, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return nil return nil
} }
return dat return dat
} }
func fileExists(path string) bool { func fileExists(path string) bool {
info, err := os.Stat(path) info, err := os.Stat(path)
if os.IsNotExist(err) { if os.IsNotExist(err) {
return false return false
} }
return !info.IsDir() return !info.IsDir()
} }
func resolvePath(path string) string { func resolvePath(path string) string {
if strings.HasPrefix(path, "~/") || strings.Contains(path, "$HOME") { if strings.HasPrefix(path, "~/") || strings.Contains(path, "$HOME") {
usr, err := user.Current() usr, err := user.Current()
if err != nil { if err != nil {
panic(err) panic(err)
} }
var hd = usr.HomeDir var hd = usr.HomeDir
if strings.Contains(path, "$HOME") { if strings.Contains(path, "$HOME") {
path = strings.Replace(path, "$HOME", hd, 1) path = strings.Replace(path, "$HOME", hd, 1)
} else { } else {
path = strings.Replace(path, "~", hd, 1) path = strings.Replace(path, "~", hd, 1)
} }
} }
return path return path
} }
func makeHostPort(addr string) (string, int) { func makeHostPort(addr string) (string, int) {
parts := strings.Split(addr, ":") parts := strings.Split(addr, ":")
host := parts[0] host := parts[0]
port, err := strconv.Atoi(parts[1]) port, err := strconv.Atoi(parts[1])
if err != nil { if err != nil {
panic(err) panic(err)
} }
return host, port return host, port
} }
func Log(s string) { func Log(s string) {
log(s) log(s)
} }
func log(s string) { func log(s string) {
s += "\n" 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)
f, err := os.OpenFile("log.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil {
if err != nil { panic(err)
panic(err) }
} if _, err := f.Write([]byte(s)); err != nil {
if _, err := f.Write([]byte(s)); err != nil { panic(err)
panic(err) }
} if err := f.Close(); err != nil {
if err := f.Close(); err != nil { panic(err)
panic(err) }
}
} }

View File

@ -1,228 +1,235 @@
package gumble package gumble
import ( import (
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto" "git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
"git.2mb.codes/~cmb/go-openal/openal" "git.2mb.codes/~cmb/go-openal/openal"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
) )
// User represents a user that is currently connected to the server. // User represents a user that is currently connected to the server.
type User struct { type User struct {
// The user's unique session ID. // The user's unique session ID.
Session uint32 Session uint32
// The user's ID. Contains an invalid value if the user is not registered. // The user's ID. Contains an invalid value if the user is not registered.
UserID uint32 UserID uint32
// The user's name. // The user's name.
Name string Name string
// The channel that the user is currently in. // The channel that the user is currently in.
Channel *Channel Channel *Channel
// Has the user has been muted? // Has the user has been muted?
Muted bool Muted bool
// Has the user been deafened? // Has the user been deafened?
Deafened bool Deafened bool
// Has the user been suppressed? // Has the user been suppressed?
Suppressed bool Suppressed bool
// Has the user been muted by him/herself? // Has the user been muted by him/herself?
SelfMuted bool SelfMuted bool
// Has the user been deafened by him/herself? // Has the user been deafened by him/herself?
SelfDeafened bool SelfDeafened bool
// Is the user a priority speaker in the channel? // Is the user a priority speaker in the channel?
PrioritySpeaker bool PrioritySpeaker bool
// Is the user recording audio? // Is the user recording audio?
Recording bool 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 // 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, or if the comment needs to be requested.
Comment string Comment string
// The user's comment hash. nil if User.Comment has been populated. // The user's comment hash. nil if User.Comment has been populated.
CommentHash []byte CommentHash []byte
// The hash of the user's certificate (can be empty). // The hash of the user's certificate (can be empty).
Hash string Hash string
// The user's texture (avatar). nil if the user does not have a // The user's texture (avatar). nil if the user does not have a
// texture, or if the texture needs to be requested. // texture, or if the texture needs to be requested.
Texture []byte Texture []byte
// The user's texture hash. nil if User.Texture has been populated. // The user's texture hash. nil if User.Texture has been populated.
TextureHash []byte TextureHash []byte
// The user's stats. Contains nil if the stats have not yet been requested. // The user's stats. Contains nil if the stats have not yet been requested.
Stats *UserStats Stats *UserStats
client *Client client *Client
decoder AudioDecoder decoder AudioDecoder
AudioSource *openal.Source AudioSource *openal.Source
Boost uint16 Boost uint16
Volume float32 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 { func (u *User) GetClient() *Client {
return u.client return u.client
} }
// SetTexture sets the user's texture. // SetTexture sets the user's texture.
func (u *User) SetTexture(texture []byte) { func (u *User) SetTexture(texture []byte) {
packet := MumbleProto.UserState{ packet := MumbleProto.UserState{
Session: &u.Session, Session: &u.Session,
Texture: texture, Texture: texture,
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// SetPrioritySpeaker sets if the user is a priority speaker in the channel. // SetPrioritySpeaker sets if the user is a priority speaker in the channel.
func (u *User) SetPrioritySpeaker(prioritySpeaker bool) { func (u *User) SetPrioritySpeaker(prioritySpeaker bool) {
packet := MumbleProto.UserState{ packet := MumbleProto.UserState{
Session: &u.Session, Session: &u.Session,
PrioritySpeaker: &prioritySpeaker, PrioritySpeaker: &prioritySpeaker,
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// SetRecording sets if the user is recording audio. // SetRecording sets if the user is recording audio.
func (u *User) SetRecording(recording bool) { func (u *User) SetRecording(recording bool) {
packet := MumbleProto.UserState{ packet := MumbleProto.UserState{
Session: &u.Session, Session: &u.Session,
Recording: &recording, Recording: &recording,
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// IsRegistered returns true if the user's certificate has been registered with // IsRegistered returns true if the user's certificate has been registered with
// the server. A registered user will have a valid user ID. // the server. A registered user will have a valid user ID.
func (u *User) IsRegistered() bool { 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 // Register will register the user with the server. If the client has
// permission to do so, the user will shortly be given a UserID. // permission to do so, the user will shortly be given a UserID.
func (u *User) Register() { func (u *User) Register() {
packet := MumbleProto.UserState{ packet := MumbleProto.UserState{
Session: &u.Session, Session: &u.Session,
UserId: proto.Uint32(0), UserId: proto.Uint32(0),
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// SetComment will set the user's comment to the given string. The user's // 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. // comment will be erased if the comment is set to the empty string.
func (u *User) SetComment(comment string) { func (u *User) SetComment(comment string) {
packet := MumbleProto.UserState{ packet := MumbleProto.UserState{
Session: &u.Session, Session: &u.Session,
Comment: &comment, Comment: &comment,
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// Move will move the user to the given channel. // Move will move the user to the given channel.
func (u *User) Move(channel *Channel) { func (u *User) Move(channel *Channel) {
packet := MumbleProto.UserState{ packet := MumbleProto.UserState{
Session: &u.Session, Session: &u.Session,
ChannelId: &channel.ID, ChannelId: &channel.ID,
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// Kick will kick the user from the server. // Kick will kick the user from the server.
func (u *User) Kick(reason string) { func (u *User) Kick(reason string) {
packet := MumbleProto.UserRemove{ packet := MumbleProto.UserRemove{
Session: &u.Session, Session: &u.Session,
Reason: &reason, Reason: &reason,
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// Ban will ban the user from the server. // Ban will ban the user from the server.
func (u *User) Ban(reason string) { func (u *User) Ban(reason string) {
packet := MumbleProto.UserRemove{ packet := MumbleProto.UserRemove{
Session: &u.Session, Session: &u.Session,
Reason: &reason, Reason: &reason,
Ban: proto.Bool(true), Ban: proto.Bool(true),
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// SetMuted sets whether the user can transmit audio or not. // SetMuted sets whether the user can transmit audio or not.
func (u *User) SetMuted(muted bool) { func (u *User) SetMuted(muted bool) {
packet := MumbleProto.UserState{ packet := MumbleProto.UserState{
Session: &u.Session, Session: &u.Session,
Mute: &muted, Mute: &muted,
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// SetSuppressed sets whether the user is suppressed by the server or not. // SetSuppressed sets whether the user is suppressed by the server or not.
func (u *User) SetSuppressed(supressed bool) { func (u *User) SetSuppressed(supressed bool) {
packet := MumbleProto.UserState{ packet := MumbleProto.UserState{
Session: &u.Session, Session: &u.Session,
Suppress: &supressed, Suppress: &supressed,
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// SetDeafened sets whether the user can receive audio or not. // SetDeafened sets whether the user can receive audio or not.
func (u *User) SetDeafened(muted bool) { func (u *User) SetDeafened(muted bool) {
packet := MumbleProto.UserState{ packet := MumbleProto.UserState{
Session: &u.Session, Session: &u.Session,
Deaf: &muted, Deaf: &muted,
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// SetSelfMuted sets whether the user can transmit audio or not. // SetSelfMuted sets whether the user can transmit audio or not.
// //
// This method should only be called on Client.Self(). // This method should only be called on Client.Self().
func (u *User) SetSelfMuted(muted bool) { func (u *User) SetSelfMuted(muted bool) {
packet := MumbleProto.UserState{ packet := MumbleProto.UserState{
Session: &u.Session, Session: &u.Session,
SelfMute: &muted, SelfMute: &muted,
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// SetSelfDeafened sets whether the user can receive audio or not. // SetSelfDeafened sets whether the user can receive audio or not.
// //
// This method should only be called on Client.Self(). // This method should only be called on Client.Self().
func (u *User) SetSelfDeafened(muted bool) { func (u *User) SetSelfDeafened(muted bool) {
packet := MumbleProto.UserState{ packet := MumbleProto.UserState{
Session: &u.Session, Session: &u.Session,
SelfDeaf: &muted, SelfDeaf: &muted,
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// RequestStats requests that the user's stats be sent to the client. // RequestStats requests that the user's stats be sent to the client.
func (u *User) RequestStats() { func (u *User) RequestStats() {
packet := MumbleProto.UserStats{ packet := MumbleProto.UserStats{
Session: &u.Session, Session: &u.Session,
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// RequestTexture requests that the user's actual texture (i.e. non-hashed) be // RequestTexture requests that the user's actual texture (i.e. non-hashed) be
// sent to the client. // sent to the client.
func (u *User) RequestTexture() { func (u *User) RequestTexture() {
packet := MumbleProto.RequestBlob{ packet := MumbleProto.RequestBlob{
SessionTexture: []uint32{u.Session}, SessionTexture: []uint32{u.Session},
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// RequestComment requests that the user's actual comment (i.e. non-hashed) be // RequestComment requests that the user's actual comment (i.e. non-hashed) be
// sent to the client. // sent to the client.
func (u *User) RequestComment() { func (u *User) RequestComment() {
packet := MumbleProto.RequestBlob{ packet := MumbleProto.RequestBlob{
SessionComment: []uint32{u.Session}, SessionComment: []uint32{u.Session},
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }
// Send will send a text message to the user. // Send will send a text message to the user.
func (u *User) Send(message string) { func (u *User) Send(message string) {
textMessage := TextMessage{ textMessage := TextMessage{
Users: []*User{u}, Users: []*User{u},
Message: message, Message: message,
} }
u.client.Send(&textMessage) u.client.Send(&textMessage)
} }
// SetPlugin sets the user's plugin data. // SetPlugin sets the user's plugin data.
@ -233,10 +240,10 @@ func (u *User) Send(message string) {
// //
// PluginShortName + "\x00" + AdditionalContextInformation // PluginShortName + "\x00" + AdditionalContextInformation
func (u *User) SetPlugin(context []byte, identity string) { func (u *User) SetPlugin(context []byte, identity string) {
packet := MumbleProto.UserState{ packet := MumbleProto.UserState{
Session: &u.Session, Session: &u.Session,
PluginContext: context, PluginContext: context,
PluginIdentity: &identity, PluginIdentity: &identity,
} }
u.client.Conn.WriteProto(&packet) u.client.Conn.WriteProto(&packet)
} }

View File

@ -164,7 +164,13 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
go func(e *gumble.AudioStreamEvent) { go func(e *gumble.AudioStreamEvent) {
var source = openal.NewSource() var source = openal.NewSource()
e.User.AudioSource = &source 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 bufferCount := e.Client.Config.Buffers
if bufferCount < 64 { if bufferCount < 64 {
@ -183,6 +189,11 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
var raw [maxBufferSize]byte var raw [maxBufferSize]byte
for packet := range e.C { for packet := range e.C {
// Skip processing if user is locally muted
if e.User.LocallyMuted {
continue
}
var boost uint16 = uint16(1) var boost uint16 = uint16(1)
samples := len(packet.AudioBuffer) samples := len(packet.AudioBuffer)
if samples > cap(raw)/2 { if samples > cap(raw)/2 {
@ -192,17 +203,13 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
boost = e.User.Boost boost = e.User.Boost
// Check if sample count suggests stereo data // 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 isStereo := samples > gumble.AudioDefaultFrameSize && samples%2 == 0
format := openal.FormatMono16 format := openal.FormatMono16
if isStereo { if isStereo {
format = openal.FormatStereo16 format = openal.FormatStereo16
// Adjust samples to represent stereo frame count
samples = samples / 2 samples = samples / 2
} }
// Process samples
rawPtr := 0 rawPtr := 0
if isStereo { if isStereo {
// Process stereo samples as pairs // Process stereo samples as pairs
@ -244,7 +251,6 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
buffer := emptyBufs[last] buffer := emptyBufs[last]
emptyBufs = emptyBufs[:last] emptyBufs = emptyBufs[:last]
// Set buffer data with correct format
buffer.SetData(format, raw[:rawPtr], gumble.AudioSampleRate) buffer.SetData(format, raw[:rawPtr], gumble.AudioSampleRate)
source.QueueBuffer(buffer) source.QueueBuffer(buffer)

View File

@ -1,176 +1,204 @@
package main package main
import ( import (
//"math" "git.2mb.codes/~cmb/barnard/gumble/gumble"
// "fmt" "git.2mb.codes/~cmb/barnard/uiterm"
"git.2mb.codes/~cmb/barnard/gumble/gumble" "sort"
"git.2mb.codes/~cmb/barnard/uiterm"
"sort"
) )
type TreeItem struct { type TreeItem struct {
User *gumble.User User *gumble.User
Channel *gumble.Channel Channel *gumble.Channel
} }
func (ti TreeItem) String() string { func (ti TreeItem) String() string {
if ti.User != nil { if ti.User != nil {
return ti.User.Name if ti.User.LocallyMuted {
} return "[MUTED] " + ti.User.Name
if ti.Channel != nil { }
return "#" + ti.Channel.Name return ti.User.Name
} }
return "" if ti.Channel != nil {
return "#" + ti.Channel.Name
}
return ""
} }
func (ti TreeItem) TreeItemStyle(fg, bg uiterm.Attribute, active bool) (uiterm.Attribute, uiterm.Attribute) { func (ti TreeItem) TreeItemStyle(fg, bg uiterm.Attribute, active bool) (uiterm.Attribute, uiterm.Attribute) {
if ti.Channel != nil { if ti.Channel != nil {
fg |= uiterm.AttrBold fg |= uiterm.AttrBold
} }
if active { if active {
fg, bg = bg, fg fg, bg = bg, fg
} }
return fg, bg return fg, bg
} }
func (b *Barnard) TreeItemCharacter(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm.TreeItem, ch rune) { func (b *Barnard) TreeItemCharacter(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm.TreeItem, ch rune) {
} }
func (b *Barnard) changeVolume(users []*gumble.User, change float32) { func (b *Barnard) changeVolume(users []*gumble.User, change float32) {
for _, u := range users { for _, u := range users {
au := u.AudioSource au := u.AudioSource
if au == nil { if au == nil {
continue continue
} }
var boost uint16 var boost uint16
var cv float32 var cv float32
var ng float32 var ng float32
var curboost float32 var curboost float32
curboost = float32((u.Boost - 1)) / 10 curboost = float32((u.Boost - 1)) / 10
cv = au.GetGain() + curboost cv = au.GetGain() + curboost
ng = cv + change ng = cv + change
boost = uint16(1) boost = uint16(1)
//b.AddOutputLine(fmt.Sprintf("cv %.2f change %.2f ng %.2f",cv,change,ng)) if ng > 1.0 {
if ng > 1.0 { perc := uint16((ng * 10)) - 10
//1.0 will give volume of one and boost of 1 perc += 1
//1.1 will give volume of 1 and boost of 2 boost = perc
//b.AddOutputLine(fmt.Sprintf("partperc %.2f",(ng*10))) ng = 1.0
perc := uint16((ng * 10)) - 10 }
perc += 1 if ng < 0 {
boost = perc ng = 0.0
ng = 1.0 }
} u.Boost = boost
if ng < 0 { u.Volume = ng
ng = 0.0 if !u.LocallyMuted {
} au.SetGain(ng)
//b.AddOutputLine(fmt.Sprintf("boost %d ng %.2f",boost,ng)) }
u.Boost = boost b.UserConfig.UpdateConfig(u)
u.Volume = ng }
au.SetGain(ng) b.UserConfig.SaveConfig()
b.UserConfig.UpdateConfig(u)
}
b.UserConfig.SaveConfig()
} }
func makeUsersArray(users gumble.Users) []*gumble.User { func makeUsersArray(users gumble.Users) []*gumble.User {
t := make([]*gumble.User, 0, len(users)) t := make([]*gumble.User, 0, len(users))
for _, u := range users { for _, u := range users {
t = append(t, u) t = append(t, u)
} }
return t return t
} }
func (b *Barnard) TreeItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm.TreeItem, key uiterm.Key) { func (b *Barnard) TreeItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm.TreeItem, key uiterm.Key) {
treeItem := item.(TreeItem) treeItem := item.(TreeItem)
if key == uiterm.KeyEnter { if key == uiterm.KeyEnter {
if treeItem.Channel != nil { if treeItem.Channel != nil {
b.Client.Self.Move(treeItem.Channel) b.Client.Self.Move(treeItem.Channel)
b.SetSelectedUser(nil) b.SetSelectedUser(nil)
b.GotoChat() b.GotoChat()
} }
if treeItem.User != nil { if treeItem.User != nil {
if b.selectedUser == treeItem.User { if b.selectedUser == treeItem.User {
b.SetSelectedUser(nil) b.SetSelectedUser(nil)
b.GotoChat() b.GotoChat()
} else { } else {
b.SetSelectedUser(treeItem.User) b.SetSelectedUser(treeItem.User)
b.GotoChat() b.GotoChat()
} //select }
} //if user and not selected }
} //if enter key }
if treeItem.Channel != nil {
var c = treeItem.Channel // Handle mute toggle
if key == *b.Hotkeys.VolumeDown { if treeItem.Channel != nil {
b.changeVolume(makeUsersArray(c.Users), -0.1) if key == *b.Hotkeys.MuteToggle {
} // Toggle mute for all users in channel
if key == *b.Hotkeys.VolumeUp { users := makeUsersArray(treeItem.Channel.Users)
b.changeVolume(makeUsersArray(c.Users), 0.1) for _, u := range users {
} b.UserConfig.ToggleMute(u)
} //set volume if u.AudioSource != nil {
if treeItem.User != nil { if u.LocallyMuted {
var u = treeItem.User u.AudioSource.SetGain(0)
if key == *b.Hotkeys.VolumeDown { } else {
b.changeVolume([]*gumble.User{u}, -0.1) u.AudioSource.SetGain(u.Volume)
} }
if key == *b.Hotkeys.VolumeUp { }
b.changeVolume([]*gumble.User{u}, 0.1) }
} b.UiTree.Rebuild()
} //user highlighted b.Ui.Refresh()
} //func }
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 { func (b *Barnard) TreeItemBuild(item uiterm.TreeItem) []uiterm.TreeItem {
if b.Client == nil { if b.Client == nil {
return nil return nil
} }
var treeItem TreeItem var treeItem TreeItem
if ti, ok := item.(TreeItem); !ok { if ti, ok := item.(TreeItem); !ok {
root := b.Client.Channels[0] root := b.Client.Channels[0]
if root == nil { if root == nil {
return nil return nil
} }
return []uiterm.TreeItem{ return []uiterm.TreeItem{
TreeItem{ TreeItem{
Channel: root, Channel: root,
}, },
} }
} else { } else {
treeItem = ti treeItem = ti
} }
if treeItem.User != nil { if treeItem.User != nil {
return nil return nil
} }
users := []uiterm.TreeItem{} users := []uiterm.TreeItem{}
ul := []*gumble.User{} ul := []*gumble.User{}
for _, user := range treeItem.Channel.Users { for _, user := range treeItem.Channel.Users {
ul = append(ul, user) ul = append(ul, user)
var u = ul[len(ul)-1] var u = ul[len(ul)-1]
_ = u _ = u
} }
sort.Slice(ul, func(i, j int) bool { sort.Slice(ul, func(i, j int) bool {
return ul[i].Name < ul[j].Name return ul[i].Name < ul[j].Name
}) })
for _, user := range ul { for _, user := range ul {
users = append(users, TreeItem{ users = append(users, TreeItem{
User: user, User: user,
}) })
} }
channels := []uiterm.TreeItem{} channels := []uiterm.TreeItem{}
cl := []*gumble.Channel{} cl := []*gumble.Channel{}
for _, subchannel := range treeItem.Channel.Children { for _, subchannel := range treeItem.Channel.Children {
cl = append(cl, subchannel) cl = append(cl, subchannel)
} }
sort.Slice(cl, func(i, j int) bool { sort.Slice(cl, func(i, j int) bool {
return cl[i].Name < cl[j].Name return cl[i].Name < cl[j].Name
}) })
for _, subchannel := range cl { for _, subchannel := range cl {
channels = append(channels, TreeItem{ channels = append(channels, TreeItem{
Channel: subchannel, Channel: subchannel,
}) })
} }
return append(users, channels...) return append(users, channels...)
} }