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
+10 -1
View File
@@ -3,12 +3,21 @@
//
// The streaming engine lives in internal/flinger; this command only wires the
// GUI and tray around it. Headless use is handled by cmd/teleportfling.
//
// Usage:
//
// teleportfling-gui [--config PATH]
package main
import (
"flag"
"teleportfling/internal/gui"
)
func main() {
gui.Run()
configPath := flag.String("config", "", "config file path (default: ~/.config/teleportfling/config.json)")
flag.Parse()
gui.Run(*configPath)
}
+46 -10
View File
@@ -14,10 +14,15 @@
// teleportfling [--name NAME] [--port PORT] [--quality 1..100]
// [--fps N] [--source screen|pattern] [--audio]
// [--stream-index N] [--duration SECONDS]
// [--config PATH]
//
// --source pattern selects the M1 synthetic test pattern (colour bars with a
// moving box) instead of real screen capture, which is useful for testing
// without granting screen-share permission.
//
// --config loads a saved config file first; any flag given explicitly on the
// command line overrides the file value. When running under a service manager
// (e.g. a systemd user unit) use --config to point at the daemon's profile.
package main
import (
@@ -28,6 +33,7 @@ import (
"syscall"
"time"
"teleportfling/internal/config"
"teleportfling/internal/flinger"
)
@@ -42,19 +48,33 @@ func main() {
noAnnounce = flag.Bool("no-announce", false, "do not announce on the LAN (receiver must connect by IP)")
streamIndex = flag.Int("stream-index", 0, "monitor index to capture (screen source)")
duration = flag.Duration("duration", 0, "stream duration (0 = run until interrupted)")
configPath = flag.String("config", "", "config file path (default: ~/.config/teleportfling/config.json)")
)
flag.Parse()
cfg := flinger.Config{
Name: *name,
Port: *port,
Quality: *quality,
FPS: *fps,
Source: *source,
Audio: *withAudio,
StreamIndex: *streamIndex,
Announce: !*noAnnounce,
}
// Base config: loaded from file (or defaults when absent), then overridden
// by any flag the user explicitly set.
cfg := loadCLIConfig(*configPath)
flag.Visit(func(f *flag.Flag) {
switch f.Name {
case "name":
cfg.Name = *name
case "port":
cfg.Port = *port
case "quality":
cfg.Quality = *quality
case "fps":
cfg.FPS = *fps
case "source":
cfg.Source = *source
case "audio":
cfg.Audio = *withAudio
case "no-announce":
cfg.Announce = !*noAnnounce
case "stream-index":
cfg.StreamIndex = *streamIndex
}
})
eng, err := flinger.New(cfg)
if err != nil {
@@ -85,3 +105,19 @@ func main() {
eng.Stop()
log.Printf("teleportfling: stopped")
}
// loadCLIConfig returns the base flinger config. With a --config path it reads
// that file; otherwise it reads the default user config. Missing files fall
// back to defaults.
func loadCLIConfig(path string) flinger.Config {
p := path
if p == "" {
p = config.Path()
}
c, err := config.LoadFrom(p)
if err != nil {
log.Printf("teleportfling: config %s: %v (using defaults)", p, err)
return flinger.DefaultConfig()
}
return c.ToFlinger()
}