// Package config loads and saves the teleportfling settings file. // // The file is JSON at ~/.config/teleportfling/config.json and holds the // user-visible knobs exposed by the GUI (name, port, quality, fps, source, // audio, monitor). Secrets and runtime state are deliberately excluded. package config import ( "encoding/json" "errors" "os" "path/filepath" "teleportfling/internal/flinger" ) // Config mirrors flinger.Config with JSON tags for persistence. type Config struct { Name string `json:"name"` Port int `json:"port"` Quality int `json:"quality"` FPS int `json:"fps"` Source string `json:"source"` Audio bool `json:"audio"` StreamIndex int `json:"stream_index"` Scale float64 `json:"scale"` // Announce is a *bool so an absent JSON key (older config files) keeps // the default instead of silently disabling announcements. Announce *bool `json:"announce"` } // Default returns the default configuration. func Default() Config { c := flinger.DefaultConfig() announce := c.Announce return Config{ Name: c.Name, Port: c.Port, Quality: c.Quality, FPS: c.FPS, Source: c.Source, Audio: c.Audio, StreamIndex: c.StreamIndex, Scale: c.Scale, Announce: &announce, } } // ToFlinger converts a persisted config to the engine config. func (c Config) ToFlinger() flinger.Config { announce := true if c.Announce != nil { announce = *c.Announce } return flinger.Config{ Name: c.Name, Port: c.Port, Quality: c.Quality, FPS: c.FPS, Source: c.Source, Audio: c.Audio, StreamIndex: c.StreamIndex, Scale: c.Scale, Announce: announce, } } // pathVar is the config file location; overridable in tests. var pathVar = func() string { dir, err := os.UserConfigDir() if err != nil { dir = "." } return filepath.Join(dir, "teleportfling", "config.json") }() // Path returns the default config file location. func Path() string { return pathVar } // Load reads the default config file, returning Default when it does not exist. func Load() (Config, error) { 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 } return Config{}, err } var c Config if err := json.Unmarshal(data, &c); err != nil { return Config{}, err } // Fill any zero values with defaults so a hand-edited file still works. d := Default() if c.Port == 0 { c.Port = d.Port } if c.Quality == 0 { c.Quality = d.Quality } if c.FPS == 0 { c.FPS = d.FPS } if c.Source == "" { c.Source = d.Source } if c.Scale <= 0 || c.Scale > 1 { c.Scale = d.Scale } if c.Announce == nil { announce := true c.Announce = &announce } return c, nil } // Save writes the default config file, creating the directory if needed. func Save(c Config) error { 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(path, data, 0o600) }