Optionally remember transmission state on reconnect.
This commit is contained in:
@@ -54,6 +54,18 @@ Barnard normalizes the level of your outgoing microphone audio with automatic ga
|
||||
agcenabled = true
|
||||
```
|
||||
|
||||
To restore the last transmit state after reconnecting or restarting Barnard,
|
||||
enable the option in `barnard-ui` under **Settings**, or set it directly in
|
||||
`~/.barnard.toml`:
|
||||
|
||||
```toml
|
||||
remembertransmissionstate = true
|
||||
```
|
||||
|
||||
This option defaults to `false`, preserving the normal behavior of starting
|
||||
with transmission off after a disconnect. When enabled, Barnard updates the
|
||||
internal `transmissionactive` value as transmission is started or stopped.
|
||||
|
||||
## FIFO Control
|
||||
|
||||
If you pass the --fifo option to Barnard, a FIFO pipe will be created.
|
||||
|
||||
+14
@@ -204,6 +204,20 @@ func (b *Barnard) setTransmitting(transmitting bool) {
|
||||
b.stateMutex.Lock()
|
||||
b.Tx = transmitting
|
||||
b.stateMutex.Unlock()
|
||||
if b.UserConfig != nil && b.UserConfig.GetRememberTransmissionState() {
|
||||
if err := b.UserConfig.SetTransmissionActive(transmitting); err != nil && b.Ui != nil {
|
||||
b.AddOutputLine("Could not save transmission state: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clearTransmittingForDisconnect changes only the live state. When transmission
|
||||
// remembering is enabled, an interrupted connection must retain the last state
|
||||
// so the next successful connection can restore it.
|
||||
func (b *Barnard) clearTransmittingForDisconnect() {
|
||||
b.stateMutex.Lock()
|
||||
b.Tx = false
|
||||
b.stateMutex.Unlock()
|
||||
}
|
||||
|
||||
func (b *Barnard) isConnected() bool {
|
||||
|
||||
@@ -81,7 +81,7 @@ func (b *Barnard) connect(reconnect bool) bool {
|
||||
b.toneTestSaverDetach = b.Client.Config.AttachAudio(b.toneTestSaver)
|
||||
|
||||
b.setConnected(true)
|
||||
if b.toneTestAutoTransmit() {
|
||||
if b.shouldStartTransmission() {
|
||||
b.toneTestStop = make(chan struct{})
|
||||
go StartToneGenerator(b.Client, b.toneTestStop)
|
||||
b.setTransmitting(true)
|
||||
@@ -149,7 +149,7 @@ func (b *Barnard) connect(reconnect bool) bool {
|
||||
b.setConnected(true)
|
||||
// Dial delivers OnConnect before connect creates the OpenAL stream, so
|
||||
// start auto-transmit here as well for initial connections and reconnects.
|
||||
b.startAutoTransmit()
|
||||
b.startConfiguredTransmission()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -195,11 +195,15 @@ func (b *Barnard) OnConnect(e *gumble.ConnectEvent) {
|
||||
b.AddOutputLine(fmt.Sprintf("Welcome message: %s", wmsg))
|
||||
}
|
||||
|
||||
b.startAutoTransmit()
|
||||
b.startConfiguredTransmission()
|
||||
}
|
||||
|
||||
func (b *Barnard) startAutoTransmit() {
|
||||
if !b.AutoTransmit || b.isTransmitting() {
|
||||
func (b *Barnard) shouldStartTransmission() bool {
|
||||
return b.AutoTransmit || (b.UserConfig.GetRememberTransmissionState() && b.UserConfig.GetTransmissionActive())
|
||||
}
|
||||
|
||||
func (b *Barnard) startConfiguredTransmission() {
|
||||
if !b.shouldStartTransmission() || b.isTransmitting() {
|
||||
return
|
||||
}
|
||||
started := b.withStream(func(stream *gumbleopenal.Stream) {
|
||||
@@ -208,8 +212,13 @@ func (b *Barnard) startAutoTransmit() {
|
||||
return
|
||||
}
|
||||
b.setTransmitting(true)
|
||||
b.UpdateGeneralStatus(" AutoTx ", true)
|
||||
b.AddOutputLine("Auto-transmit started")
|
||||
if b.AutoTransmit {
|
||||
b.UpdateGeneralStatus(" AutoTx ", true)
|
||||
b.AddOutputLine("Auto-transmit started")
|
||||
} else {
|
||||
b.UpdateGeneralStatus(" Tx ", true)
|
||||
b.AddOutputLine("Transmission state restored")
|
||||
}
|
||||
})
|
||||
if !started {
|
||||
return
|
||||
@@ -249,7 +258,7 @@ func (b *Barnard) OnDisconnect(e *gumble.DisconnectEvent) {
|
||||
} else {
|
||||
b.AddOutputLine("Disconnected: " + reason)
|
||||
}
|
||||
b.setTransmitting(false)
|
||||
b.clearTransmittingForDisconnect()
|
||||
b.setConnected(false)
|
||||
b.postUI(func() {
|
||||
b.UiTree.Rebuild()
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"git.stormux.org/storm/barnard/config"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -444,6 +446,32 @@ func (app *App) remove_server() error {
|
||||
return app.ui.message("Removed server " + name)
|
||||
}
|
||||
|
||||
func settings_menu_options(rememberTransmissionState bool) []string {
|
||||
state := "Off"
|
||||
if rememberTransmissionState {
|
||||
state = "On"
|
||||
}
|
||||
return []string{
|
||||
"Remember transmission state: " + state,
|
||||
"Go Back",
|
||||
}
|
||||
}
|
||||
|
||||
func (app *App) manage_settings() error {
|
||||
cfg := config.NewConfig(&app.paths.BarnardTOML)
|
||||
for {
|
||||
options := settings_menu_options(cfg.GetRememberTransmissionState())
|
||||
selection, cancelled, err := app.ui.menu(options)
|
||||
if err != nil || cancelled || selection == len(options)-1 {
|
||||
return err
|
||||
}
|
||||
enabled := !cfg.GetRememberTransmissionState()
|
||||
if err := cfg.SetRememberTransmissionState(enabled); err != nil {
|
||||
return app.ui.message("Could not save settings: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func config_has_value(path, wantedKey string) bool {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
@@ -600,6 +628,8 @@ func (app *App) run() error {
|
||||
err = app.manage_certificate()
|
||||
case "Logs":
|
||||
err = app.manage_logs()
|
||||
case "Settings":
|
||||
err = app.manage_settings()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -614,6 +644,7 @@ func main_menu_options() []string {
|
||||
"Remove server",
|
||||
"Manage Certificate",
|
||||
"Logs",
|
||||
"Settings",
|
||||
"Exit",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,4 +209,16 @@ func TestProgramIdentityAndMainMenuUseBarnardUI(t *testing.T) {
|
||||
t.Fatalf("legacy go-ui name remains in main menu: %q", option)
|
||||
}
|
||||
}
|
||||
if !slices.Contains(options, "Settings") {
|
||||
t.Fatalf("main menu does not contain Settings: %v", options)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingsMenuReportsRememberTransmissionState(t *testing.T) {
|
||||
if got := settings_menu_options(false)[0]; got != "Remember transmission state: Off" {
|
||||
t.Fatalf("disabled setting label = %q", got)
|
||||
}
|
||||
if got := settings_menu_options(true)[0]; got != "Remember transmission state: On" {
|
||||
t.Fatalf("enabled setting label = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
+60
-14
@@ -21,20 +21,22 @@ type Config struct {
|
||||
}
|
||||
|
||||
type exportableConfig struct {
|
||||
Hotkeys *Hotkeys
|
||||
AudioDriver *string
|
||||
MicVolume *float32
|
||||
InputDevice *string
|
||||
OutputDevice *string
|
||||
Servers []*server
|
||||
DefaultServer *string
|
||||
Username *string
|
||||
NotifyCommand *string
|
||||
NoiseSuppressionEnabled *bool
|
||||
AGCEnabled *bool
|
||||
Certificate *string
|
||||
RecordingFormat *string
|
||||
RecordingDirectory *string
|
||||
Hotkeys *Hotkeys
|
||||
AudioDriver *string
|
||||
MicVolume *float32
|
||||
InputDevice *string
|
||||
OutputDevice *string
|
||||
Servers []*server
|
||||
DefaultServer *string
|
||||
Username *string
|
||||
NotifyCommand *string
|
||||
NoiseSuppressionEnabled *bool
|
||||
AGCEnabled *bool
|
||||
RememberTransmissionState *bool
|
||||
TransmissionActive *bool
|
||||
Certificate *string
|
||||
RecordingFormat *string
|
||||
RecordingDirectory *string
|
||||
}
|
||||
|
||||
type server struct {
|
||||
@@ -144,6 +146,14 @@ func (c *Config) LoadConfig() {
|
||||
enabled := true
|
||||
jc.AGCEnabled = &enabled
|
||||
}
|
||||
if c.config.RememberTransmissionState == nil {
|
||||
enabled := false
|
||||
jc.RememberTransmissionState = &enabled
|
||||
}
|
||||
if c.config.TransmissionActive == nil {
|
||||
active := false
|
||||
jc.TransmissionActive = &active
|
||||
}
|
||||
if c.config.Certificate == nil {
|
||||
cert := string("")
|
||||
jc.Certificate = &cert
|
||||
@@ -425,6 +435,42 @@ func (c *Config) SetAGCEnabled(enabled bool) error {
|
||||
return c.saveConfigLocked()
|
||||
}
|
||||
|
||||
func (c *Config) GetRememberTransmissionState() bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.config.RememberTransmissionState == nil {
|
||||
return false
|
||||
}
|
||||
return *c.config.RememberTransmissionState
|
||||
}
|
||||
|
||||
func (c *Config) SetRememberTransmissionState(enabled bool) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.config.RememberTransmissionState = &enabled
|
||||
if !enabled {
|
||||
active := false
|
||||
c.config.TransmissionActive = &active
|
||||
}
|
||||
return c.saveConfigLocked()
|
||||
}
|
||||
|
||||
func (c *Config) GetTransmissionActive() bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.config.TransmissionActive == nil {
|
||||
return false
|
||||
}
|
||||
return *c.config.TransmissionActive
|
||||
}
|
||||
|
||||
func (c *Config) SetTransmissionActive(active bool) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.config.TransmissionActive = &active
|
||||
return c.saveConfigLocked()
|
||||
}
|
||||
|
||||
func (c *Config) GetRecordingFormat() string {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
@@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
@@ -179,6 +180,44 @@ func TestAGCDefaultsOnAndPersists(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRememberTransmissionStateDefaultsOffAndPersists(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "barnard.toml")
|
||||
cfg := NewConfig(&configPath)
|
||||
if cfg.GetRememberTransmissionState() {
|
||||
t.Fatal("expected transmission-state remembering to default off")
|
||||
}
|
||||
if cfg.GetTransmissionActive() {
|
||||
t.Fatal("expected remembered transmission state to default off")
|
||||
}
|
||||
|
||||
if err := cfg.SetRememberTransmissionState(true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := cfg.SetTransmissionActive(true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
saved, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(saved), "RememberTransmissionState = true") ||
|
||||
!strings.Contains(string(saved), "TransmissionActive = true") {
|
||||
t.Fatalf("saved TOML does not contain the transmission settings:\n%s", saved)
|
||||
}
|
||||
reloaded := NewConfig(&configPath)
|
||||
if !reloaded.GetRememberTransmissionState() || !reloaded.GetTransmissionActive() {
|
||||
t.Fatal("expected enabled transmission-state remembering and active state to persist")
|
||||
}
|
||||
|
||||
if err := reloaded.SetRememberTransmissionState(false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
reloaded = NewConfig(&configPath)
|
||||
if reloaded.GetRememberTransmissionState() || reloaded.GetTransmissionActive() {
|
||||
t.Fatal("expected disabling transmission-state remembering to clear the saved state")
|
||||
}
|
||||
}
|
||||
|
||||
// Regression: malformed and IPv6 addresses were split at every colon and
|
||||
// could panic while merely reading a saved user preference.
|
||||
func TestMakeHostPortHandlesIPv6AndMalformedAddress(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.stormux.org/storm/barnard/config"
|
||||
)
|
||||
|
||||
func TestDisconnectClearPreservesRememberedTransmissionState(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "barnard.toml")
|
||||
userConfig := config.NewConfig(&configPath)
|
||||
if err := userConfig.SetRememberTransmissionState(true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := &Barnard{UserConfig: userConfig}
|
||||
|
||||
b.setTransmitting(true)
|
||||
b.clearTransmittingForDisconnect()
|
||||
|
||||
if b.isTransmitting() {
|
||||
t.Fatal("disconnect did not clear live transmission state")
|
||||
}
|
||||
if !userConfig.GetTransmissionActive() {
|
||||
t.Fatal("disconnect cleared the remembered transmission state")
|
||||
}
|
||||
if !b.shouldStartTransmission() {
|
||||
t.Fatal("remembered active transmission was not selected for restoration")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRememberedTransmissionStateIsIgnoredWhenDisabled(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "barnard.toml")
|
||||
userConfig := config.NewConfig(&configPath)
|
||||
if err := userConfig.SetTransmissionActive(true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := &Barnard{UserConfig: userConfig}
|
||||
|
||||
if b.shouldStartTransmission() {
|
||||
t.Fatal("disabled transmission-state remembering selected transmission for restoration")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user