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
|
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
|
## FIFO Control
|
||||||
|
|
||||||
If you pass the --fifo option to Barnard, a FIFO pipe will be created.
|
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.stateMutex.Lock()
|
||||||
b.Tx = transmitting
|
b.Tx = transmitting
|
||||||
b.stateMutex.Unlock()
|
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 {
|
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.toneTestSaverDetach = b.Client.Config.AttachAudio(b.toneTestSaver)
|
||||||
|
|
||||||
b.setConnected(true)
|
b.setConnected(true)
|
||||||
if b.toneTestAutoTransmit() {
|
if b.shouldStartTransmission() {
|
||||||
b.toneTestStop = make(chan struct{})
|
b.toneTestStop = make(chan struct{})
|
||||||
go StartToneGenerator(b.Client, b.toneTestStop)
|
go StartToneGenerator(b.Client, b.toneTestStop)
|
||||||
b.setTransmitting(true)
|
b.setTransmitting(true)
|
||||||
@@ -149,7 +149,7 @@ func (b *Barnard) connect(reconnect bool) bool {
|
|||||||
b.setConnected(true)
|
b.setConnected(true)
|
||||||
// Dial delivers OnConnect before connect creates the OpenAL stream, so
|
// Dial delivers OnConnect before connect creates the OpenAL stream, so
|
||||||
// start auto-transmit here as well for initial connections and reconnects.
|
// start auto-transmit here as well for initial connections and reconnects.
|
||||||
b.startAutoTransmit()
|
b.startConfiguredTransmission()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,11 +195,15 @@ func (b *Barnard) OnConnect(e *gumble.ConnectEvent) {
|
|||||||
b.AddOutputLine(fmt.Sprintf("Welcome message: %s", wmsg))
|
b.AddOutputLine(fmt.Sprintf("Welcome message: %s", wmsg))
|
||||||
}
|
}
|
||||||
|
|
||||||
b.startAutoTransmit()
|
b.startConfiguredTransmission()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Barnard) startAutoTransmit() {
|
func (b *Barnard) shouldStartTransmission() bool {
|
||||||
if !b.AutoTransmit || b.isTransmitting() {
|
return b.AutoTransmit || (b.UserConfig.GetRememberTransmissionState() && b.UserConfig.GetTransmissionActive())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Barnard) startConfiguredTransmission() {
|
||||||
|
if !b.shouldStartTransmission() || b.isTransmitting() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
started := b.withStream(func(stream *gumbleopenal.Stream) {
|
started := b.withStream(func(stream *gumbleopenal.Stream) {
|
||||||
@@ -208,8 +212,13 @@ func (b *Barnard) startAutoTransmit() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.setTransmitting(true)
|
b.setTransmitting(true)
|
||||||
b.UpdateGeneralStatus(" AutoTx ", true)
|
if b.AutoTransmit {
|
||||||
b.AddOutputLine("Auto-transmit started")
|
b.UpdateGeneralStatus(" AutoTx ", true)
|
||||||
|
b.AddOutputLine("Auto-transmit started")
|
||||||
|
} else {
|
||||||
|
b.UpdateGeneralStatus(" Tx ", true)
|
||||||
|
b.AddOutputLine("Transmission state restored")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if !started {
|
if !started {
|
||||||
return
|
return
|
||||||
@@ -249,7 +258,7 @@ func (b *Barnard) OnDisconnect(e *gumble.DisconnectEvent) {
|
|||||||
} else {
|
} else {
|
||||||
b.AddOutputLine("Disconnected: " + reason)
|
b.AddOutputLine("Disconnected: " + reason)
|
||||||
}
|
}
|
||||||
b.setTransmitting(false)
|
b.clearTransmittingForDisconnect()
|
||||||
b.setConnected(false)
|
b.setConnected(false)
|
||||||
b.postUI(func() {
|
b.postUI(func() {
|
||||||
b.UiTree.Rebuild()
|
b.UiTree.Rebuild()
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"git.stormux.org/storm/barnard/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -444,6 +446,32 @@ func (app *App) remove_server() error {
|
|||||||
return app.ui.message("Removed server " + name)
|
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 {
|
func config_has_value(path, wantedKey string) bool {
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -600,6 +628,8 @@ func (app *App) run() error {
|
|||||||
err = app.manage_certificate()
|
err = app.manage_certificate()
|
||||||
case "Logs":
|
case "Logs":
|
||||||
err = app.manage_logs()
|
err = app.manage_logs()
|
||||||
|
case "Settings":
|
||||||
|
err = app.manage_settings()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -614,6 +644,7 @@ func main_menu_options() []string {
|
|||||||
"Remove server",
|
"Remove server",
|
||||||
"Manage Certificate",
|
"Manage Certificate",
|
||||||
"Logs",
|
"Logs",
|
||||||
|
"Settings",
|
||||||
"Exit",
|
"Exit",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,4 +209,16 @@ func TestProgramIdentityAndMainMenuUseBarnardUI(t *testing.T) {
|
|||||||
t.Fatalf("legacy go-ui name remains in main menu: %q", option)
|
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 {
|
type exportableConfig struct {
|
||||||
Hotkeys *Hotkeys
|
Hotkeys *Hotkeys
|
||||||
AudioDriver *string
|
AudioDriver *string
|
||||||
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
|
||||||
NoiseSuppressionEnabled *bool
|
NoiseSuppressionEnabled *bool
|
||||||
AGCEnabled *bool
|
AGCEnabled *bool
|
||||||
Certificate *string
|
RememberTransmissionState *bool
|
||||||
RecordingFormat *string
|
TransmissionActive *bool
|
||||||
RecordingDirectory *string
|
Certificate *string
|
||||||
|
RecordingFormat *string
|
||||||
|
RecordingDirectory *string
|
||||||
}
|
}
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
@@ -144,6 +146,14 @@ func (c *Config) LoadConfig() {
|
|||||||
enabled := true
|
enabled := true
|
||||||
jc.AGCEnabled = &enabled
|
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 {
|
if c.config.Certificate == nil {
|
||||||
cert := string("")
|
cert := string("")
|
||||||
jc.Certificate = &cert
|
jc.Certificate = &cert
|
||||||
@@ -425,6 +435,42 @@ func (c *Config) SetAGCEnabled(enabled bool) error {
|
|||||||
return c.saveConfigLocked()
|
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 {
|
func (c *Config) GetRecordingFormat() string {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package config
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
"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
|
// Regression: malformed and IPv6 addresses were split at every colon and
|
||||||
// could panic while merely reading a saved user preference.
|
// could panic while merely reading a saved user preference.
|
||||||
func TestMakeHostPortHandlesIPv6AndMalformedAddress(t *testing.T) {
|
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