feat: support custom --config path in CLI and GUI

Add config.LoadFrom/SaveTo for arbitrary paths and thread a --config
flag through both entry points:
- CLI: --config loads a file first; explicit flags override file values
- GUI: --config selects the settings file used for load and save
- tests for LoadFrom/SaveTo and default fallback
This commit is contained in:
2026-09-19 08:35:07 +01:00
parent b9a539e133
commit 34efbaf26d
5 changed files with 128 additions and 27 deletions
+17 -8
View File
@@ -71,15 +71,20 @@ var pathVar = func() string {
return filepath.Join(dir, "teleportfling", "config.json")
}()
// Path returns the config file location.
// Path returns the default config file location.
func Path() string {
return pathVar
}
// Load reads the config file, returning Default when it does not exist.
// Load reads the default config file, returning Default when it does not exist.
func Load() (Config, error) {
p := Path()
data, err := os.ReadFile(p)
return LoadFrom(Path())
}
// LoadFrom reads the config file at path, returning Default when it does not
// exist. This lets the CLI and GUI support custom --config paths.
func LoadFrom(path string) (Config, error) {
data, err := os.ReadFile(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return Default(), nil
@@ -113,15 +118,19 @@ func Load() (Config, error) {
return c, nil
}
// Save writes the config file, creating the directory if needed.
// Save writes the default config file, creating the directory if needed.
func Save(c Config) error {
p := Path()
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
return SaveTo(Path(), c)
}
// SaveTo writes the config file at path, creating the directory if needed.
func SaveTo(path string, c Config) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
data, err := json.MarshalIndent(c, "", " ")
if err != nil {
return err
}
return os.WriteFile(p, data, 0o600)
return os.WriteFile(path, data, 0o600)
}
+30
View File
@@ -92,3 +92,33 @@ func TestToFlingerAnnounceDefault(t *testing.T) {
t.Error("ToFlinger announce default should be true")
}
}
// TestLoadFromMissing returns defaults for a non-existent custom path.
func TestLoadFromMissing(t *testing.T) {
c, err := LoadFrom(filepath.Join(t.TempDir(), "missing", "config.json"))
if err != nil {
t.Fatalf("LoadFrom: %v", err)
}
if c.Port != 9756 {
t.Errorf("default port = %d, want 9756", c.Port)
}
}
// TestSaveToLoadFromRoundTrip uses an explicit path independent of pathVar.
func TestSaveToLoadFromRoundTrip(t *testing.T) {
p := filepath.Join(t.TempDir(), "custom", "daemon.json")
announce := false
want := Config{Name: "Daemon", Port: 9901, Quality: 90, FPS: 30, Source: "screen", Audio: true, Announce: &announce}
if err := SaveTo(p, want); err != nil {
t.Fatalf("SaveTo: %v", err)
}
got, err := LoadFrom(p)
if err != nil {
t.Fatalf("LoadFrom: %v", err)
}
if got.Name != want.Name || got.Port != want.Port || *got.Announce != false {
t.Errorf("round trip mismatch: got %+v want %+v", got, want)
}
}
+25 -8
View File
@@ -43,6 +43,9 @@ type App struct {
win fyne.Window
desk desktop.App
// configPath overrides the default config location ("" = default).
configPath string
cfg config.Config
eng *flinger.Engine
lock bool // serialises start/stop against UI actions
@@ -63,9 +66,10 @@ type App struct {
statsDone chan struct{}
}
// Run starts the GUI and blocks until the app exits.
func Run() {
g := &App{}
// Run starts the GUI and blocks until the app exits. configPath selects a
// non-default settings file ("" uses the default location).
func Run(configPath string) {
g := &App{configPath: configPath}
g.fyneApp = app.NewWithID(appID)
g.win = g.fyneApp.NewWindow("TeleportFling")
@@ -74,7 +78,7 @@ func Run() {
g.iconActive = newTrayResource(color.NRGBA{R: 46, G: 125, B: 50, A: 255}, color.NRGBA{R: 150, G: 220, B: 140, A: 255})
// Load persisted settings (falling back to defaults).
g.cfg = mustLoadConfig()
g.cfg = g.mustLoadConfig()
g.buildUI()
@@ -89,9 +93,18 @@ func Run() {
g.win.ShowAndRun()
}
// mustLoadConfig loads the config, logging and falling back to defaults.
func mustLoadConfig() config.Config {
c, err := config.Load()
// mustLoadConfig loads the config (from the configured path, or the default),
// logging and falling back to defaults on error.
func (g *App) mustLoadConfig() config.Config {
var (
c config.Config
err error
)
if g.configPath != "" {
c, err = config.LoadFrom(g.configPath)
} else {
c, err = config.Load()
}
if err != nil {
log.Printf("gui: config load: %v (using defaults)", err)
return config.Default()
@@ -201,7 +214,11 @@ func (g *App) start() {
StreamIndex: g.cfg.StreamIndex,
Announce: &announce,
}
if err := config.Save(g.cfg); err != nil {
if g.configPath != "" {
if err := config.SaveTo(g.configPath, g.cfg); err != nil {
log.Printf("gui: config save: %v", err)
}
} else if err := config.Save(g.cfg); err != nil {
log.Printf("gui: config save: %v", err)
}