summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen
diff options
context:
space:
mode:
authorNippy <nippy@rp1.hu>2026-05-09 13:33:09 +0200
committerNippy <nippy@rp1.hu>2026-05-09 13:33:09 +0200
commita2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5 (patch)
tree1a8769217f84bfe9d6216fafaadaf8cdd876d457 /raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen
parent34b9c34f982b2596cfa9e368a2d7f74e9a17477c (diff)
downloadRaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.tar.gz
RaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.zip
raveos update
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen/matugen.go978
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen/matugen_test.go443
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen/queue.go143
3 files changed, 0 insertions, 1564 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen/matugen.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen/matugen.go
deleted file mode 100644
index a29d324..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen/matugen.go
+++ /dev/null
@@ -1,978 +0,0 @@
-package matugen
-
-import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "math"
- "os"
- "os/exec"
- "path/filepath"
- "strconv"
- "strings"
- "sync"
- "syscall"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/dank16"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
- "github.com/lucasb-eyer/go-colorful"
-)
-
-var ErrNoChanges = errors.New("no color changes")
-
-type ColorMode string
-
-const (
- ColorModeDark ColorMode = "dark"
- ColorModeLight ColorMode = "light"
-)
-
-type TemplateKind int
-
-const (
- TemplateKindNormal TemplateKind = iota
- TemplateKindTerminal
- TemplateKindGTK
- TemplateKindVSCode
- TemplateKindEmacs
-)
-
-type TemplateDef struct {
- ID string
- Commands []string
- Flatpaks []string
- ConfigFile string
- Kind TemplateKind
- RunUnconditionally bool
-}
-
-var templateRegistry = []TemplateDef{
- {ID: "gtk", Kind: TemplateKindGTK, RunUnconditionally: true},
- {ID: "niri", Commands: []string{"niri"}, ConfigFile: "niri.toml"},
- {ID: "hyprland", Commands: []string{"Hyprland"}, ConfigFile: "hyprland.toml"},
- {ID: "mangowc", Commands: []string{"mango"}, ConfigFile: "mangowc.toml"},
- {ID: "qt5ct", Commands: []string{"qt5ct"}, ConfigFile: "qt5ct.toml"},
- {ID: "qt6ct", Commands: []string{"qt6ct"}, ConfigFile: "qt6ct.toml"},
- {ID: "firefox", Commands: []string{"firefox"}, ConfigFile: "firefox.toml"},
- {ID: "pywalfox", Commands: []string{"pywalfox"}, ConfigFile: "pywalfox.toml"},
- {ID: "zenbrowser", Commands: []string{"zen", "zen-browser", "zen-beta", "zen-twilight"}, Flatpaks: []string{"app.zen_browser.zen"}, ConfigFile: "zenbrowser.toml"},
- {ID: "vesktop", Commands: []string{"vesktop"}, Flatpaks: []string{"dev.vencord.Vesktop"}, ConfigFile: "vesktop.toml"},
- {ID: "equibop", Commands: []string{"equibop"}, ConfigFile: "equibop.toml"},
- {ID: "ghostty", Commands: []string{"ghostty"}, ConfigFile: "ghostty.toml", Kind: TemplateKindTerminal},
- {ID: "kitty", Commands: []string{"kitty"}, ConfigFile: "kitty.toml", Kind: TemplateKindTerminal},
- {ID: "foot", Commands: []string{"foot"}, ConfigFile: "foot.toml", Kind: TemplateKindTerminal},
- {ID: "alacritty", Commands: []string{"alacritty"}, ConfigFile: "alacritty.toml", Kind: TemplateKindTerminal},
- {ID: "wezterm", Commands: []string{"wezterm"}, ConfigFile: "wezterm.toml", Kind: TemplateKindTerminal},
- {ID: "nvim", Commands: []string{"nvim"}, ConfigFile: "neovim.toml", Kind: TemplateKindTerminal},
- {ID: "dgop", Commands: []string{"dgop"}, ConfigFile: "dgop.toml"},
- {ID: "kcolorscheme", ConfigFile: "kcolorscheme.toml", RunUnconditionally: true},
- {ID: "vscode", Kind: TemplateKindVSCode},
- {ID: "emacs", Commands: []string{"emacs"}, ConfigFile: "emacs.toml", Kind: TemplateKindEmacs},
- {ID: "zed", Commands: []string{"zed", "zeditor", "zedit"}, ConfigFile: "zed.toml"},
-}
-
-func (c *ColorMode) GTKTheme() string {
- switch *c {
- case ColorModeDark:
- return "adw-gtk3-dark"
- default:
- return "adw-gtk3"
- }
-}
-
-var (
- matugenVersionMu sync.Mutex
- matugenVersionOK bool
- matugenSupportsCOE bool
- matugenIsV4 bool
-)
-
-type Options struct {
- StateDir string
- ShellDir string
- ConfigDir string
- Kind string
- Value string
- Mode ColorMode
- IconTheme string
- MatugenType string
- Contrast float64
- RunUserTemplates bool
- ColorsOnly bool
- StockColors string
- SyncModeWithPortal bool
- TerminalsAlwaysDark bool
- SkipTemplates string
- AppChecker utils.AppChecker
-}
-
-type ColorsOutput struct {
- Colors struct {
- Dark map[string]string `json:"dark"`
- Light map[string]string `json:"light"`
- } `json:"colors"`
-}
-
-func (o *Options) ColorsOutput() string {
- return filepath.Join(o.StateDir, "dms-colors.json")
-}
-
-func (o *Options) ShouldSkipTemplate(name string) bool {
- if o.SkipTemplates == "" {
- return false
- }
- for _, skip := range strings.Split(o.SkipTemplates, ",") {
- if strings.TrimSpace(skip) == name {
- return true
- }
- }
- return false
-}
-
-func Run(opts Options) error {
- if opts.StateDir == "" {
- return fmt.Errorf("state-dir is required")
- }
- if opts.ShellDir == "" {
- return fmt.Errorf("shell-dir is required")
- }
- if opts.ConfigDir == "" {
- return fmt.Errorf("config-dir is required")
- }
- if opts.Kind == "" {
- return fmt.Errorf("kind is required")
- }
- if opts.Value == "" {
- return fmt.Errorf("value is required")
- }
- if opts.Mode == "" {
- opts.Mode = ColorModeDark
- }
- if opts.MatugenType == "" {
- opts.MatugenType = "scheme-tonal-spot"
- }
- if opts.IconTheme == "" {
- opts.IconTheme = "System Default"
- }
- if opts.AppChecker == nil {
- opts.AppChecker = utils.DefaultAppChecker{}
- }
-
- if err := os.MkdirAll(opts.StateDir, 0o755); err != nil {
- return fmt.Errorf("failed to create state dir: %w", err)
- }
-
- log.Infof("Building theme: %s %s (%s)", opts.Kind, opts.Value, opts.Mode)
-
- changed, buildErr := buildOnce(&opts)
- if buildErr != nil {
- return buildErr
- }
-
- if !changed {
- log.Info("No color changes detected, skipping refresh")
- return ErrNoChanges
- }
-
- if opts.SyncModeWithPortal {
- syncColorScheme(opts.Mode)
- }
-
- log.Info("Done")
- return nil
-}
-
-func buildOnce(opts *Options) (bool, error) {
- cfgFile, err := os.CreateTemp("", "matugen-config-*.toml")
- if err != nil {
- return false, fmt.Errorf("failed to create temp config: %w", err)
- }
- defer os.Remove(cfgFile.Name())
- defer cfgFile.Close()
-
- tmpDir, err := os.MkdirTemp("", "matugen-templates-*")
- if err != nil {
- return false, fmt.Errorf("failed to create temp dir: %w", err)
- }
- defer os.RemoveAll(tmpDir)
-
- if err := buildMergedConfig(opts, cfgFile, tmpDir); err != nil {
- return false, fmt.Errorf("failed to build config: %w", err)
- }
- cfgFile.Close()
-
- oldColors, _ := os.ReadFile(opts.ColorsOutput())
-
- var primaryDark, primaryLight, surface string
- var dank16JSON string
- var importArgs []string
-
- if opts.StockColors != "" {
- log.Info("Using stock/custom theme colors with matugen base")
- primaryDark = extractNestedColor(opts.StockColors, "primary", "dark")
- primaryLight = extractNestedColor(opts.StockColors, "primary", "light")
- surface = extractNestedColor(opts.StockColors, "surface", "dark")
-
- if primaryDark == "" {
- return false, fmt.Errorf("failed to extract primary dark from stock colors")
- }
- if primaryLight == "" {
- primaryLight = primaryDark
- }
-
- dank16JSON = generateDank16Variants(primaryDark, primaryLight, surface, opts.Mode)
- importData := fmt.Sprintf(`{"colors": %s, "dank16": %s}`, opts.StockColors, dank16JSON)
- importArgs = []string{"--import-json-string", importData}
-
- log.Info("Running matugen color hex with stock color overrides")
- args := []string{"color", "hex", primaryDark, "-m", string(opts.Mode), "-t", opts.MatugenType, "-c", cfgFile.Name()}
- args = appendContrastArg(args, opts.Contrast)
- args = append(args, importArgs...)
- if err := runMatugen(args); err != nil {
- return false, err
- }
- } else {
- log.Infof("Using dynamic theme from %s: %s", opts.Kind, opts.Value)
-
- matJSON, err := runMatugenDryRun(opts)
- if err != nil {
- return false, fmt.Errorf("matugen dry-run failed: %w", err)
- }
-
- primaryDark = extractMatugenColor(matJSON, "primary", "dark")
- primaryLight = extractMatugenColor(matJSON, "primary", "light")
- surface = extractMatugenColor(matJSON, "surface", "dark")
-
- if primaryDark == "" {
- return false, fmt.Errorf("failed to extract primary color")
- }
- if primaryLight == "" {
- primaryLight = primaryDark
- }
-
- dank16JSON = generateDank16Variants(primaryDark, primaryLight, surface, opts.Mode)
- importData := fmt.Sprintf(`{"dank16": %s}`, dank16JSON)
- importArgs = []string{"--import-json-string", importData}
-
- log.Infof("Running matugen %s with dank16 injection", opts.Kind)
- var args []string
- switch opts.Kind {
- case "hex":
- args = []string{"color", "hex", opts.Value}
- default:
- args = []string{opts.Kind, opts.Value}
- }
- args = append(args, "-m", string(opts.Mode), "-t", opts.MatugenType, "-c", cfgFile.Name())
- args = appendContrastArg(args, opts.Contrast)
- args = append(args, importArgs...)
- if err := runMatugen(args); err != nil {
- return false, err
- }
- }
-
- newColors, _ := os.ReadFile(opts.ColorsOutput())
- if bytes.Equal(oldColors, newColors) && len(oldColors) > 0 {
- return false, nil
- }
-
- if opts.ColorsOnly {
- return true, nil
- }
-
- if isDMSGTKActive(opts.ConfigDir) {
- switch opts.Mode {
- case ColorModeLight:
- syncAccentColor(primaryLight)
- default:
- syncAccentColor(primaryDark)
- }
- refreshGTK(opts.Mode)
- refreshGTK4()
- }
-
- if !opts.ShouldSkipTemplate("qt6ct") && appExists(opts.AppChecker, []string{"qt6ct"}, nil) {
- refreshQt6ct()
- }
-
- signalTerminals(opts)
-
- return true, nil
-}
-
-func appendContrastArg(args []string, contrast float64) []string {
- if contrast == 0 {
- return args
- }
- return append(args, "--contrast", strconv.FormatFloat(contrast, 'f', -1, 64))
-}
-
-func buildMergedConfig(opts *Options, cfgFile *os.File, tmpDir string) error {
- userConfigPath := filepath.Join(opts.ConfigDir, "matugen", "config.toml")
-
- wroteConfig := false
- if opts.RunUserTemplates {
- if data, err := os.ReadFile(userConfigPath); err == nil {
- configSection := extractTOMLSection(string(data), "[config]", "[templates]")
- if configSection != "" {
- cfgFile.WriteString(configSection)
- cfgFile.WriteString("\n")
- wroteConfig = true
- }
- }
- }
- if !wroteConfig {
- cfgFile.WriteString("[config]\n\n")
- }
-
- baseConfigPath := filepath.Join(opts.ShellDir, "matugen", "configs", "base.toml")
- if data, err := os.ReadFile(baseConfigPath); err == nil {
- content := string(data)
- lines := strings.Split(content, "\n")
- for _, line := range lines {
- if strings.TrimSpace(line) == "[config]" {
- continue
- }
- cfgFile.WriteString(substituteVars(line, opts.ShellDir) + "\n")
- }
- cfgFile.WriteString("\n")
- }
-
- fmt.Fprintf(cfgFile, `[templates.dank]
-input_path = '%s/matugen/templates/dank.json'
-output_path = '%s'
-
-`, opts.ShellDir, opts.ColorsOutput())
-
- if opts.ColorsOnly {
- return nil
- }
-
- homeDir, _ := os.UserHomeDir()
- for _, tmpl := range templateRegistry {
- if opts.ShouldSkipTemplate(tmpl.ID) {
- continue
- }
-
- switch tmpl.Kind {
- case TemplateKindGTK:
- switch opts.Mode {
- case ColorModeLight:
- appendConfig(opts, cfgFile, nil, nil, "gtk3-light.toml")
- default:
- appendConfig(opts, cfgFile, nil, nil, "gtk3-dark.toml")
- }
- case TemplateKindTerminal:
- appendTerminalConfig(opts, cfgFile, tmpDir, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile)
- case TemplateKindVSCode:
- appendVSCodeConfig(cfgFile, "vscode", filepath.Join(homeDir, ".vscode/extensions"), opts.ShellDir)
- appendVSCodeConfig(cfgFile, "codium", filepath.Join(homeDir, ".vscode-oss/extensions"), opts.ShellDir)
- appendVSCodeConfig(cfgFile, "codeoss", filepath.Join(homeDir, ".config/Code - OSS/extensions"), opts.ShellDir)
- appendVSCodeConfig(cfgFile, "cursor", filepath.Join(homeDir, ".cursor/extensions"), opts.ShellDir)
- appendVSCodeConfig(cfgFile, "windsurf", filepath.Join(homeDir, ".windsurf/extensions"), opts.ShellDir)
- appendVSCodeConfig(cfgFile, "vscode-insiders", filepath.Join(homeDir, ".vscode-insiders/extensions"), opts.ShellDir)
- case TemplateKindEmacs:
- if utils.EmacsConfigDir() != "" {
- appendConfig(opts, cfgFile, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile)
- }
- default:
- appendConfig(opts, cfgFile, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile)
- }
- }
-
- if opts.RunUserTemplates {
- if data, err := os.ReadFile(userConfigPath); err == nil {
- templatesSection := extractTOMLSection(string(data), "[templates]", "")
- if templatesSection != "" {
- cfgFile.WriteString(templatesSection)
- cfgFile.WriteString("\n")
- }
- }
- }
-
- userPluginConfigDir := filepath.Join(opts.ConfigDir, "matugen", "dms", "configs")
- if entries, err := os.ReadDir(userPluginConfigDir); err == nil {
- for _, entry := range entries {
- if !strings.HasSuffix(entry.Name(), ".toml") {
- continue
- }
- if data, err := os.ReadFile(filepath.Join(userPluginConfigDir, entry.Name())); err == nil {
- cfgFile.WriteString(string(data))
- cfgFile.WriteString("\n")
- }
- }
- }
-
- return nil
-}
-
-func appendConfig(
- opts *Options,
- cfgFile *os.File,
- checkCmd []string,
- checkFlatpaks []string,
- fileName string,
-) {
- configPath := filepath.Join(opts.ShellDir, "matugen", "configs", fileName)
- if _, err := os.Stat(configPath); err != nil {
- return
- }
- if !appExists(opts.AppChecker, checkCmd, checkFlatpaks) {
- return
- }
- data, err := os.ReadFile(configPath)
- if err != nil {
- return
- }
- cfgFile.WriteString(substituteVars(string(data), opts.ShellDir))
- cfgFile.WriteString("\n")
-}
-
-func appendTerminalConfig(opts *Options, cfgFile *os.File, tmpDir string, checkCmd []string, checkFlatpaks []string, fileName string) {
- configPath := filepath.Join(opts.ShellDir, "matugen", "configs", fileName)
- if _, err := os.Stat(configPath); err != nil {
- return
- }
- if !appExists(opts.AppChecker, checkCmd, checkFlatpaks) {
- return
- }
- data, err := os.ReadFile(configPath)
- if err != nil {
- return
- }
-
- content := string(data)
-
- if !opts.TerminalsAlwaysDark {
- cfgFile.WriteString(substituteVars(content, opts.ShellDir))
- cfgFile.WriteString("\n")
- return
- }
-
- lines := strings.Split(content, "\n")
- for _, line := range lines {
- if !strings.Contains(line, "input_path") || !strings.Contains(line, "SHELL_DIR/matugen/templates/") {
- continue
- }
-
- start := strings.Index(line, "'SHELL_DIR/matugen/templates/")
- if start == -1 {
- continue
- }
- end := strings.Index(line[start+1:], "'")
- if end == -1 {
- continue
- }
- templateName := line[start+len("'SHELL_DIR/matugen/templates/") : start+1+end]
- origPath := filepath.Join(opts.ShellDir, "matugen", "templates", templateName)
-
- origData, err := os.ReadFile(origPath)
- if err != nil {
- continue
- }
-
- modified := strings.ReplaceAll(string(origData), ".default.", ".dark.")
- tmpPath := filepath.Join(tmpDir, templateName)
- if err := os.WriteFile(tmpPath, []byte(modified), 0o644); err != nil {
- continue
- }
-
- content = strings.ReplaceAll(content,
- fmt.Sprintf("'SHELL_DIR/matugen/templates/%s'", templateName),
- fmt.Sprintf("'%s'", tmpPath))
- }
-
- cfgFile.WriteString(substituteVars(content, opts.ShellDir))
- cfgFile.WriteString("\n")
-}
-
-func appExists(checker utils.AppChecker, checkCmd []string, checkFlatpaks []string) bool {
- // Both nil is treated as "skip check" / unconditionally run
- if checkCmd == nil && checkFlatpaks == nil {
- return true
- }
- if checkCmd != nil && checker.AnyCommandExists(checkCmd...) {
- return true
- }
- if checkFlatpaks != nil && checker.AnyFlatpakExists(checkFlatpaks...) {
- return true
- }
- return false
-}
-
-func appendVSCodeConfig(cfgFile *os.File, name, extBaseDir, shellDir string) {
- pattern := filepath.Join(extBaseDir, "danklinux.dms-theme-*")
- matches, err := filepath.Glob(pattern)
- if err != nil || len(matches) == 0 {
- return
- }
-
- extDir := matches[0]
- templateDir := filepath.Join(shellDir, "matugen", "templates")
- fmt.Fprintf(cfgFile, `[templates.dms%sdefault]
-input_path = '%s/vscode-color-theme-default.json'
-output_path = '%s/themes/dankshell-default.json'
-
-[templates.dms%sdark]
-input_path = '%s/vscode-color-theme-dark.json'
-output_path = '%s/themes/dankshell-dark.json'
-
-[templates.dms%slight]
-input_path = '%s/vscode-color-theme-light.json'
-output_path = '%s/themes/dankshell-light.json'
-
-`, name, templateDir, extDir,
- name, templateDir, extDir,
- name, templateDir, extDir)
- log.Infof("Added %s theme config (extension found at %s)", name, extDir)
-}
-
-func substituteVars(content, shellDir string) string {
- result := strings.ReplaceAll(content, "'SHELL_DIR/", "'"+shellDir+"/")
- result = strings.ReplaceAll(result, "'CONFIG_DIR/", "'"+utils.XDGConfigHome()+"/")
- result = strings.ReplaceAll(result, "'DATA_DIR/", "'"+utils.XDGDataHome()+"/")
- result = strings.ReplaceAll(result, "'CACHE_DIR/", "'"+utils.XDGCacheHome()+"/")
- if emacsDir := utils.EmacsConfigDir(); emacsDir != "" {
- result = strings.ReplaceAll(result, "'EMACS_DIR/", "'"+emacsDir+"/")
- }
- return result
-}
-
-func extractTOMLSection(content, startMarker, endMarker string) string {
- startIdx := strings.Index(content, startMarker)
- if startIdx == -1 {
- return ""
- }
-
- if endMarker == "" {
- return content[startIdx:]
- }
-
- endIdx := strings.Index(content[startIdx:], endMarker)
- if endIdx == -1 {
- return content[startIdx:]
- }
- return content[startIdx : startIdx+endIdx]
-}
-
-type matugenFlags struct {
- supportsCOE bool
- isV4 bool
-}
-
-func detectMatugenVersion() (matugenFlags, error) {
- matugenVersionMu.Lock()
- defer matugenVersionMu.Unlock()
-
- if matugenVersionOK {
- return matugenFlags{matugenSupportsCOE, matugenIsV4}, nil
- }
-
- return detectMatugenVersionLocked()
-}
-
-func redetectMatugenVersion(old matugenFlags) (matugenFlags, bool) {
- matugenVersionMu.Lock()
- defer matugenVersionMu.Unlock()
-
- matugenVersionOK = false
- flags, err := detectMatugenVersionLocked()
- if err != nil {
- return old, false
- }
- changed := flags.supportsCOE != old.supportsCOE || flags.isV4 != old.isV4
- return flags, changed
-}
-
-func detectMatugenVersionLocked() (matugenFlags, error) {
- cmd := exec.Command("matugen", "--version")
- output, err := cmd.Output()
- if err != nil {
- return matugenFlags{}, fmt.Errorf("failed to get matugen version: %w", err)
- }
-
- versionStr := strings.TrimSpace(string(output))
- versionStr = strings.TrimPrefix(versionStr, "matugen ")
-
- parts := strings.Split(versionStr, ".")
- if len(parts) < 2 {
- return matugenFlags{}, fmt.Errorf("unexpected matugen version format: %q", versionStr)
- }
-
- major, err := strconv.Atoi(parts[0])
- if err != nil {
- return matugenFlags{}, fmt.Errorf("failed to parse matugen major version %q: %w", parts[0], err)
- }
-
- minor, err := strconv.Atoi(parts[1])
- if err != nil {
- return matugenFlags{}, fmt.Errorf("failed to parse matugen minor version %q: %w", parts[1], err)
- }
-
- matugenSupportsCOE = major > 3 || (major == 3 && minor >= 1)
- matugenIsV4 = major >= 4
- matugenVersionOK = true
-
- if matugenSupportsCOE {
- log.Debugf("Matugen %s detected: continue-on-error support enabled", versionStr)
- }
- if matugenIsV4 {
- log.Debugf("Matugen %s detected: using v4 compatibility flags", versionStr)
- }
- return matugenFlags{matugenSupportsCOE, matugenIsV4}, nil
-}
-
-func buildMatugenArgs(baseArgs []string, flags matugenFlags) []string {
- args := make([]string, 0, len(baseArgs)+4)
- if flags.supportsCOE {
- args = append(args, "--continue-on-error")
- }
- args = append(args, baseArgs...)
- if flags.isV4 {
- args = append(args, "--source-color-index", "0")
- }
- return args
-}
-
-func runMatugen(baseArgs []string) error {
- flags, err := detectMatugenVersion()
- if err != nil {
- return err
- }
-
- args := buildMatugenArgs(baseArgs, flags)
- cmd := exec.Command("matugen", args...)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- runErr := cmd.Run()
- if runErr == nil {
- return nil
- }
-
- log.Warnf("Matugen failed (v4=%v): %v", flags.isV4, runErr)
-
- newFlags, changed := redetectMatugenVersion(flags)
- if !changed {
- return runErr
- }
-
- log.Warnf("Matugen version changed (v4: %v -> %v), retrying", flags.isV4, newFlags.isV4)
- args = buildMatugenArgs(baseArgs, newFlags)
- retryCmd := exec.Command("matugen", args...)
- retryCmd.Stdout = os.Stdout
- retryCmd.Stderr = os.Stderr
- return retryCmd.Run()
-}
-
-func runMatugenDryRun(opts *Options) (string, error) {
- flags, err := detectMatugenVersion()
- if err != nil {
- return "", err
- }
-
- output, dryErr := execDryRun(opts, flags)
- if dryErr == nil {
- return output, nil
- }
-
- log.Warnf("Matugen dry-run failed (v4=%v): %v", flags.isV4, dryErr)
-
- newFlags, changed := redetectMatugenVersion(flags)
- if !changed {
- return "", dryErr
- }
-
- log.Warnf("Matugen version changed (v4: %v -> %v), retrying dry-run", flags.isV4, newFlags.isV4)
- return execDryRun(opts, newFlags)
-}
-
-func execDryRun(opts *Options, flags matugenFlags) (string, error) {
- var baseArgs []string
- switch opts.Kind {
- case "hex":
- baseArgs = []string{"color", "hex", opts.Value}
- default:
- baseArgs = []string{opts.Kind, opts.Value}
- }
- baseArgs = append(baseArgs, "-m", "dark", "-t", opts.MatugenType, "--json", "hex", "--dry-run")
- baseArgs = appendContrastArg(baseArgs, opts.Contrast)
- if flags.isV4 {
- baseArgs = append(baseArgs, "--source-color-index", "0", "--old-json-output")
- }
-
- cmd := exec.Command("matugen", baseArgs...)
- var stderr strings.Builder
- cmd.Stderr = &stderr
- output, err := cmd.Output()
- if err != nil {
- if stderr.Len() > 0 {
- return "", fmt.Errorf("matugen %v failed (v4=%v): %s", baseArgs, flags.isV4, strings.TrimSpace(stderr.String()))
- }
- return "", fmt.Errorf("matugen %v failed (v4=%v): %w", baseArgs, flags.isV4, err)
- }
- return strings.ReplaceAll(string(output), "\n", ""), nil
-}
-
-func extractMatugenColor(jsonStr, colorName, variant string) string {
- var data map[string]any
- if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
- return ""
- }
-
- colors, ok := data["colors"].(map[string]any)
- if !ok {
- return ""
- }
-
- colorData, ok := colors[colorName].(map[string]any)
- if !ok {
- return ""
- }
-
- variantData, ok := colorData[variant].(string)
- if !ok {
- return ""
- }
-
- return variantData
-}
-
-func extractNestedColor(jsonStr, colorName, variant string) string {
- var data map[string]any
- if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
- return ""
- }
-
- colorData, ok := data[colorName].(map[string]any)
- if !ok {
- return ""
- }
-
- variantData, ok := colorData[variant].(map[string]any)
- if !ok {
- return ""
- }
-
- color, ok := variantData["color"].(string)
- if !ok {
- return ""
- }
-
- return color
-}
-
-func generateDank16Variants(primaryDark, primaryLight, surface string, mode ColorMode) string {
- variantOpts := dank16.VariantOptions{
- PrimaryDark: primaryDark,
- PrimaryLight: primaryLight,
- Background: surface,
- UseDPS: true,
- IsLightMode: mode == ColorModeLight,
- }
- variantColors := dank16.GenerateVariantPalette(variantOpts)
- return dank16.GenerateVariantJSON(variantColors)
-}
-
-func isDMSGTKActive(configDir string) bool {
- gtkCSS := filepath.Join(configDir, "gtk-3.0", "gtk.css")
-
- info, err := os.Lstat(gtkCSS)
- if err != nil {
- return false
- }
-
- if info.Mode()&os.ModeSymlink != 0 {
- target, err := os.Readlink(gtkCSS)
- return err == nil && strings.Contains(target, "dank-colors.css")
- }
-
- data, err := os.ReadFile(gtkCSS)
- return err == nil && strings.Contains(string(data), "dank-colors.css")
-}
-
-func refreshGTK(mode ColorMode) {
- if err := utils.GsettingsSet("org.gnome.desktop.interface", "gtk-theme", ""); err != nil {
- log.Warnf("Failed to reset gtk-theme: %v", err)
- }
- if err := utils.GsettingsSet("org.gnome.desktop.interface", "gtk-theme", mode.GTKTheme()); err != nil {
- log.Warnf("Failed to set gtk-theme: %v", err)
- }
-}
-
-func refreshGTK4() {
- output, err := utils.GsettingsGet("org.gnome.desktop.interface", "color-scheme")
- if err != nil {
- return
- }
- current := strings.Trim(output, "'")
-
- var toggle string
- if current == "prefer-dark" {
- toggle = "default"
- } else {
- toggle = "prefer-dark"
- }
-
- if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", toggle); err != nil {
- log.Warnf("Failed to toggle color-scheme for GTK4 refresh: %v", err)
- return
- }
- time.Sleep(50 * time.Millisecond)
- if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", current); err != nil {
- log.Warnf("Failed to restore color-scheme for GTK4 refresh: %v", err)
- }
-}
-
-func refreshQt6ct() {
- confPath := filepath.Join(utils.XDGConfigHome(), "qt6ct", "qt6ct.conf")
- now := time.Now()
- if err := os.Chtimes(confPath, now, now); err != nil {
- log.Warnf("Failed to touch qt6ct.conf: %v", err)
- }
-}
-
-func signalTerminals(opts *Options) {
- if !opts.ShouldSkipTemplate("kitty") && appExists(opts.AppChecker, []string{"kitty"}, nil) {
- signalByName("kitty", syscall.SIGUSR1)
- signalByName(".kitty-wrapped", syscall.SIGUSR1)
- }
- if !opts.ShouldSkipTemplate("ghostty") && appExists(opts.AppChecker, []string{"ghostty"}, nil) {
- signalByName("ghostty", syscall.SIGUSR2)
- signalByName(".ghostty-wrappe", syscall.SIGUSR2)
- }
-}
-
-func signalByName(name string, sig syscall.Signal) {
- entries, err := os.ReadDir("/proc")
- if err != nil {
- return
- }
- for _, entry := range entries {
- pid, err := strconv.Atoi(entry.Name())
- if err != nil {
- continue
- }
- comm, err := os.ReadFile(filepath.Join("/proc", entry.Name(), "comm"))
- if err != nil {
- continue
- }
- if strings.TrimSpace(string(comm)) == name {
- syscall.Kill(pid, sig)
- }
- }
-}
-
-func syncColorScheme(mode ColorMode) {
- scheme := "prefer-dark"
- if mode == ColorModeLight {
- scheme = "default"
- }
-
- if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", scheme); err != nil {
- log.Warnf("Failed to sync color-scheme: %v", err)
- }
-}
-
-var adwaitaAccents = []struct {
- name string
- colors []colorful.Color
-}{
- {"blue", hexColors("#3f8ae5", "#438de6", "#a4caee")},
- {"green", hexColors("#26a269", "#39ac76", "#81d5ad")},
- {"orange", hexColors("#f17738", "#ff7800", "#ffc994")},
- {"pink", hexColors("#e4358a", "#e64392", "#f9b3d5")},
- {"purple", hexColors("#954ab5", "#9c46b9", "#d099d6")},
- {"red", hexColors("#e84053", "#e01b24", "#f2a1a5")},
- {"slate", hexColors("#557b9f", "#6a8daf", "#b4c6d6")},
- {"teal", hexColors("#129eb0", "#2190a4", "#7bdff4")},
- {"yellow", hexColors("#cbac10", "#d4b411", "#f5c211")},
-}
-
-func hexColors(hexes ...string) []colorful.Color {
- out := make([]colorful.Color, len(hexes))
- for i, h := range hexes {
- out[i], _ = colorful.Hex(h)
- }
- return out
-}
-
-func closestAdwaitaAccent(primaryHex string) string {
- c, err := colorful.Hex(primaryHex)
- if err != nil {
- return "blue"
- }
-
- best := "blue"
- bestDist := math.MaxFloat64
- for _, a := range adwaitaAccents {
- for _, ref := range a.colors {
- d := c.DistanceCIEDE2000(ref)
- if d < bestDist {
- bestDist = d
- best = a.name
- }
- }
- }
- return best
-}
-
-func syncAccentColor(primaryHex string) {
- accent := closestAdwaitaAccent(primaryHex)
- log.Infof("Setting GNOME accent color: %s", accent)
- if err := utils.GsettingsSet("org.gnome.desktop.interface", "accent-color", accent); err != nil {
- log.Warnf("Failed to set accent-color: %v", err)
- }
-}
-
-type TemplateCheck struct {
- ID string `json:"id"`
- Detected bool `json:"detected"`
-}
-
-func CheckTemplates(checker utils.AppChecker) []TemplateCheck {
- if checker == nil {
- checker = utils.DefaultAppChecker{}
- }
-
- homeDir, _ := os.UserHomeDir()
- checks := make([]TemplateCheck, 0, len(templateRegistry))
-
- for _, tmpl := range templateRegistry {
- detected := false
-
- switch {
- case tmpl.RunUnconditionally:
- detected = true
- case tmpl.Kind == TemplateKindVSCode:
- detected = checkVSCodeExtension(homeDir)
- case tmpl.Kind == TemplateKindEmacs:
- detected = appExists(checker, tmpl.Commands, tmpl.Flatpaks) && utils.EmacsConfigDir() != ""
- default:
- detected = appExists(checker, tmpl.Commands, tmpl.Flatpaks)
- }
-
- checks = append(checks, TemplateCheck{ID: tmpl.ID, Detected: detected})
- }
-
- return checks
-}
-
-func checkVSCodeExtension(homeDir string) bool {
- extDirs := []string{
- filepath.Join(homeDir, ".vscode/extensions"),
- filepath.Join(homeDir, ".vscode-oss/extensions"),
- filepath.Join(homeDir, ".config/Code - OSS/extensions"),
- filepath.Join(homeDir, ".cursor/extensions"),
- filepath.Join(homeDir, ".windsurf/extensions"),
- }
-
- for _, extDir := range extDirs {
- pattern := filepath.Join(extDir, "danklinux.dms-theme-*")
- if matches, err := filepath.Glob(pattern); err == nil && len(matches) > 0 {
- return true
- }
- }
- return false
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen/matugen_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen/matugen_test.go
deleted file mode 100644
index 48880df..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen/matugen_test.go
+++ /dev/null
@@ -1,443 +0,0 @@
-package matugen
-
-import (
- "os"
- "path/filepath"
- "strings"
- "testing"
-
- mocks_utils "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/utils"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
- "github.com/stretchr/testify/assert"
-)
-
-func TestAppendConfigBinaryExists(t *testing.T) {
- tempDir := t.TempDir()
-
- shellDir := filepath.Join(tempDir, "shell")
- configsDir := filepath.Join(shellDir, "matugen", "configs")
- if err := os.MkdirAll(configsDir, 0o755); err != nil {
- t.Fatalf("failed to create configs dir: %v", err)
- }
-
- testConfig := "test config content"
- configPath := filepath.Join(configsDir, "test.toml")
- if err := os.WriteFile(configPath, []byte(testConfig), 0o644); err != nil {
- t.Fatalf("failed to write config: %v", err)
- }
-
- outFile := filepath.Join(tempDir, "output.toml")
- cfgFile, err := os.Create(outFile)
- if err != nil {
- t.Fatalf("failed to create output file: %v", err)
- }
- defer cfgFile.Close()
-
- mockChecker := mocks_utils.NewMockAppChecker(t)
- mockChecker.EXPECT().AnyCommandExists("sh").Return(true)
-
- opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
-
- appendConfig(opts, cfgFile, []string{"sh"}, nil, "test.toml")
-
- cfgFile.Close()
- output, err := os.ReadFile(outFile)
- if err != nil {
- t.Fatalf("failed to read output: %v", err)
- }
-
- if len(output) == 0 {
- t.Errorf("expected config to be written when binary exists")
- }
- if string(output) != testConfig+"\n" {
- t.Errorf("expected %q, got %q", testConfig+"\n", string(output))
- }
-}
-
-func TestAppendConfigBinaryDoesNotExist(t *testing.T) {
- tempDir := t.TempDir()
-
- shellDir := filepath.Join(tempDir, "shell")
- configsDir := filepath.Join(shellDir, "matugen", "configs")
- if err := os.MkdirAll(configsDir, 0o755); err != nil {
- t.Fatalf("failed to create configs dir: %v", err)
- }
-
- testConfig := "test config content"
- configPath := filepath.Join(configsDir, "test.toml")
- if err := os.WriteFile(configPath, []byte(testConfig), 0o644); err != nil {
- t.Fatalf("failed to write config: %v", err)
- }
-
- outFile := filepath.Join(tempDir, "output.toml")
- cfgFile, err := os.Create(outFile)
- if err != nil {
- t.Fatalf("failed to create output file: %v", err)
- }
- defer cfgFile.Close()
-
- mockChecker := mocks_utils.NewMockAppChecker(t)
- mockChecker.EXPECT().AnyCommandExists("nonexistent-binary-12345").Return(false)
- mockChecker.EXPECT().AnyFlatpakExists().Return(false)
-
- opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
-
- appendConfig(opts, cfgFile, []string{"nonexistent-binary-12345"}, []string{}, "test.toml")
-
- cfgFile.Close()
- output, err := os.ReadFile(outFile)
- if err != nil {
- t.Fatalf("failed to read output: %v", err)
- }
-
- if len(output) != 0 {
- t.Errorf("expected no config when binary doesn't exist, got: %q", string(output))
- }
-}
-
-func TestAppendConfigFlatpakExists(t *testing.T) {
- tempDir := t.TempDir()
-
- shellDir := filepath.Join(tempDir, "shell")
- configsDir := filepath.Join(shellDir, "matugen", "configs")
- if err := os.MkdirAll(configsDir, 0o755); err != nil {
- t.Fatalf("failed to create configs dir: %v", err)
- }
-
- testConfig := "zen config content"
- configPath := filepath.Join(configsDir, "test.toml")
- if err := os.WriteFile(configPath, []byte(testConfig), 0o644); err != nil {
- t.Fatalf("failed to write config: %v", err)
- }
-
- outFile := filepath.Join(tempDir, "output.toml")
- cfgFile, err := os.Create(outFile)
- if err != nil {
- t.Fatalf("failed to create output file: %v", err)
- }
- defer cfgFile.Close()
-
- mockChecker := mocks_utils.NewMockAppChecker(t)
- mockChecker.EXPECT().AnyFlatpakExists("app.zen_browser.zen").Return(true)
-
- opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
-
- appendConfig(opts, cfgFile, nil, []string{"app.zen_browser.zen"}, "test.toml")
-
- cfgFile.Close()
- output, err := os.ReadFile(outFile)
- if err != nil {
- t.Fatalf("failed to read output: %v", err)
- }
-
- if len(output) == 0 {
- t.Errorf("expected config to be written when flatpak exists")
- }
-}
-
-func TestAppendConfigFlatpakDoesNotExist(t *testing.T) {
- tempDir := t.TempDir()
-
- shellDir := filepath.Join(tempDir, "shell")
- configsDir := filepath.Join(shellDir, "matugen", "configs")
- if err := os.MkdirAll(configsDir, 0o755); err != nil {
- t.Fatalf("failed to create configs dir: %v", err)
- }
-
- testConfig := "test config content"
- configPath := filepath.Join(configsDir, "test.toml")
- if err := os.WriteFile(configPath, []byte(testConfig), 0o644); err != nil {
- t.Fatalf("failed to write config: %v", err)
- }
-
- outFile := filepath.Join(tempDir, "output.toml")
- cfgFile, err := os.Create(outFile)
- if err != nil {
- t.Fatalf("failed to create output file: %v", err)
- }
- defer cfgFile.Close()
-
- mockChecker := mocks_utils.NewMockAppChecker(t)
- mockChecker.EXPECT().AnyCommandExists().Return(false)
- mockChecker.EXPECT().AnyFlatpakExists("com.nonexistent.flatpak").Return(false)
-
- opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
-
- appendConfig(opts, cfgFile, []string{}, []string{"com.nonexistent.flatpak"}, "test.toml")
-
- cfgFile.Close()
- output, err := os.ReadFile(outFile)
- if err != nil {
- t.Fatalf("failed to read output: %v", err)
- }
-
- if len(output) != 0 {
- t.Errorf("expected no config when flatpak doesn't exist, got: %q", string(output))
- }
-}
-
-func TestAppendConfigBothExist(t *testing.T) {
- tempDir := t.TempDir()
-
- shellDir := filepath.Join(tempDir, "shell")
- configsDir := filepath.Join(shellDir, "matugen", "configs")
- if err := os.MkdirAll(configsDir, 0o755); err != nil {
- t.Fatalf("failed to create configs dir: %v", err)
- }
-
- testConfig := "zen config content"
- configPath := filepath.Join(configsDir, "test.toml")
- if err := os.WriteFile(configPath, []byte(testConfig), 0o644); err != nil {
- t.Fatalf("failed to write config: %v", err)
- }
-
- outFile := filepath.Join(tempDir, "output.toml")
- cfgFile, err := os.Create(outFile)
- if err != nil {
- t.Fatalf("failed to create output file: %v", err)
- }
- defer cfgFile.Close()
-
- mockChecker := mocks_utils.NewMockAppChecker(t)
- mockChecker.EXPECT().AnyCommandExists("sh").Return(true)
-
- opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
-
- appendConfig(opts, cfgFile, []string{"sh"}, []string{"app.zen_browser.zen"}, "test.toml")
-
- cfgFile.Close()
- output, err := os.ReadFile(outFile)
- if err != nil {
- t.Fatalf("failed to read output: %v", err)
- }
-
- if len(output) == 0 {
- t.Errorf("expected config to be written when both binary and flatpak exist")
- }
-}
-
-func TestAppendConfigNeitherExists(t *testing.T) {
- tempDir := t.TempDir()
-
- shellDir := filepath.Join(tempDir, "shell")
- configsDir := filepath.Join(shellDir, "matugen", "configs")
- if err := os.MkdirAll(configsDir, 0o755); err != nil {
- t.Fatalf("failed to create configs dir: %v", err)
- }
-
- testConfig := "test config content"
- configPath := filepath.Join(configsDir, "test.toml")
- if err := os.WriteFile(configPath, []byte(testConfig), 0o644); err != nil {
- t.Fatalf("failed to write config: %v", err)
- }
-
- outFile := filepath.Join(tempDir, "output.toml")
- cfgFile, err := os.Create(outFile)
- if err != nil {
- t.Fatalf("failed to create output file: %v", err)
- }
- defer cfgFile.Close()
-
- mockChecker := mocks_utils.NewMockAppChecker(t)
- mockChecker.EXPECT().AnyCommandExists("nonexistent-binary-12345").Return(false)
- mockChecker.EXPECT().AnyFlatpakExists("com.nonexistent.flatpak").Return(false)
-
- opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
-
- appendConfig(opts, cfgFile, []string{"nonexistent-binary-12345"}, []string{"com.nonexistent.flatpak"}, "test.toml")
-
- cfgFile.Close()
- output, err := os.ReadFile(outFile)
- if err != nil {
- t.Fatalf("failed to read output: %v", err)
- }
-
- if len(output) != 0 {
- t.Errorf("expected no config when neither exists, got: %q", string(output))
- }
-}
-
-func TestAppendConfigNoChecks(t *testing.T) {
- tempDir := t.TempDir()
-
- shellDir := filepath.Join(tempDir, "shell")
- configsDir := filepath.Join(shellDir, "matugen", "configs")
- if err := os.MkdirAll(configsDir, 0o755); err != nil {
- t.Fatalf("failed to create configs dir: %v", err)
- }
-
- testConfig := "always include"
- configPath := filepath.Join(configsDir, "test.toml")
- if err := os.WriteFile(configPath, []byte(testConfig), 0o644); err != nil {
- t.Fatalf("failed to write config: %v", err)
- }
-
- outFile := filepath.Join(tempDir, "output.toml")
- cfgFile, err := os.Create(outFile)
- if err != nil {
- t.Fatalf("failed to create output file: %v", err)
- }
- defer cfgFile.Close()
-
- opts := &Options{ShellDir: shellDir}
-
- appendConfig(opts, cfgFile, nil, nil, "test.toml")
-
- cfgFile.Close()
- output, err := os.ReadFile(outFile)
- if err != nil {
- t.Fatalf("failed to read output: %v", err)
- }
-
- if len(output) == 0 {
- t.Errorf("expected config to be written when no checks specified")
- }
-}
-
-func TestAppendConfigFileDoesNotExist(t *testing.T) {
- tempDir := t.TempDir()
-
- shellDir := filepath.Join(tempDir, "shell")
- configsDir := filepath.Join(shellDir, "matugen", "configs")
- if err := os.MkdirAll(configsDir, 0o755); err != nil {
- t.Fatalf("failed to create configs dir: %v", err)
- }
-
- outFile := filepath.Join(tempDir, "output.toml")
- cfgFile, err := os.Create(outFile)
- if err != nil {
- t.Fatalf("failed to create output file: %v", err)
- }
- defer cfgFile.Close()
-
- opts := &Options{ShellDir: shellDir}
-
- appendConfig(opts, cfgFile, nil, nil, "nonexistent.toml")
-
- cfgFile.Close()
- output, err := os.ReadFile(outFile)
- if err != nil {
- t.Fatalf("failed to read output: %v", err)
- }
-
- if len(output) != 0 {
- t.Errorf("expected no config when file doesn't exist, got: %q", string(output))
- }
-}
-
-func TestSubstituteVars(t *testing.T) {
- configDir := utils.XDGConfigHome()
- dataDir := utils.XDGDataHome()
- cacheDir := utils.XDGCacheHome()
-
- tests := []struct {
- name string
- input string
- shellDir string
- expected string
- }{
- {
- name: "substitutes SHELL_DIR",
- input: "input_path = 'SHELL_DIR/matugen/templates/foo.conf'",
- shellDir: "/home/user/shell",
- expected: "input_path = '/home/user/shell/matugen/templates/foo.conf'",
- },
- {
- name: "substitutes CONFIG_DIR",
- input: "output_path = 'CONFIG_DIR/kitty/theme.conf'",
- shellDir: "/home/user/shell",
- expected: "output_path = '" + configDir + "/kitty/theme.conf'",
- },
- {
- name: "substitutes DATA_DIR",
- input: "output_path = 'DATA_DIR/color-schemes/theme.colors'",
- shellDir: "/home/user/shell",
- expected: "output_path = '" + dataDir + "/color-schemes/theme.colors'",
- },
- {
- name: "substitutes CACHE_DIR",
- input: "output_path = 'CACHE_DIR/wal/colors.json'",
- shellDir: "/home/user/shell",
- expected: "output_path = '" + cacheDir + "/wal/colors.json'",
- },
- {
- name: "substitutes all dir types",
- input: "'SHELL_DIR/a' 'CONFIG_DIR/b' 'DATA_DIR/c' 'CACHE_DIR/d'",
- shellDir: "/shell",
- expected: "'/shell/a' '" + configDir + "/b' '" + dataDir + "/c' '" + cacheDir + "/d'",
- },
- {
- name: "no substitution when no placeholders",
- input: "input_path = '/absolute/path/foo.conf'",
- shellDir: "/home/user/shell",
- expected: "input_path = '/absolute/path/foo.conf'",
- },
- {
- name: "multiple SHELL_DIR occurrences",
- input: "'SHELL_DIR/a' and 'SHELL_DIR/b'",
- shellDir: "/shell",
- expected: "'/shell/a' and '/shell/b'",
- },
- {
- name: "only substitutes quoted paths",
- input: "SHELL_DIR/unquoted and 'SHELL_DIR/quoted'",
- shellDir: "/shell",
- expected: "SHELL_DIR/unquoted and '/shell/quoted'",
- },
- }
-
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- result := substituteVars(tc.input, tc.shellDir)
- assert.Equal(t, tc.expected, result)
- })
- }
-}
-
-func TestBuildMergedConfigColorsOnly(t *testing.T) {
- tempDir := t.TempDir()
-
- shellDir := filepath.Join(tempDir, "shell")
- configsDir := filepath.Join(shellDir, "matugen", "configs")
- if err := os.MkdirAll(configsDir, 0o755); err != nil {
- t.Fatalf("failed to create configs dir: %v", err)
- }
-
- baseConfig := "[config]\ncustom_keywords = []\n"
- if err := os.WriteFile(filepath.Join(configsDir, "base.toml"), []byte(baseConfig), 0o644); err != nil {
- t.Fatalf("failed to write base config: %v", err)
- }
-
- cfgFile, err := os.CreateTemp(tempDir, "merged-*.toml")
- if err != nil {
- t.Fatalf("failed to create temp config: %v", err)
- }
- defer os.Remove(cfgFile.Name())
- defer cfgFile.Close()
-
- opts := &Options{
- ShellDir: shellDir,
- ConfigDir: filepath.Join(tempDir, "config"),
- StateDir: filepath.Join(tempDir, "state"),
- ColorsOnly: true,
- }
-
- if err := buildMergedConfig(opts, cfgFile, filepath.Join(tempDir, "templates")); err != nil {
- t.Fatalf("buildMergedConfig failed: %v", err)
- }
-
- if err := cfgFile.Close(); err != nil {
- t.Fatalf("failed to close merged config: %v", err)
- }
-
- output, err := os.ReadFile(cfgFile.Name())
- if err != nil {
- t.Fatalf("failed to read merged config: %v", err)
- }
-
- content := string(output)
- assert.Contains(t, content, "[templates.dank]")
- assert.Contains(t, content, "output_path = '"+filepath.Join(opts.StateDir, "dms-colors.json")+"'")
- assert.NotContains(t, content, "[templates.gtk]")
- assert.False(t, strings.Contains(content, "output_path = 'CONFIG_DIR/"), "colors-only config should not emit app template outputs")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen/queue.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen/queue.go
deleted file mode 100644
index 38cf72a..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/matugen/queue.go
+++ /dev/null
@@ -1,143 +0,0 @@
-package matugen
-
-import (
- "context"
- "errors"
- "sync"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
-)
-
-type Result struct {
- Success bool
- Error error
-}
-
-type QueuedJob struct {
- Options Options
- Done chan Result
- Ctx context.Context
- Cancel context.CancelFunc
-}
-
-type Queue struct {
- mu sync.Mutex
- current *QueuedJob
- pending *QueuedJob
- jobDone chan struct{}
-}
-
-var globalQueue *Queue
-var queueOnce sync.Once
-
-func GetQueue() *Queue {
- queueOnce.Do(func() {
- globalQueue = &Queue{
- jobDone: make(chan struct{}, 1),
- }
- })
- return globalQueue
-}
-
-func (q *Queue) Submit(opts Options) <-chan Result {
- result := make(chan Result, 1)
- ctx, cancel := context.WithCancel(context.Background())
-
- job := &QueuedJob{
- Options: opts,
- Done: result,
- Ctx: ctx,
- Cancel: cancel,
- }
-
- q.mu.Lock()
-
- if q.pending != nil {
- log.Info("Cancelling pending theme request")
- q.pending.Cancel()
- q.pending.Done <- Result{Success: false, Error: context.Canceled}
- close(q.pending.Done)
- }
-
- if q.current != nil {
- q.pending = job
- q.mu.Unlock()
- log.Info("Theme request queued (worker running)")
- return result
- }
-
- q.current = job
- q.mu.Unlock()
-
- go q.runWorker()
- return result
-}
-
-func (q *Queue) runWorker() {
- for {
- q.mu.Lock()
- job := q.current
- if job == nil {
- q.mu.Unlock()
- return
- }
- q.mu.Unlock()
-
- select {
- case <-job.Ctx.Done():
- q.finishJob(Result{Success: false, Error: context.Canceled})
- continue
- default:
- }
-
- log.Infof("Processing theme: %s %s (%s)", job.Options.Kind, job.Options.Value, job.Options.Mode)
- err := Run(job.Options)
-
- var result Result
- switch {
- case err == nil:
- result = Result{Success: true}
- case errors.Is(err, ErrNoChanges):
- result = Result{Success: true}
- default:
- result = Result{Success: false, Error: err}
- }
-
- q.finishJob(result)
- }
-}
-
-func (q *Queue) finishJob(result Result) {
- q.mu.Lock()
- defer q.mu.Unlock()
-
- if q.current != nil {
- select {
- case q.current.Done <- result:
- default:
- }
- close(q.current.Done)
- }
-
- q.current = q.pending
- q.pending = nil
-
- if q.current == nil {
- select {
- case q.jobDone <- struct{}{}:
- default:
- }
- }
-}
-
-func (q *Queue) IsRunning() bool {
- q.mu.Lock()
- defer q.mu.Unlock()
- return q.current != nil
-}
-
-func (q *Queue) HasPending() bool {
- q.mu.Lock()
- defer q.mu.Unlock()
- return q.pending != nil
-}