hyprland/wallpaper: add wallpaper management commands

parent aa008de8
......@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"ximperconf/config"
"ximperconf/hyprland/wallpaper"
"github.com/urfave/cli/v3"
)
......@@ -232,6 +233,7 @@ func CommandList() *cli.Command {
},
},
},
wallpaper.CommandList(),
},
}
}
......
package wallpaper
import (
"context"
"fmt"
"ximperconf/config"
"ximperconf/ui"
"github.com/fatih/color"
"github.com/urfave/cli/v3"
)
func WallpaperSetCommand(ctx context.Context, cmd *cli.Command) error {
lightPath := cmd.String("light")
darkPath := cmd.String("dark")
monitor := cmd.String("monitor")
if monitor == "default" {
monitor = ""
}
if lightPath == "" && darkPath == "" {
return fmt.Errorf("укажите --light и/или --dark")
}
manager, err := NewWallpaperManager()
if err != nil {
return err
}
monitors := []string{monitor}
if monitor == "all" {
monitors, err = GetHyprlandMonitors()
if err != nil {
return fmt.Errorf("не удалось получить список мониторов: %w", err)
}
}
for _, mon := range monitors {
if err := manager.SetWallpaper(lightPath, darkPath, mon); err != nil {
return err
}
}
if err := manager.Save(); err != nil {
return err
}
color.Green("Обои сохранены в конфиг")
theme := manager.GetCurrentTheme()
if theme == "" {
if darkPath != "" {
theme = ThemeDark
} else {
theme = ThemeLight
}
manager.SetCurrentTheme(theme)
manager.Save()
}
if err := manager.ApplyAll(theme); err != nil {
color.Yellow("Не удалось применить обои: %v", err)
} else {
color.Green("Обои применены")
}
return nil
}
func WallpaperGetCommand(ctx context.Context, cmd *cli.Command) error {
manager, err := NewWallpaperManager()
if err != nil {
return err
}
monitor := cmd.String("monitor")
if monitor == "default" {
monitor = ""
}
if cmd.Bool("pair") {
light := manager.GetWallpaper(monitor, ThemeLight)
dark := manager.GetWallpaper(monitor, ThemeDark)
fmt.Printf("[\"%s\", \"%s\"]\n", light, dark)
return nil
}
var theme ThemeType
if cmd.Bool("light") {
theme = ThemeLight
} else if cmd.Bool("dark") {
theme = ThemeDark
} else {
theme = manager.GetCurrentTheme()
if theme == "" {
return fmt.Errorf("укажите --light или --dark, либо установите текущую тему через apply")
}
}
path := manager.GetWallpaper(monitor, theme)
if config.IsJSON(cmd) {
return ui.PrintJSON(map[string]string{
"path": path,
"monitor": monitor,
"theme": string(theme),
})
}
if path == "" {
return fmt.Errorf("обои не настроены")
}
fmt.Println(path)
return nil
}
func WallpaperApplyCommand(ctx context.Context, cmd *cli.Command) error {
themeArg := cmd.Args().Get(0)
manager, err := NewWallpaperManager()
if err != nil {
return err
}
var targetTheme ThemeType
if themeArg != "" {
if themeArg != "light" && themeArg != "dark" {
return fmt.Errorf("укажите тему: light или dark")
}
targetTheme = ThemeType(themeArg)
manager.SetCurrentTheme(targetTheme)
if err := manager.Save(); err != nil {
return err
}
} else {
targetTheme = manager.GetCurrentTheme()
if targetTheme == "" {
return fmt.Errorf("текущая тема не установлена, укажите light или dark")
}
}
if err := manager.ApplyAll(targetTheme); err != nil {
return err
}
color.Green("Обои применены (тема: %s)", targetTheme)
return nil
}
func WallpaperInfoCommand(ctx context.Context, cmd *cli.Command) error {
manager, err := NewWallpaperManager()
if err != nil {
return err
}
monitors, err := GetHyprlandMonitors()
if err != nil {
return fmt.Errorf("не удалось получить список мониторов: %w", err)
}
type monitorEntry struct {
Monitor string `json:"monitor"`
Light string `json:"light"`
Dark string `json:"dark"`
}
entries := []monitorEntry{
{
Monitor: "default",
Light: manager.Config.Default.Light,
Dark: manager.Config.Default.Dark,
},
}
for _, mon := range monitors {
entries = append(entries, monitorEntry{
Monitor: mon,
Light: manager.GetWallpaper(mon, ThemeLight),
Dark: manager.GetWallpaper(mon, ThemeDark),
})
}
if config.IsJSON(cmd) {
return ui.PrintJSON(entries)
}
for _, e := range entries {
fmt.Printf("%s:\n", e.Monitor)
fmt.Printf(" light: %s\n", e.Light)
fmt.Printf(" dark: %s\n", e.Dark)
}
return nil
}
package wallpaper
import (
"fmt"
"os/exec"
)
type HyprpaperBackend struct{}
func (b *HyprpaperBackend) Name() string { return "hyprpaper" }
func (b *HyprpaperBackend) SetWallpaper(monitor, path string) error {
arg := fmt.Sprintf("%s,%s,%s", monitor, path, "contain")
cmd := exec.Command("hyprctl", "hyprpaper", "wallpaper", arg)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%v: %s", err, string(out))
}
return nil
}
package wallpaper
import "fmt"
type WallpaperBackend interface {
Name() string
SetWallpaper(monitor, path string) error
}
var backends = map[string]WallpaperBackend{
"hyprpaper": &HyprpaperBackend{},
}
const DefaultBackendName = "hyprpaper"
func ResolveBackend(name string) (WallpaperBackend, error) {
if name == "" {
name = DefaultBackendName
}
b, ok := backends[name]
if !ok {
return nil, fmt.Errorf("неизвестный бэкенд обоев: %s", name)
}
return b, nil
}
package wallpaper
import (
"ximperconf/config"
"github.com/urfave/cli/v3"
)
func CommandList() *cli.Command {
return &cli.Command{
Name: "wallpaper",
Usage: "Управление обоями",
Commands: []*cli.Command{
{
Name: "set",
Usage: "установить обои в конфиг",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "light",
Usage: "путь к обоям для светлой темы",
},
&cli.StringFlag{
Name: "dark",
Usage: "путь к обоям для тёмной темы",
},
&cli.StringFlag{
Name: "monitor",
Aliases: []string{"m"},
Usage: "имя монитора",
},
},
Action: WallpaperSetCommand,
},
{
Name: "get",
Usage: "получить путь к обоям",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "light",
Usage: "для светлой темы",
},
&cli.BoolFlag{
Name: "dark",
Usage: "для тёмной темы",
},
&cli.BoolFlag{
Name: "pair",
Usage: "вернуть пару [light, dark]",
},
&cli.StringFlag{
Name: "monitor",
Aliases: []string{"m"},
Usage: "имя монитора",
},
config.FormatFlag,
},
Action: WallpaperGetCommand,
},
{
Name: "apply",
Usage: "применить обои",
ArgsUsage: "[light|dark]",
Action: WallpaperApplyCommand,
},
{
Name: "info",
Usage: "информация о настроенных обоях",
Flags: []cli.Flag{
config.FormatFlag,
},
Action: WallpaperInfoCommand,
},
},
}
}
package wallpaper
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"ximperconf/utils"
"gopkg.in/yaml.v3"
)
type ThemeType string
const (
ThemeLight ThemeType = "light"
ThemeDark ThemeType = "dark"
)
type WallpaperEntry struct {
Light string `yaml:"light,omitempty" json:"light,omitempty"`
Dark string `yaml:"dark,omitempty" json:"dark,omitempty"`
}
type WallpaperConfig struct {
Backend string `yaml:"backend,omitempty" json:"backend,omitempty"`
CurrentTheme string `yaml:"current_theme,omitempty" json:"current_theme,omitempty"`
Monitors map[string]WallpaperEntry `yaml:"monitors,omitempty" json:"monitors,omitempty"`
Default WallpaperEntry `yaml:"default,omitempty" json:"default,omitempty"`
}
type WallpaperManager struct {
Config *WallpaperConfig
ConfigPath string
Changed bool
backend WallpaperBackend
}
func NewWallpaperManager() (*WallpaperManager, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}
configPath := filepath.Join(home, ".config", "ximper", "wallpaper.yaml")
manager := &WallpaperManager{
ConfigPath: configPath,
Config: &WallpaperConfig{
Monitors: make(map[string]WallpaperEntry),
},
}
if utils.FileExists(configPath) {
data, err := os.ReadFile(configPath)
if err != nil {
return nil, err
}
if err := yaml.Unmarshal(data, manager.Config); err != nil {
return nil, fmt.Errorf("ошибка парсинга %s: %w", configPath, err)
}
if manager.Config.Monitors == nil {
manager.Config.Monitors = make(map[string]WallpaperEntry)
}
}
backend, err := ResolveBackend(manager.Config.Backend)
if err != nil {
return nil, err
}
manager.backend = backend
return manager, nil
}
func (m *WallpaperManager) Save() error {
if !m.Changed {
return nil
}
dir := filepath.Dir(m.ConfigPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
data, err := yaml.Marshal(m.Config)
if err != nil {
return err
}
m.Changed = false
return os.WriteFile(m.ConfigPath, data, 0644)
}
func (m *WallpaperManager) SetWallpaper(lightPath, darkPath, monitor string) error {
if lightPath != "" {
absPath, err := filepath.Abs(lightPath)
if err != nil {
return err
}
if !utils.FileExists(absPath) {
return fmt.Errorf("файл не найден: %s", absPath)
}
lightPath = absPath
}
if darkPath != "" {
absPath, err := filepath.Abs(darkPath)
if err != nil {
return err
}
if !utils.FileExists(absPath) {
return fmt.Errorf("файл не найден: %s", absPath)
}
darkPath = absPath
}
if monitor == "" {
if lightPath != "" {
m.Config.Default.Light = lightPath
}
if darkPath != "" {
m.Config.Default.Dark = darkPath
}
} else {
entry := m.Config.Monitors[monitor]
if lightPath != "" {
entry.Light = lightPath
}
if darkPath != "" {
entry.Dark = darkPath
}
m.Config.Monitors[monitor] = entry
}
m.Changed = true
return nil
}
func (m *WallpaperManager) GetWallpaper(monitor string, theme ThemeType) string {
var entry WallpaperEntry
var found bool
if monitor != "" {
entry, found = m.Config.Monitors[monitor]
}
var path string
if found {
switch theme {
case ThemeLight:
path = entry.Light
case ThemeDark:
path = entry.Dark
}
}
if path == "" {
switch theme {
case ThemeLight:
path = m.Config.Default.Light
case ThemeDark:
path = m.Config.Default.Dark
}
}
return path
}
func (m *WallpaperManager) GetCurrentTheme() ThemeType {
return ThemeType(m.Config.CurrentTheme)
}
func (m *WallpaperManager) SetCurrentTheme(theme ThemeType) {
m.Config.CurrentTheme = string(theme)
m.Changed = true
}
func (m *WallpaperManager) Apply(monitor string, theme ThemeType) error {
path := m.GetWallpaper(monitor, theme)
if path == "" {
return fmt.Errorf("обои не настроены для монитора %s, тема %s", monitor, theme)
}
if !utils.FileExists(path) {
return fmt.Errorf("файл обоев не найден: %s", path)
}
if err := m.backend.SetWallpaper(monitor, path); err != nil {
return fmt.Errorf("ошибка установки обоев: %w", err)
}
return nil
}
func (m *WallpaperManager) ApplyAll(theme ThemeType) error {
monitors, err := GetHyprlandMonitors()
if err != nil {
return fmt.Errorf("ошибка получения списка мониторов: %w", err)
}
for _, mon := range monitors {
if err := m.Apply(mon, theme); err != nil {
return fmt.Errorf("монитор %s: %w", mon, err)
}
}
return nil
}
func GetHyprlandMonitors() ([]string, error) {
out, err := exec.Command("hyprctl", "monitors", "-j").Output()
if err != nil {
return nil, err
}
var monitors []struct {
Name string `json:"name"`
}
if err := json.Unmarshal(out, &monitors); err != nil {
return nil, err
}
var names []string
for _, mon := range monitors {
names = append(names, mon.Name)
}
return names, nil
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment