103 lines
2.9 KiB
Go
103 lines
2.9 KiB
Go
// Package settings keeps the few preferences the desktop app has: which
|
|
// palette to paint with and which language to speak. Everything else magpie
|
|
// knows is derived from the agents' own files.
|
|
//
|
|
// The file is ~/.config/magpie/settings.json; a missing file means "follow
|
|
// the system" for both.
|
|
package settings
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
// Settings is what the user chose. "" and "system" both mean "follow the OS".
|
|
type Settings struct {
|
|
Theme string `json:"theme,omitempty"` // system | light | dark
|
|
Lang string `json:"lang,omitempty"` // system | en | zh
|
|
Tray string `json:"tray,omitempty"` // what clicking the tray icon opens: panel | window
|
|
// Proxy for magpie's own requests to vendors: "" follows the
|
|
// environment and then the system, "direct" uses none, anything else
|
|
// is the proxy (http://, https:// or socks5://; host:port means http).
|
|
Proxy string `json:"proxy,omitempty"`
|
|
}
|
|
|
|
// Themes and Langs are the accepted values, in the order the UI offers them.
|
|
var (
|
|
Themes = []string{"system", "light", "dark"}
|
|
Langs = []string{"system", "en", "zh"}
|
|
Trays = []string{"panel", "window"}
|
|
)
|
|
|
|
// Path is the settings file.
|
|
func Path() string {
|
|
if x := os.Getenv("XDG_CONFIG_HOME"); x != "" {
|
|
return filepath.Join(x, "magpie", "settings.json")
|
|
}
|
|
home, _ := os.UserHomeDir()
|
|
return filepath.Join(home, ".config", "magpie", "settings.json")
|
|
}
|
|
|
|
// Dir is the folder every magpie file lives in.
|
|
func Dir() string { return filepath.Dir(Path()) }
|
|
|
|
// Load reads the settings; anything missing or unreadable is the default.
|
|
func Load() Settings {
|
|
var s Settings
|
|
if b, err := os.ReadFile(Path()); err == nil {
|
|
_ = json.Unmarshal(b, &s)
|
|
}
|
|
return s.normal()
|
|
}
|
|
|
|
// Save validates and writes the settings.
|
|
func Save(s Settings) error {
|
|
s = s.normal()
|
|
if !slices.Contains(Themes, s.Theme) {
|
|
return fmt.Errorf("theme must be one of %v, not %q", Themes, s.Theme)
|
|
}
|
|
if !slices.Contains(Langs, s.Lang) {
|
|
return fmt.Errorf("language must be one of %v, not %q", Langs, s.Lang)
|
|
}
|
|
if !slices.Contains(Trays, s.Tray) {
|
|
return fmt.Errorf("tray must be one of %v, not %q", Trays, s.Tray)
|
|
}
|
|
s.Proxy = strings.TrimSpace(s.Proxy)
|
|
if s.Proxy != "" && s.Proxy != "direct" {
|
|
raw := s.Proxy
|
|
if !strings.Contains(raw, "://") {
|
|
raw = "http://" + raw
|
|
}
|
|
u, err := url.Parse(raw)
|
|
if err != nil || u.Host == "" || !slices.Contains([]string{"http", "https", "socks5", "socks5h"}, u.Scheme) {
|
|
return fmt.Errorf("proxy must look like http://127.0.0.1:7890 or socks5://127.0.0.1:1080, not %q", s.Proxy)
|
|
}
|
|
}
|
|
if err := os.MkdirAll(Dir(), 0o755); err != nil {
|
|
return err
|
|
}
|
|
b, err := json.MarshalIndent(s, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(Path(), append(b, '\n'), 0o644)
|
|
}
|
|
|
|
func (s Settings) normal() Settings {
|
|
if s.Theme == "" {
|
|
s.Theme = "system"
|
|
}
|
|
if s.Lang == "" {
|
|
s.Lang = "system"
|
|
}
|
|
if s.Tray == "" {
|
|
s.Tray = "panel"
|
|
}
|
|
return s
|
|
}
|