Add mnemonic mute controls
This commit is contained in:
@@ -226,6 +226,7 @@ When in the treeview, pressing:
|
|||||||
* left or right arrow on a channel changes the volume for all users in that channel
|
* left or right arrow on a channel changes the volume for all users in that channel
|
||||||
* left or right arrow on a user changes the volume for that user.
|
* left or right arrow on a user changes the volume for that user.
|
||||||
* backspace restores the focused user, or every user in the focused channel, to unmuted, 100% volume, and normal boost.
|
* backspace restores the focused user, or every user in the focused channel, to unmuted, 100% volume, and normal boost.
|
||||||
|
* m locally mutes or unmutes the focused user or channel. On your own user, it changes your Mumble self-mute state instead.
|
||||||
* enter on de-selected user selects that user for PM mode.
|
* enter on de-selected user selects that user for PM mode.
|
||||||
* enter on selected user de-selects the user
|
* enter on selected user de-selects the user
|
||||||
* enter on a channel de-selects any selected users (if any) and moves you to the specified channel.
|
* enter on a channel de-selects any selected users (if any) and moves you to the specified channel.
|
||||||
@@ -300,7 +301,8 @@ After running the command above, `barnard` will be compiled as `$(go env GOPATH)
|
|||||||
|
|
||||||
### Key bindings
|
### Key bindings
|
||||||
|
|
||||||
- <kbd>F1</kbd>: toggle voice transmission
|
- <kbd>Ctrl+T</kbd>: toggle voice transmission
|
||||||
|
- <kbd>Alt+M</kbd>: toggle your Mumble self-mute state from anywhere
|
||||||
- <kbd>F9</kbd>: toggle noise suppression
|
- <kbd>F9</kbd>: toggle noise suppression
|
||||||
- <kbd>F12</kbd>: toggle automatic gain control
|
- <kbd>F12</kbd>: toggle automatic gain control
|
||||||
- <kbd>F10</kbd>: open actions menu for the focused tree item
|
- <kbd>F10</kbd>: open actions menu for the focused tree item
|
||||||
@@ -313,7 +315,7 @@ After running the command above, `barnard` will be compiled as `$(go env GOPATH)
|
|||||||
- <kbd>End</kbd>: scroll chat to the bottom
|
- <kbd>End</kbd>: scroll chat to the bottom
|
||||||
- <kbd>Ctrl+Q</kbd>: quit
|
- <kbd>Ctrl+Q</kbd>: quit
|
||||||
|
|
||||||
With the user/channel tree focused, Left/Right changes incoming volume and Backspace restores the focused user or channel to unmuted, 100% volume, and normal boost. In the message input, those keys retain their normal text-editing behavior.
|
With the user/channel tree focused, M locally mutes the focused user or channel, Left/Right changes incoming volume, and Backspace restores the focused user or channel to unmuted, 100% volume, and normal boost. M on your own user toggles self-mute. In the message input, printable letters and editing keys retain their normal behavior.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
+23
-1
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"sync"
|
"sync"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"git.stormux.org/storm/barnard/config"
|
"git.stormux.org/storm/barnard/config"
|
||||||
"git.stormux.org/storm/barnard/fileplayback"
|
"git.stormux.org/storm/barnard/fileplayback"
|
||||||
@@ -251,6 +252,13 @@ func (b *Barnard) StopTransmission() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
if b.Hotkeys != nil && matchesCharacterHotkey(b.Hotkeys.MuteToggle, ch) {
|
||||||
|
b.TreeItemKeyPress(ui, tree, item, *b.Hotkeys.MuteToggle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchesCharacterHotkey(configured *uiterm.Key, ch rune) bool {
|
||||||
|
return configured != nil && uiterm.Key(unicode.ToLower(ch)) == *configured
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchesVolumeResetKey(configured *uiterm.Key, pressed uiterm.Key) bool {
|
func matchesVolumeResetKey(configured *uiterm.Key, pressed uiterm.Key) bool {
|
||||||
@@ -308,6 +316,11 @@ func (b *Barnard) TreeItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm
|
|||||||
|
|
||||||
// Update channel mute state
|
// Update channel mute state
|
||||||
b.setChannelMuted(treeItem.Channel.ID, channelWillBeMuted)
|
b.setChannelMuted(treeItem.Channel.ID, channelWillBeMuted)
|
||||||
|
state := "unmuted"
|
||||||
|
if channelWillBeMuted {
|
||||||
|
state = "muted"
|
||||||
|
}
|
||||||
|
b.AddOutputLine("Locally " + state + " channel " + treeItem.Channel.Name)
|
||||||
if channelWillBeMuted && b.Client.Self.Channel.ID == treeItem.Channel.ID && b.isTransmitting() {
|
if channelWillBeMuted && b.Client.Self.Channel.ID == treeItem.Channel.ID && b.isTransmitting() {
|
||||||
b.StopTransmission()
|
b.StopTransmission()
|
||||||
}
|
}
|
||||||
@@ -329,14 +342,23 @@ func (b *Barnard) TreeItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm
|
|||||||
|
|
||||||
if treeItem.User != nil {
|
if treeItem.User != nil {
|
||||||
if key == *b.Hotkeys.MuteToggle {
|
if key == *b.Hotkeys.MuteToggle {
|
||||||
// Toggle mute for single user
|
if b.Client.Self != nil && treeItem.User.Session == b.Client.Self.Session {
|
||||||
|
b.toggleSelfMute()
|
||||||
|
} else {
|
||||||
|
// Other users are muted only in this Barnard client.
|
||||||
if err := b.UserConfig.ToggleMute(treeItem.User); err != nil {
|
if err := b.UserConfig.ToggleMute(treeItem.User); err != nil {
|
||||||
b.AddOutputLine("Mute: could not save setting: " + err.Error())
|
b.AddOutputLine("Mute: could not save setting: " + err.Error())
|
||||||
}
|
}
|
||||||
b.updateUserGain(treeItem.User)
|
b.updateUserGain(treeItem.User)
|
||||||
|
state := "unmuted"
|
||||||
|
if treeItem.User.LocallyMuted() {
|
||||||
|
state = "muted"
|
||||||
|
}
|
||||||
|
b.AddOutputLine("Locally " + state + " " + treeItem.User.Name)
|
||||||
b.RebuildUserChannelTreePreservingSelection()
|
b.RebuildUserChannelTreePreservingSelection()
|
||||||
b.Ui.Refresh()
|
b.Ui.Refresh()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if key == *b.Hotkeys.VolumeDown {
|
if key == *b.Hotkeys.VolumeDown {
|
||||||
b.changeVolume([]*gumble.User{treeItem.User}, -0.1)
|
b.changeVolume([]*gumble.User{treeItem.User}, -0.1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,24 @@ func TestConcurrentConnectionStateAccess(t *testing.T) {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSelfMutedReadsProtocolState(t *testing.T) {
|
||||||
|
b := &Barnard{Client: &gumble.Client{Self: &gumble.User{SelfMuted: true}}}
|
||||||
|
if !b.selfMuted() {
|
||||||
|
t.Fatal("self mute state was not reported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransmitDoesNotStartWhileSelfMuted(t *testing.T) {
|
||||||
|
b := &Barnard{
|
||||||
|
Client: &gumble.Client{Self: &gumble.User{SelfMuted: true, Channel: &gumble.Channel{ID: 1}}},
|
||||||
|
Connected: true,
|
||||||
|
}
|
||||||
|
b.setTransmit(nil, 2)
|
||||||
|
if b.isTransmitting() {
|
||||||
|
t.Fatal("transmission started while self-muted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConcurrentSelectedUserAccess(t *testing.T) {
|
func TestConcurrentSelectedUserAccess(t *testing.T) {
|
||||||
b := &Barnard{}
|
b := &Barnard{}
|
||||||
user := &gumble.User{Session: 1}
|
user := &gumble.User{Session: 1}
|
||||||
|
|||||||
@@ -22,4 +22,5 @@ type Hotkeys struct {
|
|||||||
AdminMenu *uiterm.Key
|
AdminMenu *uiterm.Key
|
||||||
NoiseSuppressionToggle *uiterm.Key
|
NoiseSuppressionToggle *uiterm.Key
|
||||||
AGCToggle *uiterm.Key
|
AGCToggle *uiterm.Key
|
||||||
|
SelfMuteToggle *uiterm.Key
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,11 +94,11 @@ 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.KeyCtrlT),
|
||||||
VolumeDown: key(uiterm.KeyArrowLeft),
|
VolumeDown: key(uiterm.KeyArrowLeft),
|
||||||
VolumeUp: key(uiterm.KeyArrowRight),
|
VolumeUp: key(uiterm.KeyArrowRight),
|
||||||
VolumeReset: key(uiterm.KeyBackspace),
|
VolumeReset: key(uiterm.KeyBackspace),
|
||||||
MuteToggle: key(uiterm.KeyF7), // Added mute toggle hotkey
|
MuteToggle: key(uiterm.KeyM),
|
||||||
RecordToggle: key(uiterm.KeyCtrlR),
|
RecordToggle: key(uiterm.KeyCtrlR),
|
||||||
Exit: key(uiterm.KeyCtrlQ),
|
Exit: key(uiterm.KeyCtrlQ),
|
||||||
ToggleTimestamps: key(uiterm.KeyF3),
|
ToggleTimestamps: key(uiterm.KeyF3),
|
||||||
@@ -111,6 +111,7 @@ func (c *Config) LoadConfig() {
|
|||||||
AdminMenu: key(uiterm.KeyF10),
|
AdminMenu: key(uiterm.KeyF10),
|
||||||
NoiseSuppressionToggle: key(uiterm.KeyF9),
|
NoiseSuppressionToggle: key(uiterm.KeyF9),
|
||||||
AGCToggle: key(uiterm.KeyF12),
|
AGCToggle: key(uiterm.KeyF12),
|
||||||
|
SelfMuteToggle: key(uiterm.KeyAltM),
|
||||||
}
|
}
|
||||||
if fileExists(c.fn) {
|
if fileExists(c.fn) {
|
||||||
var data []byte
|
var data []byte
|
||||||
@@ -181,11 +182,11 @@ func (c *Config) ensureHotkeys() {
|
|||||||
c.config.Hotkeys = &Hotkeys{}
|
c.config.Hotkeys = &Hotkeys{}
|
||||||
}
|
}
|
||||||
defaults := Hotkeys{
|
defaults := Hotkeys{
|
||||||
Talk: key(uiterm.KeyF1),
|
Talk: key(uiterm.KeyCtrlT),
|
||||||
VolumeDown: key(uiterm.KeyArrowLeft),
|
VolumeDown: key(uiterm.KeyArrowLeft),
|
||||||
VolumeUp: key(uiterm.KeyArrowRight),
|
VolumeUp: key(uiterm.KeyArrowRight),
|
||||||
VolumeReset: key(uiterm.KeyBackspace),
|
VolumeReset: key(uiterm.KeyBackspace),
|
||||||
MuteToggle: key(uiterm.KeyF7),
|
MuteToggle: key(uiterm.KeyM),
|
||||||
RecordToggle: key(uiterm.KeyCtrlR),
|
RecordToggle: key(uiterm.KeyCtrlR),
|
||||||
Exit: key(uiterm.KeyCtrlQ),
|
Exit: key(uiterm.KeyCtrlQ),
|
||||||
ToggleTimestamps: key(uiterm.KeyF3),
|
ToggleTimestamps: key(uiterm.KeyF3),
|
||||||
@@ -198,6 +199,7 @@ func (c *Config) ensureHotkeys() {
|
|||||||
AdminMenu: key(uiterm.KeyF10),
|
AdminMenu: key(uiterm.KeyF10),
|
||||||
NoiseSuppressionToggle: key(uiterm.KeyF9),
|
NoiseSuppressionToggle: key(uiterm.KeyF9),
|
||||||
AGCToggle: key(uiterm.KeyF12),
|
AGCToggle: key(uiterm.KeyF12),
|
||||||
|
SelfMuteToggle: key(uiterm.KeyAltM),
|
||||||
}
|
}
|
||||||
hotkeys := c.config.Hotkeys
|
hotkeys := c.config.Hotkeys
|
||||||
if hotkeys.Talk == nil {
|
if hotkeys.Talk == nil {
|
||||||
@@ -251,6 +253,9 @@ func (c *Config) ensureHotkeys() {
|
|||||||
if hotkeys.AGCToggle == nil {
|
if hotkeys.AGCToggle == nil {
|
||||||
hotkeys.AGCToggle = defaults.AGCToggle
|
hotkeys.AGCToggle = defaults.AGCToggle
|
||||||
}
|
}
|
||||||
|
if hotkeys.SelfMuteToggle == nil {
|
||||||
|
hotkeys.SelfMuteToggle = defaults.SelfMuteToggle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) findServer(address string) *server {
|
func (c *Config) findServer(address string) *server {
|
||||||
|
|||||||
@@ -87,9 +87,12 @@ func TestNewHotkeyDefaults(t *testing.T) {
|
|||||||
got *uiterm.Key
|
got *uiterm.Key
|
||||||
want uiterm.Key
|
want uiterm.Key
|
||||||
}{
|
}{
|
||||||
|
{name: "talk", got: hotkeys.Talk, want: uiterm.KeyCtrlT},
|
||||||
{name: "volume down", got: hotkeys.VolumeDown, want: uiterm.KeyArrowLeft},
|
{name: "volume down", got: hotkeys.VolumeDown, want: uiterm.KeyArrowLeft},
|
||||||
{name: "volume up", got: hotkeys.VolumeUp, want: uiterm.KeyArrowRight},
|
{name: "volume up", got: hotkeys.VolumeUp, want: uiterm.KeyArrowRight},
|
||||||
{name: "volume reset", got: hotkeys.VolumeReset, want: uiterm.KeyBackspace},
|
{name: "volume reset", got: hotkeys.VolumeReset, want: uiterm.KeyBackspace},
|
||||||
|
{name: "tree mute", got: hotkeys.MuteToggle, want: uiterm.KeyM},
|
||||||
|
{name: "self mute", got: hotkeys.SelfMuteToggle, want: uiterm.KeyAltM},
|
||||||
{name: "actions menu", got: hotkeys.AdminMenu, want: uiterm.KeyF10},
|
{name: "actions menu", got: hotkeys.AdminMenu, want: uiterm.KeyF10},
|
||||||
{name: "exit", got: hotkeys.Exit, want: uiterm.KeyCtrlQ},
|
{name: "exit", got: hotkeys.Exit, want: uiterm.KeyCtrlQ},
|
||||||
}
|
}
|
||||||
@@ -102,14 +105,15 @@ func TestNewHotkeyDefaults(t *testing.T) {
|
|||||||
|
|
||||||
func TestExplicitLegacyHotkeysRemainConfigured(t *testing.T) {
|
func TestExplicitLegacyHotkeysRemainConfigured(t *testing.T) {
|
||||||
configPath := filepath.Join(t.TempDir(), "barnard.toml")
|
configPath := filepath.Join(t.TempDir(), "barnard.toml")
|
||||||
data := []byte("[hotkeys]\nvolumedown = \"f5\"\nvolumeup = \"f6\"\nvolumereset = \"f8\"\nadminmenu = \"f11\"\nexit = \"f10\"\n")
|
data := []byte("[hotkeys]\ntalk = \"f1\"\nvolumedown = \"f5\"\nvolumeup = \"f6\"\nvolumereset = \"f8\"\nmutetoggle = \"f7\"\nadminmenu = \"f11\"\nexit = \"f10\"\n")
|
||||||
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hotkeys := NewConfig(&configPath).GetHotkeys()
|
hotkeys := NewConfig(&configPath).GetHotkeys()
|
||||||
if *hotkeys.VolumeDown != uiterm.KeyF5 || *hotkeys.VolumeUp != uiterm.KeyF6 ||
|
if *hotkeys.Talk != uiterm.KeyF1 || *hotkeys.VolumeDown != uiterm.KeyF5 ||
|
||||||
*hotkeys.VolumeReset != uiterm.KeyF8 || *hotkeys.AdminMenu != uiterm.KeyF11 ||
|
*hotkeys.VolumeUp != uiterm.KeyF6 || *hotkeys.VolumeReset != uiterm.KeyF8 ||
|
||||||
|
*hotkeys.MuteToggle != uiterm.KeyF7 || *hotkeys.AdminMenu != uiterm.KeyF11 ||
|
||||||
*hotkeys.Exit != uiterm.KeyF10 {
|
*hotkeys.Exit != uiterm.KeyF10 {
|
||||||
t.Fatalf("explicit legacy hotkeys were replaced: %+v", hotkeys)
|
t.Fatalf("explicit legacy hotkeys were replaced: %+v", hotkeys)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,14 @@ micup() {
|
|||||||
[[ $notify ]] && notify "You are now transmitting."
|
[[ $notify ]] && notify "You are now transmitting."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mute() {
|
||||||
|
[[ $notify ]] && notify "You muted yourself."
|
||||||
|
}
|
||||||
|
|
||||||
|
unmute() {
|
||||||
|
[[ $notify ]] && notify "You unmuted yourself."
|
||||||
|
}
|
||||||
|
|
||||||
msg() {
|
msg() {
|
||||||
[[ $sound ]] && play -n synth .3 sin 1290:1490 sin 1494:1294 remix - norm -8
|
[[ $sound ]] && play -n synth .3 sin 1290:1490 sin 1494:1294 remix - norm -8
|
||||||
[[ $notify ]] && notify "$1 from $2: $3"
|
[[ $notify ]] && notify "$1 from $2: $3"
|
||||||
|
|||||||
@@ -205,6 +205,46 @@ func (b *Barnard) OnVoiceToggle(ui *uiterm.Ui, key uiterm.Key) {
|
|||||||
b.setTransmit(ui, 2)
|
b.setTransmit(ui, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Barnard) OnSelfMuteToggle(ui *uiterm.Ui, key uiterm.Key) {
|
||||||
|
b.toggleSelfMute()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Barnard) selfMuted() bool {
|
||||||
|
if b.Client == nil || b.Client.Self == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
muted := false
|
||||||
|
b.Client.Do(func() { muted = b.Client.Self.SelfMuted })
|
||||||
|
return muted
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Barnard) setSelfMute(muted bool) {
|
||||||
|
if !b.isConnected() || b.Client == nil || b.Client.Self == nil {
|
||||||
|
b.Notify("error", "me", "cannot change self mute while disconnected")
|
||||||
|
b.UpdateGeneralStatus("cannot change self mute while disconnected", true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if b.selfMuted() == muted {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if muted && b.isTransmitting() {
|
||||||
|
b.StopTransmission()
|
||||||
|
}
|
||||||
|
b.Client.Self.SetSelfMuted(muted)
|
||||||
|
event := "unmute"
|
||||||
|
line := "You unmuted yourself"
|
||||||
|
if muted {
|
||||||
|
event = "mute"
|
||||||
|
line = "You muted yourself"
|
||||||
|
}
|
||||||
|
b.Notify(event, "me", "")
|
||||||
|
b.AddOutputLine(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Barnard) toggleSelfMute() {
|
||||||
|
b.setSelfMute(!b.selfMuted())
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Barnard) CommandLog(ui *uiterm.Ui, cmd string) {
|
func (b *Barnard) CommandLog(ui *uiterm.Ui, cmd string) {
|
||||||
b.AddOutputLine("command " + cmd)
|
b.AddOutputLine("command " + cmd)
|
||||||
}
|
}
|
||||||
@@ -381,6 +421,10 @@ func (b *Barnard) setTransmit(ui *uiterm.Ui, val int) {
|
|||||||
b.Notify("error", "me", "cannot transmit in muted channel")
|
b.Notify("error", "me", "cannot transmit in muted channel")
|
||||||
b.setTransmitting(false)
|
b.setTransmitting(false)
|
||||||
b.UpdateGeneralStatus("cannot transmit in muted channel", true)
|
b.UpdateGeneralStatus("cannot transmit in muted channel", true)
|
||||||
|
} else if b.selfMuted() {
|
||||||
|
b.Notify("error", "me", "cannot transmit while self-muted")
|
||||||
|
b.setTransmitting(false)
|
||||||
|
b.UpdateGeneralStatus("cannot transmit while self-muted", true)
|
||||||
} else {
|
} else {
|
||||||
b.setTransmitting(true)
|
b.setTransmitting(true)
|
||||||
if b.ToneTest {
|
if b.ToneTest {
|
||||||
@@ -640,6 +684,7 @@ func (b *Barnard) OnUiInitialize(ui *uiterm.Ui) {
|
|||||||
b.Ui.AddKeyListener(b.OnFocusPress, b.Hotkeys.SwitchViews)
|
b.Ui.AddKeyListener(b.OnFocusPress, b.Hotkeys.SwitchViews)
|
||||||
b.Ui.AddKeyListener(b.OnAdminMenuPress, b.Hotkeys.AdminMenu)
|
b.Ui.AddKeyListener(b.OnAdminMenuPress, b.Hotkeys.AdminMenu)
|
||||||
b.Ui.AddKeyListener(b.OnVoiceToggle, b.Hotkeys.Talk)
|
b.Ui.AddKeyListener(b.OnVoiceToggle, b.Hotkeys.Talk)
|
||||||
|
b.Ui.AddKeyListener(b.OnSelfMuteToggle, b.Hotkeys.SelfMuteToggle)
|
||||||
b.Ui.AddKeyListener(b.OnTimestampToggle, b.Hotkeys.ToggleTimestamps)
|
b.Ui.AddKeyListener(b.OnTimestampToggle, b.Hotkeys.ToggleTimestamps)
|
||||||
b.Ui.AddKeyListener(b.OnNoiseSuppressionToggle, b.Hotkeys.NoiseSuppressionToggle)
|
b.Ui.AddKeyListener(b.OnNoiseSuppressionToggle, b.Hotkeys.NoiseSuppressionToggle)
|
||||||
b.Ui.AddKeyListener(b.OnAGCToggle, b.Hotkeys.AGCToggle)
|
b.Ui.AddKeyListener(b.OnAGCToggle, b.Hotkeys.AGCToggle)
|
||||||
|
|||||||
+20
-2
@@ -14,7 +14,13 @@ func (ti TreeItem) String() string {
|
|||||||
}
|
}
|
||||||
if ti.User != nil {
|
if ti.User != nil {
|
||||||
if ti.User.LocallyMuted() {
|
if ti.User.LocallyMuted() {
|
||||||
return "[MUTED] " + esc(ti.User.Name)
|
return "[LOCALLY MUTED] " + esc(ti.User.Name)
|
||||||
|
}
|
||||||
|
if ti.User.Muted {
|
||||||
|
return "[SERVER MUTED] " + esc(ti.User.Name)
|
||||||
|
}
|
||||||
|
if ti.User.SelfMuted {
|
||||||
|
return "[SELF MUTED] " + esc(ti.User.Name)
|
||||||
}
|
}
|
||||||
// Calculate total volume as percentage
|
// Calculate total volume as percentage
|
||||||
boostPercent := float32(ti.User.Boost()-1) * 10
|
boostPercent := float32(ti.User.Boost()-1) * 10
|
||||||
@@ -76,6 +82,14 @@ func (b *Barnard) resetVolume(users []*gumble.User) {
|
|||||||
b.AddOutputLine("Volume: could not save reset: " + err.Error())
|
b.AddOutputLine("Volume: could not save reset: " + err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if b.Client.Self != nil {
|
||||||
|
for _, user := range users {
|
||||||
|
if user != nil && user.Session == b.Client.Self.Session {
|
||||||
|
b.setSelfMute(false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
b.withStream(func(stream *gumbleopenal.Stream) {
|
b.withStream(func(stream *gumbleopenal.Stream) {
|
||||||
for _, u := range users {
|
for _, u := range users {
|
||||||
stream.UpdateUserGain(u)
|
stream.UpdateUserGain(u)
|
||||||
@@ -148,7 +162,11 @@ func (b *Barnard) TreeItemBuild(item uiterm.TreeItem) []uiterm.TreeItem {
|
|||||||
totalVolume := user.Volume()*100 + boostPercent
|
totalVolume := user.Volume()*100 + boostPercent
|
||||||
display := fmt.Sprintf("%s [%.0f%%]", esc(user.Name), totalVolume)
|
display := fmt.Sprintf("%s [%.0f%%]", esc(user.Name), totalVolume)
|
||||||
if user.LocallyMuted() {
|
if user.LocallyMuted() {
|
||||||
display = "[MUTED] " + display
|
display = "[LOCALLY MUTED] " + display
|
||||||
|
} else if user.Muted {
|
||||||
|
display = "[SERVER MUTED] " + display
|
||||||
|
} else if user.SelfMuted {
|
||||||
|
display = "[SELF MUTED] " + display
|
||||||
}
|
}
|
||||||
ul = append(ul, userDisplay{user: user, name: user.Name, session: user.Session, display: display})
|
ul = append(ul, userDisplay{user: user, name: user.Name, session: user.Session, display: display})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,42 @@ func TestMatchesVolumeResetKeyAcceptsBothTerminalBackspaces(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTreeMuteDefaultMatchesLowerAndUpperM(t *testing.T) {
|
||||||
|
configured := uiterm.KeyM
|
||||||
|
for _, ch := range []rune{'m', 'M'} {
|
||||||
|
if !matchesCharacterHotkey(&configured, ch) {
|
||||||
|
t.Errorf("%q did not match tree mute binding", ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeItemDistinguishesMuteKinds(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
user func() *gumble.User
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "local mute",
|
||||||
|
user: func() *gumble.User {
|
||||||
|
u := &gumble.User{Name: "Username"}
|
||||||
|
u.SetLocallyMuted(true)
|
||||||
|
return u
|
||||||
|
},
|
||||||
|
want: "[LOCALLY MUTED] Username",
|
||||||
|
},
|
||||||
|
{name: "server mute", user: func() *gumble.User { return &gumble.User{Name: "Username", Muted: true} }, want: "[SERVER MUTED] Username"},
|
||||||
|
{name: "self mute", user: func() *gumble.User { return &gumble.User{Name: "Username", SelfMuted: true} }, want: "[SELF MUTED] Username"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := (TreeItem{User: tt.user()}).String(); got != tt.want {
|
||||||
|
t.Fatalf("display = %q, want %q", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Regression: rebuilding the channel tree ranged protocol-owned maps without
|
// Regression: rebuilding the channel tree ranged protocol-owned maps without
|
||||||
// Client.Do while TCP handlers could add or remove users/channels.
|
// Client.Do while TCP handlers could add or remove users/channels.
|
||||||
func TestTreeItemBuildReadsMapsUnderClientSnapshot(t *testing.T) {
|
func TestTreeItemBuildReadsMapsUnderClientSnapshot(t *testing.T) {
|
||||||
|
|||||||
@@ -1,15 +1,31 @@
|
|||||||
package uiterm
|
package uiterm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
// MarshalText implements the encoding.TextMarshaler interface for Key
|
// MarshalText implements the encoding.TextMarshaler interface for Key
|
||||||
// This allows TOML to serialize Key values as strings
|
// This allows TOML to serialize Key values as strings
|
||||||
func (i Key) MarshalText() ([]byte, error) {
|
func (i Key) MarshalText() ([]byte, error) {
|
||||||
|
if r := rune(i); r >= 0x21 && r <= unicode.MaxASCII && unicode.IsPrint(r) {
|
||||||
|
return []byte(string(r)), nil
|
||||||
|
}
|
||||||
return []byte(i.String()), nil
|
return []byte(i.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalText implements the encoding.TextUnmarshaler interface for Key
|
// UnmarshalText implements the encoding.TextUnmarshaler interface for Key
|
||||||
// This allows TOML to deserialize strings back into Key values
|
// This allows TOML to deserialize strings back into Key values
|
||||||
func (i *Key) UnmarshalText(data []byte) error {
|
func (i *Key) UnmarshalText(data []byte) error {
|
||||||
|
if r, size := utf8.DecodeRune(data); r != utf8.RuneError && size == len(data) && unicode.IsPrint(r) {
|
||||||
|
*i = Key(unicode.ToLower(r))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
*i, err = KeyString(string(data))
|
*i, err = KeyString(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid key %q: %w", data, err)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package uiterm
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestPrintableKeyTextRoundTrip(t *testing.T) {
|
||||||
|
encoded, err := KeyM.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(encoded) != "m" {
|
||||||
|
t.Fatalf("encoded key = %q, want m", encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
var decoded Key
|
||||||
|
if err := decoded.UnmarshalText([]byte("M")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if decoded != KeyM {
|
||||||
|
t.Fatalf("decoded key = %v, want %v", decoded, KeyM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNamedKeyTextStillRoundTrips(t *testing.T) {
|
||||||
|
encoded, err := KeyCtrlT.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var decoded Key
|
||||||
|
if err := decoded.UnmarshalText(encoded); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if decoded != KeyCtrlT {
|
||||||
|
t.Fatalf("decoded key = %v, want %v", decoded, KeyCtrlT)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package uiterm
|
||||||
|
|
||||||
|
// Printable keys normally arrive through a view's character handler rather
|
||||||
|
// than the special-key handler.
|
||||||
|
const KeyM Key = 'm'
|
||||||
Reference in New Issue
Block a user