summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/deployer.go718
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/deployer_test.go792
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/dms.go57
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/alacritty-theme.toml31
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/alacritty.toml37
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/ghostty-colors.conf21
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/ghostty.conf51
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hypr-binds.conf162
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hypr-colors.conf25
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hypr-layout.conf11
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hyprland.conf121
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/kitty-tabs.conf24
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/kitty-theme.conf24
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/kitty.conf37
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-alttab.kdl10
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-binds.kdl221
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-colors.kdl39
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-greeter.kdl17
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-layout.kdl22
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri.kdl279
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/testpage.pdfbin0 -> 7396 bytes
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/hyprland.go15
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/niri.go21
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/terminals.go24
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/testpage.go6
25 files changed, 2765 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/deployer.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/deployer.go
new file mode 100644
index 0000000..7cc513f
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/deployer.go
@@ -0,0 +1,718 @@
+package config
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "time"
+
+ "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
+)
+
+type ConfigDeployer struct {
+ logChan chan<- string
+}
+
+type DeploymentResult struct {
+ ConfigType string
+ Path string
+ BackupPath string
+ Deployed bool
+ Error error
+}
+
+func NewConfigDeployer(logChan chan<- string) *ConfigDeployer {
+ return &ConfigDeployer{
+ logChan: logChan,
+ }
+}
+
+func (cd *ConfigDeployer) log(message string) {
+ if cd.logChan != nil {
+ cd.logChan <- message
+ }
+}
+
+// DeployConfigurations deploys all necessary configurations based on the chosen window manager
+func (cd *ConfigDeployer) DeployConfigurations(ctx context.Context, wm deps.WindowManager) ([]DeploymentResult, error) {
+ return cd.DeployConfigurationsWithTerminal(ctx, wm, deps.TerminalGhostty)
+}
+
+// DeployConfigurationsWithTerminal deploys all necessary configurations based on chosen window manager and terminal
+func (cd *ConfigDeployer) DeployConfigurationsWithTerminal(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal) ([]DeploymentResult, error) {
+ return cd.DeployConfigurationsSelective(ctx, wm, terminal, nil, nil)
+}
+
+// DeployConfigurationsWithSystemd deploys configurations with systemd option
+func (cd *ConfigDeployer) DeployConfigurationsWithSystemd(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, useSystemd bool) ([]DeploymentResult, error) {
+ return cd.deployConfigurationsInternal(ctx, wm, terminal, nil, nil, nil, useSystemd)
+}
+
+func (cd *ConfigDeployer) DeployConfigurationsSelective(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, installedDeps []deps.Dependency, replaceConfigs map[string]bool) ([]DeploymentResult, error) {
+ return cd.DeployConfigurationsSelectiveWithReinstalls(ctx, wm, terminal, installedDeps, replaceConfigs, nil)
+}
+
+func (cd *ConfigDeployer) DeployConfigurationsSelectiveWithReinstalls(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, installedDeps []deps.Dependency, replaceConfigs map[string]bool, reinstallItems map[string]bool) ([]DeploymentResult, error) {
+ return cd.deployConfigurationsInternal(ctx, wm, terminal, installedDeps, replaceConfigs, reinstallItems, true)
+}
+
+func (cd *ConfigDeployer) deployConfigurationsInternal(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, installedDeps []deps.Dependency, replaceConfigs map[string]bool, reinstallItems map[string]bool, useSystemd bool) ([]DeploymentResult, error) {
+ var results []DeploymentResult
+
+ // Primary config file paths used to detect fresh installs.
+ configPrimaryPaths := map[string]string{
+ "Niri": filepath.Join(os.Getenv("HOME"), ".config", "niri", "config.kdl"),
+ "Hyprland": filepath.Join(os.Getenv("HOME"), ".config", "hypr", "hyprland.conf"),
+ "Ghostty": filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "config"),
+ "Kitty": filepath.Join(os.Getenv("HOME"), ".config", "kitty", "kitty.conf"),
+ "Alacritty": filepath.Join(os.Getenv("HOME"), ".config", "alacritty", "alacritty.toml"),
+ }
+
+ shouldReplaceConfig := func(configType string) bool {
+ if replaceConfigs == nil {
+ return true
+ }
+ replace, exists := replaceConfigs[configType]
+ if !exists || replace {
+ return true
+ }
+ // Config is explicitly set to "don't replace" — but still deploy
+ // if the config file doesn't exist yet (fresh install scenario).
+ if primaryPath, ok := configPrimaryPaths[configType]; ok {
+ if _, err := os.Stat(primaryPath); os.IsNotExist(err) {
+ return true
+ }
+ }
+ return false
+ }
+
+ switch wm {
+ case deps.WindowManagerNiri:
+ if shouldReplaceConfig("Niri") {
+ result, err := cd.deployNiriConfig(terminal, useSystemd)
+ results = append(results, result)
+ if err != nil {
+ return results, fmt.Errorf("failed to deploy Niri config: %w", err)
+ }
+ }
+ case deps.WindowManagerHyprland:
+ if shouldReplaceConfig("Hyprland") {
+ result, err := cd.deployHyprlandConfig(terminal, useSystemd)
+ results = append(results, result)
+ if err != nil {
+ return results, fmt.Errorf("failed to deploy Hyprland config: %w", err)
+ }
+ }
+ }
+
+ switch terminal {
+ case deps.TerminalGhostty:
+ if shouldReplaceConfig("Ghostty") {
+ ghosttyResults, err := cd.deployGhosttyConfig()
+ results = append(results, ghosttyResults...)
+ if err != nil {
+ return results, fmt.Errorf("failed to deploy Ghostty config: %w", err)
+ }
+ }
+ case deps.TerminalKitty:
+ if shouldReplaceConfig("Kitty") {
+ kittyResults, err := cd.deployKittyConfig()
+ results = append(results, kittyResults...)
+ if err != nil {
+ return results, fmt.Errorf("failed to deploy Kitty config: %w", err)
+ }
+ }
+ case deps.TerminalAlacritty:
+ if shouldReplaceConfig("Alacritty") {
+ alacrittyResults, err := cd.deployAlacrittyConfig()
+ results = append(results, alacrittyResults...)
+ if err != nil {
+ return results, fmt.Errorf("failed to deploy Alacritty config: %w", err)
+ }
+ }
+ }
+
+ return results, nil
+}
+
+func (cd *ConfigDeployer) deployNiriConfig(terminal deps.Terminal, useSystemd bool) (DeploymentResult, error) {
+ result := DeploymentResult{
+ ConfigType: "Niri",
+ Path: filepath.Join(os.Getenv("HOME"), ".config", "niri", "config.kdl"),
+ }
+
+ configDir := filepath.Dir(result.Path)
+ if err := os.MkdirAll(configDir, 0o755); err != nil {
+ result.Error = fmt.Errorf("failed to create config directory: %w", err)
+ return result, result.Error
+ }
+
+ dmsDir := filepath.Join(configDir, "dms")
+ if err := os.MkdirAll(dmsDir, 0o755); err != nil {
+ result.Error = fmt.Errorf("failed to create dms directory: %w", err)
+ return result, result.Error
+ }
+
+ var existingConfig string
+ if _, err := os.Stat(result.Path); err == nil {
+ cd.log("Found existing Niri configuration")
+
+ existingData, err := os.ReadFile(result.Path)
+ if err != nil {
+ result.Error = fmt.Errorf("failed to read existing config: %w", err)
+ return result, result.Error
+ }
+ existingConfig = string(existingData)
+
+ timestamp := time.Now().Format("2006-01-02_15-04-05")
+ result.BackupPath = result.Path + ".backup." + timestamp
+ if err := os.WriteFile(result.BackupPath, existingData, 0o644); err != nil {
+ result.Error = fmt.Errorf("failed to create backup: %w", err)
+ return result, result.Error
+ }
+ cd.log(fmt.Sprintf("Backed up existing config to %s", result.BackupPath))
+ }
+
+ var terminalCommand string
+ switch terminal {
+ case deps.TerminalGhostty:
+ terminalCommand = "ghostty"
+ case deps.TerminalKitty:
+ terminalCommand = "kitty"
+ case deps.TerminalAlacritty:
+ terminalCommand = "alacritty"
+ default:
+ terminalCommand = "ghostty"
+ }
+
+ newConfig := strings.ReplaceAll(NiriConfig, "{{TERMINAL_COMMAND}}", terminalCommand)
+
+ if !useSystemd {
+ newConfig = cd.transformNiriConfigForNonSystemd(newConfig, terminalCommand)
+ }
+
+ if existingConfig != "" {
+ mergedConfig, err := cd.mergeNiriOutputSections(newConfig, existingConfig, dmsDir)
+ if err != nil {
+ cd.log(fmt.Sprintf("Warning: Failed to merge output sections: %v", err))
+ } else {
+ newConfig = mergedConfig
+ cd.log("Successfully merged existing output sections")
+ }
+ }
+
+ if err := os.WriteFile(result.Path, []byte(newConfig), 0o644); err != nil {
+ result.Error = fmt.Errorf("failed to write config: %w", err)
+ return result, result.Error
+ }
+
+ if err := cd.deployNiriDmsConfigs(dmsDir, terminalCommand); err != nil {
+ result.Error = fmt.Errorf("failed to deploy dms configs: %w", err)
+ return result, result.Error
+ }
+
+ result.Deployed = true
+ cd.log("Successfully deployed Niri configuration")
+ return result, nil
+}
+
+func (cd *ConfigDeployer) deployNiriDmsConfigs(dmsDir, terminalCommand string) error {
+ configs := []struct {
+ name string
+ content string
+ }{
+ {"colors.kdl", NiriColorsConfig},
+ {"layout.kdl", NiriLayoutConfig},
+ {"alttab.kdl", NiriAlttabConfig},
+ {"binds.kdl", strings.ReplaceAll(NiriBindsConfig, "{{TERMINAL_COMMAND}}", terminalCommand)},
+ {"outputs.kdl", ""},
+ {"cursor.kdl", ""},
+ {"windowrules.kdl", ""},
+ }
+
+ for _, cfg := range configs {
+ path := filepath.Join(dmsDir, cfg.name)
+ // Skip if file already exists and is not empty to preserve user modifications
+ if info, err := os.Stat(path); err == nil && info.Size() > 0 {
+ cd.log(fmt.Sprintf("Skipping %s (already exists)", cfg.name))
+ continue
+ }
+ if err := os.WriteFile(path, []byte(cfg.content), 0o644); err != nil {
+ return fmt.Errorf("failed to write %s: %w", cfg.name, err)
+ }
+ cd.log(fmt.Sprintf("Deployed %s", cfg.name))
+ }
+
+ return nil
+}
+
+func (cd *ConfigDeployer) deployGhosttyConfig() ([]DeploymentResult, error) {
+ var results []DeploymentResult
+
+ mainResult := DeploymentResult{
+ ConfigType: "Ghostty",
+ Path: filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "config"),
+ }
+
+ configDir := filepath.Dir(mainResult.Path)
+ if err := os.MkdirAll(configDir, 0o755); err != nil {
+ mainResult.Error = fmt.Errorf("failed to create config directory: %w", err)
+ return []DeploymentResult{mainResult}, mainResult.Error
+ }
+
+ if _, err := os.Stat(mainResult.Path); err == nil {
+ cd.log("Found existing Ghostty configuration")
+
+ existingData, err := os.ReadFile(mainResult.Path)
+ if err != nil {
+ mainResult.Error = fmt.Errorf("failed to read existing config: %w", err)
+ return []DeploymentResult{mainResult}, mainResult.Error
+ }
+
+ timestamp := time.Now().Format("2006-01-02_15-04-05")
+ mainResult.BackupPath = mainResult.Path + ".backup." + timestamp
+ if err := os.WriteFile(mainResult.BackupPath, existingData, 0o644); err != nil {
+ mainResult.Error = fmt.Errorf("failed to create backup: %w", err)
+ return []DeploymentResult{mainResult}, mainResult.Error
+ }
+ cd.log(fmt.Sprintf("Backed up existing config to %s", mainResult.BackupPath))
+ }
+
+ if err := os.WriteFile(mainResult.Path, []byte(GhosttyConfig), 0o644); err != nil {
+ mainResult.Error = fmt.Errorf("failed to write config: %w", err)
+ return []DeploymentResult{mainResult}, mainResult.Error
+ }
+
+ mainResult.Deployed = true
+ cd.log("Successfully deployed Ghostty configuration")
+ results = append(results, mainResult)
+
+ colorResult := DeploymentResult{
+ ConfigType: "Ghostty Colors",
+ Path: filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "themes", "dankcolors"),
+ }
+
+ themesDir := filepath.Dir(colorResult.Path)
+ if err := os.MkdirAll(themesDir, 0o755); err != nil {
+ mainResult.Error = fmt.Errorf("failed to create themes directory: %w", err)
+ return []DeploymentResult{mainResult}, mainResult.Error
+ }
+
+ if err := os.WriteFile(colorResult.Path, []byte(GhosttyColorConfig), 0o644); err != nil {
+ colorResult.Error = fmt.Errorf("failed to write color config: %w", err)
+ return results, colorResult.Error
+ }
+
+ colorResult.Deployed = true
+ cd.log("Successfully deployed Ghostty color configuration")
+ results = append(results, colorResult)
+
+ return results, nil
+}
+
+func (cd *ConfigDeployer) deployKittyConfig() ([]DeploymentResult, error) {
+ var results []DeploymentResult
+
+ mainResult := DeploymentResult{
+ ConfigType: "Kitty",
+ Path: filepath.Join(os.Getenv("HOME"), ".config", "kitty", "kitty.conf"),
+ }
+
+ configDir := filepath.Dir(mainResult.Path)
+ if err := os.MkdirAll(configDir, 0o755); err != nil {
+ mainResult.Error = fmt.Errorf("failed to create config directory: %w", err)
+ return []DeploymentResult{mainResult}, mainResult.Error
+ }
+
+ if _, err := os.Stat(mainResult.Path); err == nil {
+ cd.log("Found existing Kitty configuration")
+
+ existingData, err := os.ReadFile(mainResult.Path)
+ if err != nil {
+ mainResult.Error = fmt.Errorf("failed to read existing config: %w", err)
+ return []DeploymentResult{mainResult}, mainResult.Error
+ }
+
+ timestamp := time.Now().Format("2006-01-02_15-04-05")
+ mainResult.BackupPath = mainResult.Path + ".backup." + timestamp
+ if err := os.WriteFile(mainResult.BackupPath, existingData, 0o644); err != nil {
+ mainResult.Error = fmt.Errorf("failed to create backup: %w", err)
+ return []DeploymentResult{mainResult}, mainResult.Error
+ }
+ cd.log(fmt.Sprintf("Backed up existing config to %s", mainResult.BackupPath))
+ }
+
+ if err := os.WriteFile(mainResult.Path, []byte(KittyConfig), 0o644); err != nil {
+ mainResult.Error = fmt.Errorf("failed to write config: %w", err)
+ return []DeploymentResult{mainResult}, mainResult.Error
+ }
+
+ mainResult.Deployed = true
+ cd.log("Successfully deployed Kitty configuration")
+ results = append(results, mainResult)
+
+ themeResult := DeploymentResult{
+ ConfigType: "Kitty Theme",
+ Path: filepath.Join(os.Getenv("HOME"), ".config", "kitty", "dank-theme.conf"),
+ }
+
+ if err := os.WriteFile(themeResult.Path, []byte(KittyThemeConfig), 0o644); err != nil {
+ themeResult.Error = fmt.Errorf("failed to write theme config: %w", err)
+ return results, themeResult.Error
+ }
+
+ themeResult.Deployed = true
+ cd.log("Successfully deployed Kitty theme configuration")
+ results = append(results, themeResult)
+
+ tabsResult := DeploymentResult{
+ ConfigType: "Kitty Tabs",
+ Path: filepath.Join(os.Getenv("HOME"), ".config", "kitty", "dank-tabs.conf"),
+ }
+
+ if err := os.WriteFile(tabsResult.Path, []byte(KittyTabsConfig), 0o644); err != nil {
+ tabsResult.Error = fmt.Errorf("failed to write tabs config: %w", err)
+ return results, tabsResult.Error
+ }
+
+ tabsResult.Deployed = true
+ cd.log("Successfully deployed Kitty tabs configuration")
+ results = append(results, tabsResult)
+
+ return results, nil
+}
+
+func (cd *ConfigDeployer) deployAlacrittyConfig() ([]DeploymentResult, error) {
+ var results []DeploymentResult
+
+ mainResult := DeploymentResult{
+ ConfigType: "Alacritty",
+ Path: filepath.Join(os.Getenv("HOME"), ".config", "alacritty", "alacritty.toml"),
+ }
+
+ configDir := filepath.Dir(mainResult.Path)
+ if err := os.MkdirAll(configDir, 0o755); err != nil {
+ mainResult.Error = fmt.Errorf("failed to create config directory: %w", err)
+ return []DeploymentResult{mainResult}, mainResult.Error
+ }
+
+ if _, err := os.Stat(mainResult.Path); err == nil {
+ cd.log("Found existing Alacritty configuration")
+
+ existingData, err := os.ReadFile(mainResult.Path)
+ if err != nil {
+ mainResult.Error = fmt.Errorf("failed to read existing config: %w", err)
+ return []DeploymentResult{mainResult}, mainResult.Error
+ }
+
+ timestamp := time.Now().Format("2006-01-02_15-04-05")
+ mainResult.BackupPath = mainResult.Path + ".backup." + timestamp
+ if err := os.WriteFile(mainResult.BackupPath, existingData, 0o644); err != nil {
+ mainResult.Error = fmt.Errorf("failed to create backup: %w", err)
+ return []DeploymentResult{mainResult}, mainResult.Error
+ }
+ cd.log(fmt.Sprintf("Backed up existing config to %s", mainResult.BackupPath))
+ }
+
+ if err := os.WriteFile(mainResult.Path, []byte(AlacrittyConfig), 0o644); err != nil {
+ mainResult.Error = fmt.Errorf("failed to write config: %w", err)
+ return []DeploymentResult{mainResult}, mainResult.Error
+ }
+
+ mainResult.Deployed = true
+ cd.log("Successfully deployed Alacritty configuration")
+ results = append(results, mainResult)
+
+ themeResult := DeploymentResult{
+ ConfigType: "Alacritty Theme",
+ Path: filepath.Join(os.Getenv("HOME"), ".config", "alacritty", "dank-theme.toml"),
+ }
+
+ if err := os.WriteFile(themeResult.Path, []byte(AlacrittyThemeConfig), 0o644); err != nil {
+ themeResult.Error = fmt.Errorf("failed to write theme config: %w", err)
+ return results, themeResult.Error
+ }
+
+ themeResult.Deployed = true
+ cd.log("Successfully deployed Alacritty theme configuration")
+ results = append(results, themeResult)
+
+ return results, nil
+}
+
+func (cd *ConfigDeployer) mergeNiriOutputSections(newConfig, existingConfig, dmsDir string) (string, error) {
+ outputRegex := regexp.MustCompile(`(?m)^(/-)?\s*output\s+"[^"]+"\s*\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}`)
+ existingOutputs := outputRegex.FindAllString(existingConfig, -1)
+
+ if len(existingOutputs) == 0 {
+ return newConfig, nil
+ }
+
+ outputsPath := filepath.Join(dmsDir, "outputs.kdl")
+ if _, err := os.Stat(outputsPath); err != nil {
+ var outputsContent strings.Builder
+ for _, output := range existingOutputs {
+ outputsContent.WriteString(output)
+ outputsContent.WriteString("\n\n")
+ }
+ if err := os.WriteFile(outputsPath, []byte(outputsContent.String()), 0o644); err != nil {
+ cd.log(fmt.Sprintf("Warning: Failed to migrate outputs to %s: %v", outputsPath, err))
+ } else {
+ cd.log("Migrated output sections to dms/outputs.kdl")
+ }
+ }
+
+ exampleOutputRegex := regexp.MustCompile(`(?m)^/-output "eDP-2" \{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}`)
+ mergedConfig := exampleOutputRegex.ReplaceAllString(newConfig, "")
+
+ inputEndRegex := regexp.MustCompile(`(?m)^}$`)
+ inputMatches := inputEndRegex.FindAllStringIndex(newConfig, -1)
+
+ if len(inputMatches) < 1 {
+ return "", fmt.Errorf("could not find insertion point for output sections")
+ }
+
+ insertPos := inputMatches[0][1]
+
+ var builder strings.Builder
+ builder.WriteString(mergedConfig[:insertPos])
+ builder.WriteString("\n// Outputs from existing configuration\n")
+
+ for _, output := range existingOutputs {
+ builder.WriteString(output)
+ builder.WriteString("\n")
+ }
+
+ builder.WriteString(mergedConfig[insertPos:])
+
+ return builder.String(), nil
+}
+
+// deployHyprlandConfig handles Hyprland configuration deployment with backup and merging
+func (cd *ConfigDeployer) deployHyprlandConfig(terminal deps.Terminal, useSystemd bool) (DeploymentResult, error) {
+ result := DeploymentResult{
+ ConfigType: "Hyprland",
+ Path: filepath.Join(os.Getenv("HOME"), ".config", "hypr", "hyprland.conf"),
+ }
+
+ configDir := filepath.Dir(result.Path)
+ if err := os.MkdirAll(configDir, 0o755); err != nil {
+ result.Error = fmt.Errorf("failed to create config directory: %w", err)
+ return result, result.Error
+ }
+
+ dmsDir := filepath.Join(configDir, "dms")
+ if err := os.MkdirAll(dmsDir, 0o755); err != nil {
+ result.Error = fmt.Errorf("failed to create dms directory: %w", err)
+ return result, result.Error
+ }
+
+ var existingConfig string
+ if _, err := os.Stat(result.Path); err == nil {
+ cd.log("Found existing Hyprland configuration")
+
+ existingData, err := os.ReadFile(result.Path)
+ if err != nil {
+ result.Error = fmt.Errorf("failed to read existing config: %w", err)
+ return result, result.Error
+ }
+ existingConfig = string(existingData)
+
+ timestamp := time.Now().Format("2006-01-02_15-04-05")
+ result.BackupPath = result.Path + ".backup." + timestamp
+ if err := os.WriteFile(result.BackupPath, existingData, 0o644); err != nil {
+ result.Error = fmt.Errorf("failed to create backup: %w", err)
+ return result, result.Error
+ }
+ cd.log(fmt.Sprintf("Backed up existing config to %s", result.BackupPath))
+ }
+
+ var terminalCommand string
+ switch terminal {
+ case deps.TerminalGhostty:
+ terminalCommand = "ghostty"
+ case deps.TerminalKitty:
+ terminalCommand = "kitty"
+ case deps.TerminalAlacritty:
+ terminalCommand = "alacritty"
+ default:
+ terminalCommand = "ghostty"
+ }
+
+ newConfig := strings.ReplaceAll(HyprlandConfig, "{{TERMINAL_COMMAND}}", terminalCommand)
+
+ if !useSystemd {
+ newConfig = cd.transformHyprlandConfigForNonSystemd(newConfig, terminalCommand)
+ }
+
+ if existingConfig != "" {
+ mergedConfig, err := cd.mergeHyprlandMonitorSections(newConfig, existingConfig, dmsDir)
+ if err != nil {
+ cd.log(fmt.Sprintf("Warning: Failed to merge monitor sections: %v", err))
+ } else {
+ newConfig = mergedConfig
+ cd.log("Successfully merged existing monitor sections")
+ }
+ }
+
+ if err := os.WriteFile(result.Path, []byte(newConfig), 0o644); err != nil {
+ result.Error = fmt.Errorf("failed to write config: %w", err)
+ return result, result.Error
+ }
+
+ if err := cd.deployHyprlandDmsConfigs(dmsDir, terminalCommand); err != nil {
+ result.Error = fmt.Errorf("failed to deploy dms configs: %w", err)
+ return result, result.Error
+ }
+
+ result.Deployed = true
+ cd.log("Successfully deployed Hyprland configuration")
+ return result, nil
+}
+
+func (cd *ConfigDeployer) deployHyprlandDmsConfigs(dmsDir string, terminalCommand string) error {
+ configs := []struct {
+ name string
+ content string
+ }{
+ {"colors.conf", HyprColorsConfig},
+ {"layout.conf", HyprLayoutConfig},
+ {"binds.conf", strings.ReplaceAll(HyprBindsConfig, "{{TERMINAL_COMMAND}}", terminalCommand)},
+ {"outputs.conf", ""},
+ {"cursor.conf", ""},
+ {"windowrules.conf", ""},
+ }
+
+ for _, cfg := range configs {
+ path := filepath.Join(dmsDir, cfg.name)
+ // Skip if file already exists and is not empty to preserve user modifications
+ if info, err := os.Stat(path); err == nil && info.Size() > 0 {
+ cd.log(fmt.Sprintf("Skipping %s (already exists)", cfg.name))
+ continue
+ }
+ if err := os.WriteFile(path, []byte(cfg.content), 0o644); err != nil {
+ return fmt.Errorf("failed to write %s: %w", cfg.name, err)
+ }
+ cd.log(fmt.Sprintf("Deployed %s", cfg.name))
+ }
+
+ return nil
+}
+
+func (cd *ConfigDeployer) mergeHyprlandMonitorSections(newConfig, existingConfig, dmsDir string) (string, error) {
+ monitorRegex := regexp.MustCompile(`(?m)^#?\s*monitor\s*=.*$`)
+ existingMonitors := monitorRegex.FindAllString(existingConfig, -1)
+
+ if len(existingMonitors) == 0 {
+ return newConfig, nil
+ }
+
+ outputsPath := filepath.Join(dmsDir, "outputs.conf")
+ if _, err := os.Stat(outputsPath); err != nil {
+ var outputsContent strings.Builder
+ for _, monitor := range existingMonitors {
+ outputsContent.WriteString(monitor)
+ outputsContent.WriteString("\n")
+ }
+ if err := os.WriteFile(outputsPath, []byte(outputsContent.String()), 0o644); err != nil {
+ cd.log(fmt.Sprintf("Warning: Failed to migrate monitors to %s: %v", outputsPath, err))
+ } else {
+ cd.log("Migrated monitor sections to dms/outputs.conf")
+ }
+ }
+
+ exampleMonitorRegex := regexp.MustCompile(`(?m)^# monitor = eDP-2.*$`)
+ mergedConfig := exampleMonitorRegex.ReplaceAllString(newConfig, "")
+
+ monitorHeaderRegex := regexp.MustCompile(`(?m)^# MONITOR CONFIG\n# ==================$`)
+ headerMatch := monitorHeaderRegex.FindStringIndex(mergedConfig)
+
+ if headerMatch == nil {
+ return "", fmt.Errorf("could not find MONITOR CONFIG section")
+ }
+
+ insertPos := headerMatch[1] + 1
+
+ var builder strings.Builder
+ builder.WriteString(mergedConfig[:insertPos])
+ builder.WriteString("# Monitors from existing configuration\n")
+
+ for _, monitor := range existingMonitors {
+ builder.WriteString(monitor)
+ builder.WriteString("\n")
+ }
+
+ builder.WriteString(mergedConfig[insertPos:])
+
+ return builder.String(), nil
+}
+
+func (cd *ConfigDeployer) transformHyprlandConfigForNonSystemd(config, terminalCommand string) string {
+ lines := strings.Split(config, "\n")
+ var result []string
+ startupSectionFound := false
+
+ for _, line := range lines {
+ trimmed := strings.TrimSpace(line)
+ if strings.HasPrefix(trimmed, "exec-once = dbus-update-activation-environment") {
+ continue
+ }
+ if strings.HasPrefix(trimmed, "exec-once = systemctl --user start") {
+ startupSectionFound = true
+ result = append(result, "exec-once = dms run")
+ result = append(result, "env = QT_QPA_PLATFORM,wayland;xcb")
+ result = append(result, "env = ELECTRON_OZONE_PLATFORM_HINT,auto")
+ result = append(result, "env = QT_QPA_PLATFORMTHEME,gtk3")
+ result = append(result, "env = QT_QPA_PLATFORMTHEME_QT6,gtk3")
+ result = append(result, fmt.Sprintf("env = TERMINAL,%s", terminalCommand))
+ continue
+ }
+ result = append(result, line)
+ }
+
+ if !startupSectionFound {
+ for i, line := range result {
+ if strings.Contains(line, "STARTUP APPS") {
+ insertLines := []string{
+ "exec-once = dms run",
+ "env = QT_QPA_PLATFORM,wayland;xcb",
+ "env = ELECTRON_OZONE_PLATFORM_HINT,auto",
+ "env = QT_QPA_PLATFORMTHEME,gtk3",
+ "env = QT_QPA_PLATFORMTHEME_QT6,gtk3",
+ fmt.Sprintf("env = TERMINAL,%s", terminalCommand),
+ }
+ result = append(result[:i+2], append(insertLines, result[i+2:]...)...)
+ break
+ }
+ }
+ }
+
+ return strings.Join(result, "\n")
+}
+
+func (cd *ConfigDeployer) transformNiriConfigForNonSystemd(config, terminalCommand string) string {
+ envVars := fmt.Sprintf(`environment {
+ XDG_CURRENT_DESKTOP "niri"
+ QT_QPA_PLATFORM "wayland;xcb"
+ ELECTRON_OZONE_PLATFORM_HINT "auto"
+ QT_QPA_PLATFORMTHEME "gtk3"
+ QT_QPA_PLATFORMTHEME_QT6 "gtk3"
+ TERMINAL "%s"
+}`, terminalCommand)
+
+ config = regexp.MustCompile(`environment \{[^}]*\}`).ReplaceAllString(config, envVars)
+
+ spawnDms := `spawn-at-startup "dms" "run"`
+ if !strings.Contains(config, spawnDms) {
+ // Insert spawn-at-startup for dms after the environment block
+ envBlockEnd := regexp.MustCompile(`environment \{[^}]*\}`)
+ if loc := envBlockEnd.FindStringIndex(config); loc != nil {
+ config = config[:loc[1]] + "\n" + spawnDms + config[loc[1]:]
+ }
+ }
+
+ return config
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/deployer_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/deployer_test.go
new file mode 100644
index 0000000..3726634
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/deployer_test.go
@@ -0,0 +1,792 @@
+package config
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestMergeNiriOutputSections(t *testing.T) {
+ cd := &ConfigDeployer{}
+
+ tests := []struct {
+ name string
+ newConfig string
+ existingConfig string
+ wantError bool
+ wantContains []string
+ }{
+ {
+ name: "no existing outputs",
+ newConfig: `input {
+ keyboard {
+ xkb {
+ }
+ }
+}
+layout {
+ gaps 5
+}`,
+ existingConfig: `input {
+ keyboard {
+ xkb {
+ }
+ }
+}
+layout {
+ gaps 10
+}`,
+ wantError: false,
+ wantContains: []string{"gaps 5"}, // Should keep new config
+ },
+ {
+ name: "merge single output",
+ newConfig: `input {
+ keyboard {
+ xkb {
+ }
+ }
+}
+/-output "eDP-2" {
+ mode "2560x1600@239.998993"
+ position x=2560 y=0
+}
+layout {
+ gaps 5
+}`,
+ existingConfig: `input {
+ keyboard {
+ xkb {
+ }
+ }
+}
+output "eDP-1" {
+ mode "1920x1080@60.000000"
+ position x=0 y=0
+ scale 1.0
+}
+layout {
+ gaps 10
+}`,
+ wantError: false,
+ wantContains: []string{
+ "gaps 5", // New config preserved
+ `output "eDP-1"`, // Existing output merged
+ "1920x1080@60.000000", // Existing output details
+ "Outputs from existing configuration", // Comment added
+ },
+ },
+ {
+ name: "merge multiple outputs",
+ newConfig: `input {
+ keyboard {
+ xkb {
+ }
+ }
+}
+/-output "eDP-2" {
+ mode "2560x1600@239.998993"
+ position x=2560 y=0
+}
+layout {
+ gaps 5
+}`,
+ existingConfig: `input {
+ keyboard {
+ xkb {
+ }
+ }
+}
+output "eDP-1" {
+ mode "1920x1080@60.000000"
+ position x=0 y=0
+ scale 1.0
+}
+/-output "HDMI-1" {
+ mode "1920x1080@60.000000"
+ position x=1920 y=0
+}
+layout {
+ gaps 10
+}`,
+ wantError: false,
+ wantContains: []string{
+ "gaps 5", // New config preserved
+ `output "eDP-1"`, // First existing output
+ `/-output "HDMI-1"`, // Second existing output (commented)
+ "1920x1080@60.000000", // Output details
+ },
+ },
+ {
+ name: "merge commented outputs",
+ newConfig: `input {
+ keyboard {
+ xkb {
+ }
+ }
+}
+/-output "eDP-2" {
+ mode "2560x1600@239.998993"
+ position x=2560 y=0
+}
+layout {
+ gaps 5
+}`,
+ existingConfig: `input {
+ keyboard {
+ xkb {
+ }
+ }
+}
+/-output "eDP-1" {
+ mode "1920x1080@60.000000"
+ position x=0 y=0
+ scale 1.0
+}
+layout {
+ gaps 10
+}`,
+ wantError: false,
+ wantContains: []string{
+ "gaps 5", // New config preserved
+ `/-output "eDP-1"`, // Commented output preserved
+ "1920x1080@60.000000", // Output details
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ tmpDir := t.TempDir()
+ result, err := cd.mergeNiriOutputSections(tt.newConfig, tt.existingConfig, tmpDir)
+
+ if tt.wantError {
+ assert.Error(t, err)
+ return
+ }
+
+ require.NoError(t, err)
+
+ for _, want := range tt.wantContains {
+ assert.Contains(t, result, want, "merged config should contain: %s", want)
+ }
+
+ assert.NotContains(t, result, `/-output "eDP-2"`, "example output should be removed")
+ })
+ }
+}
+
+func TestConfigDeploymentFlow(t *testing.T) {
+ tempDir, err := os.MkdirTemp("", "dankinstall-test")
+ require.NoError(t, err)
+ defer os.RemoveAll(tempDir)
+
+ originalHome := os.Getenv("HOME")
+ os.Setenv("HOME", tempDir)
+ defer os.Setenv("HOME", originalHome)
+
+ logChan := make(chan string, 100)
+ cd := NewConfigDeployer(logChan)
+
+ t.Run("deploy ghostty config to empty directory", func(t *testing.T) {
+ results, err := cd.deployGhosttyConfig()
+ require.NoError(t, err)
+ require.Len(t, results, 2)
+
+ mainResult := results[0]
+ assert.Equal(t, "Ghostty", mainResult.ConfigType)
+ assert.True(t, mainResult.Deployed)
+ assert.Empty(t, mainResult.BackupPath)
+ assert.FileExists(t, mainResult.Path)
+
+ content, err := os.ReadFile(mainResult.Path)
+ require.NoError(t, err)
+ assert.Contains(t, string(content), "window-decoration = false")
+
+ colorResult := results[1]
+ assert.Equal(t, "Ghostty Colors", colorResult.ConfigType)
+ assert.True(t, colorResult.Deployed)
+ assert.FileExists(t, colorResult.Path)
+
+ colorContent, err := os.ReadFile(colorResult.Path)
+ require.NoError(t, err)
+ assert.Contains(t, string(colorContent), "background = #101418")
+ })
+
+ t.Run("deploy ghostty config with existing file", func(t *testing.T) {
+ existingContent := "# Old config\nfont-size = 14\n"
+ ghosttyPath := getGhosttyPath()
+ err := os.MkdirAll(filepath.Dir(ghosttyPath), 0o755)
+ require.NoError(t, err)
+ err = os.WriteFile(ghosttyPath, []byte(existingContent), 0o644)
+ require.NoError(t, err)
+
+ results, err := cd.deployGhosttyConfig()
+ require.NoError(t, err)
+ require.Len(t, results, 2)
+
+ mainResult := results[0]
+ assert.Equal(t, "Ghostty", mainResult.ConfigType)
+ assert.True(t, mainResult.Deployed)
+ assert.NotEmpty(t, mainResult.BackupPath)
+ assert.FileExists(t, mainResult.Path)
+ assert.FileExists(t, mainResult.BackupPath)
+
+ backupContent, err := os.ReadFile(mainResult.BackupPath)
+ require.NoError(t, err)
+ assert.Equal(t, existingContent, string(backupContent))
+
+ newContent, err := os.ReadFile(mainResult.Path)
+ require.NoError(t, err)
+ assert.NotContains(t, string(newContent), "# Old config")
+
+ colorResult := results[1]
+ assert.Equal(t, "Ghostty Colors", colorResult.ConfigType)
+ assert.True(t, colorResult.Deployed)
+ assert.FileExists(t, colorResult.Path)
+ })
+}
+
+func getGhosttyPath() string {
+ return filepath.Join(os.Getenv("HOME"), ".config", "ghostty", "config")
+}
+
+func TestMergeHyprlandMonitorSections(t *testing.T) {
+ cd := &ConfigDeployer{}
+
+ tests := []struct {
+ name string
+ newConfig string
+ existingConfig string
+ wantError bool
+ wantContains []string
+ wantNotContains []string
+ }{
+ {
+ name: "no existing monitors",
+ newConfig: `# ==================
+# MONITOR CONFIG
+# ==================
+# monitor = eDP-2, 2560x1600@239.998993, 2560x0, 1, vrr, 1
+
+# ==================
+# ENVIRONMENT VARS
+# ==================
+env = XDG_CURRENT_DESKTOP,niri`,
+ existingConfig: `# Some other config
+input {
+ kb_layout = us
+}`,
+ wantError: false,
+ wantContains: []string{"MONITOR CONFIG", "ENVIRONMENT VARS"},
+ },
+ {
+ name: "merge single monitor",
+ newConfig: `# ==================
+# MONITOR CONFIG
+# ==================
+# monitor = eDP-2, 2560x1600@239.998993, 2560x0, 1, vrr, 1
+
+# ==================
+# ENVIRONMENT VARS
+# ==================`,
+ existingConfig: `# My config
+monitor = DP-1, 1920x1080@144, 0x0, 1
+input {
+ kb_layout = us
+}`,
+ wantError: false,
+ wantContains: []string{
+ "MONITOR CONFIG",
+ "monitor = DP-1, 1920x1080@144, 0x0, 1",
+ "Monitors from existing configuration",
+ },
+ wantNotContains: []string{
+ "monitor = eDP-2", // Example monitor should be removed
+ },
+ },
+ {
+ name: "merge multiple monitors",
+ newConfig: `# ==================
+# MONITOR CONFIG
+# ==================
+# monitor = eDP-2, 2560x1600@239.998993, 2560x0, 1, vrr, 1
+
+# ==================
+# ENVIRONMENT VARS
+# ==================`,
+ existingConfig: `monitor = DP-1, 1920x1080@144, 0x0, 1
+# monitor = HDMI-A-1, 1920x1080@60, 1920x0, 1
+monitor = eDP-1, 2560x1440@165, auto, 1.25`,
+ wantError: false,
+ wantContains: []string{
+ "monitor = DP-1",
+ "# monitor = HDMI-A-1", // Commented monitor preserved
+ "monitor = eDP-1",
+ "Monitors from existing configuration",
+ },
+ wantNotContains: []string{
+ "monitor = eDP-2", // Example monitor should be removed
+ },
+ },
+ {
+ name: "preserve commented monitors",
+ newConfig: `# ==================
+# MONITOR CONFIG
+# ==================
+# monitor = eDP-2, 2560x1600@239.998993, 2560x0, 1, vrr, 1
+
+# ==================`,
+ existingConfig: `# monitor = DP-1, 1920x1080@144, 0x0, 1
+# monitor = HDMI-A-1, 1920x1080@60, 1920x0, 1`,
+ wantError: false,
+ wantContains: []string{
+ "# monitor = DP-1",
+ "# monitor = HDMI-A-1",
+ "Monitors from existing configuration",
+ },
+ },
+ {
+ name: "no monitor config section",
+ newConfig: `# Some config without monitor section
+input {
+ kb_layout = us
+}`,
+ existingConfig: `monitor = DP-1, 1920x1080@144, 0x0, 1`,
+ wantError: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ tmpDir := t.TempDir()
+ result, err := cd.mergeHyprlandMonitorSections(tt.newConfig, tt.existingConfig, tmpDir)
+
+ if tt.wantError {
+ assert.Error(t, err)
+ return
+ }
+
+ require.NoError(t, err)
+
+ for _, want := range tt.wantContains {
+ assert.Contains(t, result, want, "merged config should contain: %s", want)
+ }
+
+ for _, notWant := range tt.wantNotContains {
+ assert.NotContains(t, result, notWant, "merged config should NOT contain: %s", notWant)
+ }
+ })
+ }
+}
+
+func TestHyprlandConfigDeployment(t *testing.T) {
+ tempDir, err := os.MkdirTemp("", "dankinstall-hyprland-test")
+ require.NoError(t, err)
+ defer os.RemoveAll(tempDir)
+
+ originalHome := os.Getenv("HOME")
+ os.Setenv("HOME", tempDir)
+ defer os.Setenv("HOME", originalHome)
+
+ logChan := make(chan string, 100)
+ cd := NewConfigDeployer(logChan)
+
+ t.Run("deploy hyprland config to empty directory", func(t *testing.T) {
+ result, err := cd.deployHyprlandConfig(deps.TerminalGhostty, true)
+ require.NoError(t, err)
+
+ assert.Equal(t, "Hyprland", result.ConfigType)
+ assert.True(t, result.Deployed)
+ assert.Empty(t, result.BackupPath)
+ assert.FileExists(t, result.Path)
+
+ content, err := os.ReadFile(result.Path)
+ require.NoError(t, err)
+ assert.Contains(t, string(content), "# MONITOR CONFIG")
+ assert.Contains(t, string(content), "source = ./dms/binds.conf")
+ assert.Contains(t, string(content), "exec-once = ")
+ })
+
+ t.Run("deploy hyprland config with existing monitors", func(t *testing.T) {
+ existingContent := `# My existing Hyprland config
+monitor = DP-1, 1920x1080@144, 0x0, 1
+monitor = HDMI-A-1, 3840x2160@60, 1920x0, 1.5
+
+general {
+ gaps_in = 10
+}
+`
+ hyprPath := filepath.Join(tempDir, ".config", "hypr", "hyprland.conf")
+ err := os.MkdirAll(filepath.Dir(hyprPath), 0o755)
+ require.NoError(t, err)
+ err = os.WriteFile(hyprPath, []byte(existingContent), 0o644)
+ require.NoError(t, err)
+
+ result, err := cd.deployHyprlandConfig(deps.TerminalKitty, true)
+ require.NoError(t, err)
+
+ assert.Equal(t, "Hyprland", result.ConfigType)
+ assert.True(t, result.Deployed)
+ assert.NotEmpty(t, result.BackupPath)
+ assert.FileExists(t, result.Path)
+ assert.FileExists(t, result.BackupPath)
+
+ backupContent, err := os.ReadFile(result.BackupPath)
+ require.NoError(t, err)
+ assert.Equal(t, existingContent, string(backupContent))
+
+ newContent, err := os.ReadFile(result.Path)
+ require.NoError(t, err)
+ assert.Contains(t, string(newContent), "monitor = DP-1, 1920x1080@144")
+ assert.Contains(t, string(newContent), "monitor = HDMI-A-1, 3840x2160@60")
+ assert.Contains(t, string(newContent), "source = ./dms/binds.conf")
+ assert.NotContains(t, string(newContent), "monitor = eDP-2")
+ })
+}
+
+func TestNiriConfigStructure(t *testing.T) {
+ assert.Contains(t, NiriConfig, "input {")
+ assert.Contains(t, NiriConfig, "layout {")
+
+ assert.Contains(t, NiriBindsConfig, "binds {")
+ assert.Contains(t, NiriBindsConfig, `spawn "{{TERMINAL_COMMAND}}"`)
+}
+
+func TestHyprlandConfigStructure(t *testing.T) {
+ assert.Contains(t, HyprlandConfig, "# MONITOR CONFIG")
+ assert.Contains(t, HyprlandConfig, "# STARTUP APPS")
+ assert.Contains(t, HyprlandConfig, "# INPUT CONFIG")
+ assert.Contains(t, HyprlandConfig, "source = ./dms/binds.conf")
+}
+
+func TestGhosttyConfigStructure(t *testing.T) {
+ assert.Contains(t, GhosttyConfig, "window-decoration = false")
+ assert.Contains(t, GhosttyConfig, "background-opacity = 1.0")
+ assert.Contains(t, GhosttyConfig, "theme = dankcolors")
+}
+
+func TestGhosttyColorConfigStructure(t *testing.T) {
+ assert.Contains(t, GhosttyColorConfig, "background = #101418")
+ assert.Contains(t, GhosttyColorConfig, "foreground = #e0e2e8")
+ assert.Contains(t, GhosttyColorConfig, "cursor-color = #9dcbfb")
+ assert.Contains(t, GhosttyColorConfig, "palette = 0=#101418")
+ assert.Contains(t, GhosttyColorConfig, "palette = 15=#ffffff")
+}
+
+func TestKittyConfigStructure(t *testing.T) {
+ assert.Contains(t, KittyConfig, "font_size 12.0")
+ assert.Contains(t, KittyConfig, "window_padding_width 12")
+ assert.Contains(t, KittyConfig, "background_opacity 1.0")
+ assert.Contains(t, KittyConfig, "include dank-tabs.conf")
+ assert.Contains(t, KittyConfig, "include dank-theme.conf")
+}
+
+func TestKittyThemeConfigStructure(t *testing.T) {
+ assert.Contains(t, KittyThemeConfig, "foreground #e0e2e8")
+ assert.Contains(t, KittyThemeConfig, "background #101418")
+ assert.Contains(t, KittyThemeConfig, "cursor #e0e2e8")
+ assert.Contains(t, KittyThemeConfig, "color0 #101418")
+ assert.Contains(t, KittyThemeConfig, "color15 #ffffff")
+}
+
+func TestKittyTabsConfigStructure(t *testing.T) {
+ assert.Contains(t, KittyTabsConfig, "tab_bar_style powerline")
+ assert.Contains(t, KittyTabsConfig, "tab_powerline_style slanted")
+ assert.Contains(t, KittyTabsConfig, "active_tab_background #124a73")
+ assert.Contains(t, KittyTabsConfig, "inactive_tab_background #101418")
+}
+
+func TestAlacrittyConfigStructure(t *testing.T) {
+ assert.Contains(t, AlacrittyConfig, "[general]")
+ assert.Contains(t, AlacrittyConfig, "~/.config/alacritty/dank-theme.toml")
+ assert.Contains(t, AlacrittyConfig, "[window]")
+ assert.Contains(t, AlacrittyConfig, "decorations = \"None\"")
+ assert.Contains(t, AlacrittyConfig, "padding = { x = 12, y = 12 }")
+ assert.Contains(t, AlacrittyConfig, "[cursor]")
+ assert.Contains(t, AlacrittyConfig, "[keyboard]")
+}
+
+func TestAlacrittyThemeConfigStructure(t *testing.T) {
+ assert.Contains(t, AlacrittyThemeConfig, "[colors.primary]")
+ assert.Contains(t, AlacrittyThemeConfig, "background = '#101418'")
+ assert.Contains(t, AlacrittyThemeConfig, "foreground = '#e0e2e8'")
+ assert.Contains(t, AlacrittyThemeConfig, "[colors.cursor]")
+ assert.Contains(t, AlacrittyThemeConfig, "cursor = '#9dcbfb'")
+ assert.Contains(t, AlacrittyThemeConfig, "[colors.normal]")
+ assert.Contains(t, AlacrittyThemeConfig, "[colors.bright]")
+}
+
+func TestKittyConfigDeployment(t *testing.T) {
+ tempDir, err := os.MkdirTemp("", "dankinstall-kitty-test")
+ require.NoError(t, err)
+ defer os.RemoveAll(tempDir)
+
+ originalHome := os.Getenv("HOME")
+ os.Setenv("HOME", tempDir)
+ defer os.Setenv("HOME", originalHome)
+
+ logChan := make(chan string, 100)
+ cd := NewConfigDeployer(logChan)
+
+ t.Run("deploy kitty config to empty directory", func(t *testing.T) {
+ results, err := cd.deployKittyConfig()
+ require.NoError(t, err)
+ require.Len(t, results, 3)
+
+ mainResult := results[0]
+ assert.Equal(t, "Kitty", mainResult.ConfigType)
+ assert.True(t, mainResult.Deployed)
+ assert.FileExists(t, mainResult.Path)
+
+ content, err := os.ReadFile(mainResult.Path)
+ require.NoError(t, err)
+ assert.Contains(t, string(content), "include dank-theme.conf")
+
+ themeResult := results[1]
+ assert.Equal(t, "Kitty Theme", themeResult.ConfigType)
+ assert.True(t, themeResult.Deployed)
+ assert.FileExists(t, themeResult.Path)
+
+ tabsResult := results[2]
+ assert.Equal(t, "Kitty Tabs", tabsResult.ConfigType)
+ assert.True(t, tabsResult.Deployed)
+ assert.FileExists(t, tabsResult.Path)
+ })
+}
+
+func TestAlacrittyConfigDeployment(t *testing.T) {
+ tempDir, err := os.MkdirTemp("", "dankinstall-alacritty-test")
+ require.NoError(t, err)
+ defer os.RemoveAll(tempDir)
+
+ originalHome := os.Getenv("HOME")
+ os.Setenv("HOME", tempDir)
+ defer os.Setenv("HOME", originalHome)
+
+ logChan := make(chan string, 100)
+ cd := NewConfigDeployer(logChan)
+
+ t.Run("deploy alacritty config to empty directory", func(t *testing.T) {
+ results, err := cd.deployAlacrittyConfig()
+ require.NoError(t, err)
+ require.Len(t, results, 2)
+
+ mainResult := results[0]
+ assert.Equal(t, "Alacritty", mainResult.ConfigType)
+ assert.True(t, mainResult.Deployed)
+ assert.FileExists(t, mainResult.Path)
+
+ content, err := os.ReadFile(mainResult.Path)
+ require.NoError(t, err)
+ assert.Contains(t, string(content), "~/.config/alacritty/dank-theme.toml")
+ assert.Contains(t, string(content), "[window]")
+
+ themeResult := results[1]
+ assert.Equal(t, "Alacritty Theme", themeResult.ConfigType)
+ assert.True(t, themeResult.Deployed)
+ assert.FileExists(t, themeResult.Path)
+
+ themeContent, err := os.ReadFile(themeResult.Path)
+ require.NoError(t, err)
+ assert.Contains(t, string(themeContent), "[colors.primary]")
+ assert.Contains(t, string(themeContent), "background = '#101418'")
+ })
+
+ t.Run("deploy alacritty config with existing file", func(t *testing.T) {
+ existingContent := "# Old alacritty config\n[window]\nopacity = 0.9\n"
+ alacrittyPath := filepath.Join(tempDir, ".config", "alacritty", "alacritty.toml")
+ err := os.MkdirAll(filepath.Dir(alacrittyPath), 0o755)
+ require.NoError(t, err)
+ err = os.WriteFile(alacrittyPath, []byte(existingContent), 0o644)
+ require.NoError(t, err)
+
+ results, err := cd.deployAlacrittyConfig()
+ require.NoError(t, err)
+ require.Len(t, results, 2)
+
+ mainResult := results[0]
+ assert.True(t, mainResult.Deployed)
+ assert.NotEmpty(t, mainResult.BackupPath)
+ assert.FileExists(t, mainResult.BackupPath)
+
+ backupContent, err := os.ReadFile(mainResult.BackupPath)
+ require.NoError(t, err)
+ assert.Equal(t, existingContent, string(backupContent))
+
+ newContent, err := os.ReadFile(mainResult.Path)
+ require.NoError(t, err)
+ assert.NotContains(t, string(newContent), "# Old alacritty config")
+ assert.Contains(t, string(newContent), "decorations = \"None\"")
+ })
+}
+
+func TestShouldReplaceConfigDeployIfMissing(t *testing.T) {
+ allFalse := map[string]bool{
+ "Niri": false,
+ "Hyprland": false,
+ "Ghostty": false,
+ "Kitty": false,
+ "Alacritty": false,
+ }
+
+ t.Run("replaceConfigs nil deploys config", func(t *testing.T) {
+ tempDir, err := os.MkdirTemp("", "dankinstall-replace-nil-test")
+ require.NoError(t, err)
+ defer os.RemoveAll(tempDir)
+
+ originalHome := os.Getenv("HOME")
+ os.Setenv("HOME", tempDir)
+ defer os.Setenv("HOME", originalHome)
+
+ logChan := make(chan string, 100)
+ cd := NewConfigDeployer(logChan)
+
+ results, err := cd.DeployConfigurationsSelectiveWithReinstalls(
+ context.Background(),
+ deps.WindowManagerNiri,
+ deps.TerminalGhostty,
+ nil, // installedDeps
+ nil, // replaceConfigs
+ nil, // reinstallItems
+ )
+ require.NoError(t, err)
+
+ // With replaceConfigs=nil, all configs should be deployed
+ hasDeployed := false
+ for _, r := range results {
+ if r.Deployed {
+ hasDeployed = true
+ break
+ }
+ }
+ assert.True(t, hasDeployed, "expected at least one config to be deployed when replaceConfigs is nil")
+ })
+
+ t.Run("replaceConfigs all false and config missing deploys config", func(t *testing.T) {
+ tempDir, err := os.MkdirTemp("", "dankinstall-replace-missing-test")
+ require.NoError(t, err)
+ defer os.RemoveAll(tempDir)
+
+ originalHome := os.Getenv("HOME")
+ os.Setenv("HOME", tempDir)
+ defer os.Setenv("HOME", originalHome)
+
+ logChan := make(chan string, 100)
+ cd := NewConfigDeployer(logChan)
+
+ results, err := cd.DeployConfigurationsSelectiveWithReinstalls(
+ context.Background(),
+ deps.WindowManagerNiri,
+ deps.TerminalGhostty,
+ nil, // installedDeps
+ allFalse, // replaceConfigs — all false
+ nil, // reinstallItems
+ )
+ require.NoError(t, err)
+
+ // Config files don't exist on disk, so they should still be deployed
+ hasDeployed := false
+ for _, r := range results {
+ if r.Deployed {
+ hasDeployed = true
+ break
+ }
+ }
+ assert.True(t, hasDeployed, "expected configs to be deployed when files are missing, even with replaceConfigs all false")
+ })
+
+ t.Run("replaceConfigs false and config exists skips config", func(t *testing.T) {
+ tempDir, err := os.MkdirTemp("", "dankinstall-replace-exists-test")
+ require.NoError(t, err)
+ defer os.RemoveAll(tempDir)
+
+ originalHome := os.Getenv("HOME")
+ os.Setenv("HOME", tempDir)
+ defer os.Setenv("HOME", originalHome)
+
+ // Create the Ghostty primary config file so shouldReplaceConfig returns false
+ ghosttyPath := filepath.Join(tempDir, ".config", "ghostty", "config")
+ err = os.MkdirAll(filepath.Dir(ghosttyPath), 0o755)
+ require.NoError(t, err)
+ err = os.WriteFile(ghosttyPath, []byte("# existing ghostty config\n"), 0o644)
+ require.NoError(t, err)
+
+ // Also create the Niri primary config file
+ niriPath := filepath.Join(tempDir, ".config", "niri", "config.kdl")
+ err = os.MkdirAll(filepath.Dir(niriPath), 0o755)
+ require.NoError(t, err)
+ err = os.WriteFile(niriPath, []byte("// existing niri config\n"), 0o644)
+ require.NoError(t, err)
+
+ logChan := make(chan string, 100)
+ cd := NewConfigDeployer(logChan)
+
+ results, err := cd.DeployConfigurationsSelectiveWithReinstalls(
+ context.Background(),
+ deps.WindowManagerNiri,
+ deps.TerminalGhostty,
+ nil, // installedDeps
+ allFalse, // replaceConfigs — all false
+ nil, // reinstallItems
+ )
+ require.NoError(t, err)
+
+ // Both Niri and Ghostty config files exist, so with all false they should be skipped
+ for _, r := range results {
+ assert.Fail(t, "expected no configs to be deployed", "got deployed config: %s", r.ConfigType)
+ }
+ })
+
+ t.Run("replaceConfigs true and config exists deploys config", func(t *testing.T) {
+ tempDir, err := os.MkdirTemp("", "dankinstall-replace-true-test")
+ require.NoError(t, err)
+ defer os.RemoveAll(tempDir)
+
+ originalHome := os.Getenv("HOME")
+ os.Setenv("HOME", tempDir)
+ defer os.Setenv("HOME", originalHome)
+
+ // Create the Ghostty primary config file
+ ghosttyPath := filepath.Join(tempDir, ".config", "ghostty", "config")
+ err = os.MkdirAll(filepath.Dir(ghosttyPath), 0o755)
+ require.NoError(t, err)
+ err = os.WriteFile(ghosttyPath, []byte("# existing ghostty config\n"), 0o644)
+ require.NoError(t, err)
+
+ logChan := make(chan string, 100)
+ cd := NewConfigDeployer(logChan)
+
+ replaceConfigs := map[string]bool{
+ "Niri": false,
+ "Hyprland": false,
+ "Ghostty": true, // explicitly true
+ "Kitty": false,
+ "Alacritty": false,
+ }
+
+ results, err := cd.DeployConfigurationsSelectiveWithReinstalls(
+ context.Background(),
+ deps.WindowManagerNiri,
+ deps.TerminalGhostty,
+ nil, // installedDeps
+ replaceConfigs, // Ghostty=true, rest=false
+ nil, // reinstallItems
+ )
+ require.NoError(t, err)
+
+ // Ghostty should be deployed because replaceConfigs["Ghostty"]=true
+ foundGhostty := false
+ for _, r := range results {
+ if r.ConfigType == "Ghostty" && r.Deployed {
+ foundGhostty = true
+ }
+ }
+ assert.True(t, foundGhostty, "expected Ghostty config to be deployed when replaceConfigs is true")
+ })
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/dms.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/dms.go
new file mode 100644
index 0000000..7842fa5
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/dms.go
@@ -0,0 +1,57 @@
+package config
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+func LocateDMSConfig() (string, error) {
+ var primaryPaths []string
+
+ configHome, err := os.UserConfigDir()
+ if err == nil && configHome != "" {
+ primaryPaths = append(primaryPaths, filepath.Join(configHome, "quickshell", "dms"))
+ }
+
+ // System data directories
+ dataDirs := os.Getenv("XDG_DATA_DIRS")
+ if dataDirs == "" {
+ dataDirs = "/usr/local/share:/usr/share"
+ }
+
+ for dir := range strings.SplitSeq(dataDirs, ":") {
+ if dir != "" {
+ primaryPaths = append(primaryPaths, filepath.Join(dir, "quickshell", "dms"))
+ }
+ }
+
+ // System config directories (fallback)
+ configDirs := os.Getenv("XDG_CONFIG_DIRS")
+ if configDirs == "" {
+ configDirs = "/etc/xdg"
+ }
+
+ for dir := range strings.SplitSeq(configDirs, ":") {
+ if dir != "" {
+ primaryPaths = append(primaryPaths, filepath.Join(dir, "quickshell", "dms"))
+ }
+ }
+
+ // Build search paths with secondary (monorepo) paths interleaved
+ var searchPaths []string
+ for _, path := range primaryPaths {
+ searchPaths = append(searchPaths, path)
+ searchPaths = append(searchPaths, filepath.Join(path, "quickshell"))
+ }
+
+ for _, path := range searchPaths {
+ shellPath := filepath.Join(path, "shell.qml")
+ if info, err := os.Stat(shellPath); err == nil && !info.IsDir() {
+ return path, nil
+ }
+ }
+
+ return "", fmt.Errorf("could not find DMS config (shell.qml) in any valid config path")
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/alacritty-theme.toml b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/alacritty-theme.toml
new file mode 100644
index 0000000..4048457
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/alacritty-theme.toml
@@ -0,0 +1,31 @@
+[colors.primary]
+background = '#101418'
+foreground = '#e0e2e8'
+
+[colors.selection]
+text = '#e0e2e8'
+background = '#124a73'
+
+[colors.cursor]
+text = '#101418'
+cursor = '#9dcbfb'
+
+[colors.normal]
+black = '#101418'
+red = '#d75a59'
+green = '#8ed88c'
+yellow = '#e0d99d'
+blue = '#4087bc'
+magenta = '#839fbc'
+cyan = '#9dcbfb'
+white = '#abb2bf'
+
+[colors.bright]
+black = '#5c6370'
+red = '#e57e7e'
+green = '#a2e5a0'
+yellow = '#efe9b3'
+blue = '#a7d9ff'
+magenta = '#3d8197'
+cyan = '#5c7ba3'
+white = '#ffffff'
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/alacritty.toml b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/alacritty.toml
new file mode 100644
index 0000000..b666177
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/alacritty.toml
@@ -0,0 +1,37 @@
+[general]
+import = [
+ "~/.config/alacritty/dank-theme.toml"
+]
+
+[window]
+decorations = "None"
+padding = { x = 12, y = 12 }
+opacity = 1.0
+
+[scrolling]
+history = 3023
+
+[cursor]
+style = { shape = "Block", blinking = "On" }
+blink_interval = 500
+unfocused_hollow = true
+
+[mouse]
+hide_when_typing = true
+
+[selection]
+save_to_clipboard = false
+
+[bell]
+duration = 0
+
+[keyboard]
+bindings = [
+ { key = "C", mods = "Control|Shift", action = "Copy" },
+ { key = "V", mods = "Control|Shift", action = "Paste" },
+ { key = "N", mods = "Control|Shift", action = "SpawnNewInstance" },
+ { key = "Equals", mods = "Control|Shift", action = "IncreaseFontSize" },
+ { key = "Minus", mods = "Control", action = "DecreaseFontSize" },
+ { key = "Key0", mods = "Control", action = "ResetFontSize" },
+ { key = "Enter", mods = "Shift", chars = "\n" },
+]
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/ghostty-colors.conf b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/ghostty-colors.conf
new file mode 100644
index 0000000..56dca5f
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/ghostty-colors.conf
@@ -0,0 +1,21 @@
+background = #101418
+foreground = #e0e2e8
+cursor-color = #9dcbfb
+selection-background = #124a73
+selection-foreground = #e0e2e8
+palette = 0=#101418
+palette = 1=#d75a59
+palette = 2=#8ed88c
+palette = 3=#e0d99d
+palette = 4=#4087bc
+palette = 5=#839fbc
+palette = 6=#9dcbfb
+palette = 7=#abb2bf
+palette = 8=#5c6370
+palette = 9=#e57e7e
+palette = 10=#a2e5a0
+palette = 11=#efe9b3
+palette = 12=#a7d9ff
+palette = 13=#3d8197
+palette = 14=#5c7ba3
+palette = 15=#ffffff
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/ghostty.conf b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/ghostty.conf
new file mode 100644
index 0000000..8ca05a2
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/ghostty.conf
@@ -0,0 +1,51 @@
+# Font Configuration
+font-size = 12
+
+# Window Configuration
+window-decoration = false
+window-padding-x = 12
+window-padding-y = 12
+background-opacity = 1.0
+background-blur-radius = 32
+
+# Cursor Configuration
+cursor-style = block
+cursor-style-blink = true
+
+# Scrollback
+scrollback-limit = 3023
+
+# Terminal features
+mouse-hide-while-typing = true
+copy-on-select = false
+confirm-close-surface = false
+
+# Disable annoying copied to clipboard
+app-notifications = no-clipboard-copy,no-config-reload
+
+# Key bindings for common actions
+#keybind = ctrl+c=copy_to_clipboard
+#keybind = ctrl+v=paste_from_clipboard
+keybind = ctrl+shift+n=new_window
+keybind = ctrl+t=new_tab
+keybind = ctrl+plus=increase_font_size:1
+keybind = ctrl+minus=decrease_font_size:1
+keybind = ctrl+zero=reset_font_size
+
+# Material 3 UI elements
+unfocused-split-opacity = 0.7
+unfocused-split-fill = #44464f
+
+# Tab configuration
+gtk-titlebar = false
+
+# Shell integration
+shell-integration = detect
+shell-integration-features = cursor,sudo,title,no-cursor
+keybind = shift+enter=text:\n
+
+# Rando stuff
+gtk-single-instance = true
+
+# Dank color generation
+theme = dankcolors
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hypr-binds.conf b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hypr-binds.conf
new file mode 100644
index 0000000..dfe5630
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hypr-binds.conf
@@ -0,0 +1,162 @@
+# === Application Launchers ===
+bind = SUPER, T, exec, {{TERMINAL_COMMAND}}
+bind = SUPER, space, exec, dms ipc call spotlight toggle
+bind = SUPER, V, exec, dms ipc call clipboard toggle
+bind = SUPER, M, exec, dms ipc call processlist focusOrToggle
+bind = SUPER, comma, exec, dms ipc call settings focusOrToggle
+bind = SUPER, N, exec, dms ipc call notifications toggle
+bind = SUPER SHIFT, N, exec, dms ipc call notepad toggle
+bind = SUPER, Y, exec, dms ipc call dankdash wallpaper
+bind = SUPER, TAB, exec, dms ipc call hypr toggleOverview
+bind = SUPER, X, exec, dms ipc call powermenu toggle
+
+# === Cheat sheet
+bind = SUPER SHIFT, Slash, exec, dms ipc call keybinds toggle hyprland
+
+# === Security ===
+bind = SUPER ALT, L, exec, dms ipc call lock lock
+bind = SUPER SHIFT, E, exit
+bind = CTRL ALT, Delete, exec, dms ipc call processlist focusOrToggle
+
+# === Audio Controls ===
+bindel = , XF86AudioRaiseVolume, exec, dms ipc call audio increment 3
+bindel = , XF86AudioLowerVolume, exec, dms ipc call audio decrement 3
+bindl = , XF86AudioMute, exec, dms ipc call audio mute
+bindl = , XF86AudioMicMute, exec, dms ipc call audio micmute
+bindl = , XF86AudioPause, exec, dms ipc call mpris playPause
+bindl = , XF86AudioPlay, exec, dms ipc call mpris playPause
+bindl = , XF86AudioPrev, exec, dms ipc call mpris previous
+bindl = , XF86AudioNext, exec, dms ipc call mpris next
+bindel = CTRL, XF86AudioRaiseVolume, exec, dms ipc call mpris increment 3
+bindel = CTRL, XF86AudioLowerVolume, exec, dms ipc call mpris decrement 3
+
+# === Brightness Controls ===
+bindel = , XF86MonBrightnessUp, exec, dms ipc call brightness increment 5 ""
+bindel = , XF86MonBrightnessDown, exec, dms ipc call brightness decrement 5 ""
+
+# === Window Management ===
+bind = SUPER, Q, killactive
+bind = SUPER, F, fullscreen, 1
+bind = SUPER SHIFT, F, fullscreen, 0
+bind = SUPER SHIFT, T, togglefloating
+bind = SUPER, W, togglegroup
+bind = SUPER SHIFT, W, exec, dms ipc call window-rules toggle
+
+# === Focus Navigation ===
+bind = SUPER, left, movefocus, l
+bind = SUPER, down, movefocus, d
+bind = SUPER, up, movefocus, u
+bind = SUPER, right, movefocus, r
+bind = SUPER, H, movefocus, l
+bind = SUPER, J, movefocus, d
+bind = SUPER, K, movefocus, u
+bind = SUPER, L, movefocus, r
+
+# === Window Movement ===
+bind = SUPER SHIFT, left, movewindow, l
+bind = SUPER SHIFT, down, movewindow, d
+bind = SUPER SHIFT, up, movewindow, u
+bind = SUPER SHIFT, right, movewindow, r
+bind = SUPER SHIFT, H, movewindow, l
+bind = SUPER SHIFT, J, movewindow, d
+bind = SUPER SHIFT, K, movewindow, u
+bind = SUPER SHIFT, L, movewindow, r
+
+# === Column Navigation ===
+bind = SUPER, Home, focuswindow, first
+bind = SUPER, End, focuswindow, last
+
+# === Monitor Navigation ===
+bind = SUPER CTRL, left, focusmonitor, l
+bind = SUPER CTRL, right, focusmonitor, r
+bind = SUPER CTRL, H, focusmonitor, l
+bind = SUPER CTRL, J, focusmonitor, d
+bind = SUPER CTRL, K, focusmonitor, u
+bind = SUPER CTRL, L, focusmonitor, r
+
+# === Move to Monitor ===
+bind = SUPER SHIFT CTRL, left, movewindow, mon:l
+bind = SUPER SHIFT CTRL, down, movewindow, mon:d
+bind = SUPER SHIFT CTRL, up, movewindow, mon:u
+bind = SUPER SHIFT CTRL, right, movewindow, mon:r
+bind = SUPER SHIFT CTRL, H, movewindow, mon:l
+bind = SUPER SHIFT CTRL, J, movewindow, mon:d
+bind = SUPER SHIFT CTRL, K, movewindow, mon:u
+bind = SUPER SHIFT CTRL, L, movewindow, mon:r
+
+# === Workspace Navigation ===
+bind = SUPER, Page_Down, workspace, e+1
+bind = SUPER, Page_Up, workspace, e-1
+bind = SUPER, U, workspace, e+1
+bind = SUPER, I, workspace, e-1
+bind = SUPER CTRL, down, movetoworkspace, e+1
+bind = SUPER CTRL, up, movetoworkspace, e-1
+bind = SUPER CTRL, U, movetoworkspace, e+1
+bind = SUPER CTRL, I, movetoworkspace, e-1
+
+# === Workspace Management ===
+bind = CTRL SHIFT, R, exec, dms ipc call workspace-rename open
+
+# === Move Workspaces ===
+bind = SUPER SHIFT, Page_Down, movetoworkspace, e+1
+bind = SUPER SHIFT, Page_Up, movetoworkspace, e-1
+bind = SUPER SHIFT, U, movetoworkspace, e+1
+bind = SUPER SHIFT, I, movetoworkspace, e-1
+
+# === Mouse Wheel Navigation ===
+bind = SUPER, mouse_down, workspace, e+1
+bind = SUPER, mouse_up, workspace, e-1
+bind = SUPER CTRL, mouse_down, movetoworkspace, e+1
+bind = SUPER CTRL, mouse_up, movetoworkspace, e-1
+
+# === Numbered Workspaces ===
+bind = SUPER, 1, workspace, 1
+bind = SUPER, 2, workspace, 2
+bind = SUPER, 3, workspace, 3
+bind = SUPER, 4, workspace, 4
+bind = SUPER, 5, workspace, 5
+bind = SUPER, 6, workspace, 6
+bind = SUPER, 7, workspace, 7
+bind = SUPER, 8, workspace, 8
+bind = SUPER, 9, workspace, 9
+
+# === Move to Numbered Workspaces ===
+bind = SUPER SHIFT, 1, movetoworkspace, 1
+bind = SUPER SHIFT, 2, movetoworkspace, 2
+bind = SUPER SHIFT, 3, movetoworkspace, 3
+bind = SUPER SHIFT, 4, movetoworkspace, 4
+bind = SUPER SHIFT, 5, movetoworkspace, 5
+bind = SUPER SHIFT, 6, movetoworkspace, 6
+bind = SUPER SHIFT, 7, movetoworkspace, 7
+bind = SUPER SHIFT, 8, movetoworkspace, 8
+bind = SUPER SHIFT, 9, movetoworkspace, 9
+
+# === Column Management ===
+bind = SUPER, bracketleft, layoutmsg, preselect l
+bind = SUPER, bracketright, layoutmsg, preselect r
+
+# === Sizing & Layout ===
+bind = SUPER, R, layoutmsg, togglesplit
+bind = SUPER CTRL, F, resizeactive, exact 100% 100%
+
+# === Move/resize windows with mainMod + LMB/RMB and dragging ===
+bindmd = SUPER, mouse:272, Move window, movewindow
+bindmd = SUPER, mouse:273, Resize window, resizewindow
+
+# === Move/resize windows with mainMod + LMB/RMB and dragging ===
+bindd = SUPER, code:20, Expand window left, resizeactive, -100 0
+bindd = SUPER, code:21, Shrink window left, resizeactive, 100 0
+
+# === Manual Sizing ===
+binde = SUPER, minus, resizeactive, -10% 0
+binde = SUPER, equal, resizeactive, 10% 0
+binde = SUPER SHIFT, minus, resizeactive, 0 -10%
+binde = SUPER SHIFT, equal, resizeactive, 0 10%
+
+# === Screenshots ===
+bind = , Print, exec, dms screenshot
+bind = CTRL, Print, exec, dms screenshot full
+bind = ALT, Print, exec, dms screenshot window
+
+# === System Controls ===
+bind = SUPER SHIFT, P, dpms, toggle
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hypr-colors.conf b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hypr-colors.conf
new file mode 100644
index 0000000..38bcb0e
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hypr-colors.conf
@@ -0,0 +1,25 @@
+# ! Auto-generated file. Do not edit directly.
+# Remove source = ./dms/colors.conf from your config to override.
+
+$primary = rgb(d0bcff)
+$outline = rgb(948f99)
+$error = rgb(f2b8b5)
+
+general {
+ col.active_border = $primary
+ col.inactive_border = $outline
+}
+
+group {
+ col.border_active = $primary
+ col.border_inactive = $outline
+ col.border_locked_active = $error
+ col.border_locked_inactive = $outline
+
+ groupbar {
+ col.active = $primary
+ col.inactive = $outline
+ col.locked_active = $error
+ col.locked_inactive = $outline
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hypr-layout.conf b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hypr-layout.conf
new file mode 100644
index 0000000..b9a8ad9
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hypr-layout.conf
@@ -0,0 +1,11 @@
+# Auto-generated by DMS - do not edit manually
+
+general {
+ gaps_in = 4
+ gaps_out = 4
+ border_size = 2
+}
+
+decoration {
+ rounding = 12
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hyprland.conf b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hyprland.conf
new file mode 100644
index 0000000..f4e7a03
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/hyprland.conf
@@ -0,0 +1,121 @@
+# Hyprland Configuration
+# https://wiki.hypr.land/Configuring/
+
+# ==================
+# MONITOR CONFIG
+# ==================
+# monitor = eDP-2, 2560x1600@239.998993, 2560x0, 1, vrr, 1
+monitor = , preferred,auto,auto
+
+# ==================
+# STARTUP APPS
+# ==================
+exec-once = dbus-update-activation-environment --systemd --all
+exec-once = systemctl --user start hyprland-session.target
+
+# ==================
+# INPUT CONFIG
+# ==================
+input {
+ kb_layout = us
+ numlock_by_default = true
+}
+
+# ==================
+# GENERAL LAYOUT
+# ==================
+general {
+ gaps_in = 5
+ gaps_out = 5
+ border_size = 2
+
+ layout = dwindle
+}
+
+# ==================
+# DECORATION
+# ==================
+decoration {
+ rounding = 12
+
+ active_opacity = 1.0
+ inactive_opacity = 1.0
+
+ shadow {
+ enabled = true
+ range = 30
+ render_power = 5
+ offset = 0 5
+ color = rgba(00000070)
+ }
+}
+
+# ==================
+# ANIMATIONS
+# ==================
+animations {
+ enabled = true
+
+ animation = windowsIn, 1, 3, default
+ animation = windowsOut, 1, 3, default
+ animation = workspaces, 1, 5, default
+ animation = windowsMove, 1, 4, default
+ animation = fade, 1, 3, default
+ animation = border, 1, 3, default
+}
+
+# ==================
+# LAYOUTS
+# ==================
+dwindle {
+ preserve_split = true
+}
+
+master {
+ mfact = 0.5
+}
+
+# ==================
+# MISC
+# ==================
+misc {
+ disable_hyprland_logo = true
+ disable_splash_rendering = true
+}
+
+# ==================
+# WINDOW RULES
+# ==================
+windowrule = tile on, match:class ^(org\.wezfurlong\.wezterm)$
+
+windowrule = rounding 12, match:class ^(org\.gnome\.)
+
+windowrule = tile on, match:class ^(gnome-control-center)$
+windowrule = tile on, match:class ^(pavucontrol)$
+windowrule = tile on, match:class ^(nm-connection-editor)$
+
+windowrule = float on, match:class ^(org\.gnome\.Calculator)$
+windowrule = float on, match:class ^(gnome-calculator)$
+windowrule = float on, match:class ^(galculator)$
+windowrule = float on, match:class ^(blueman-manager)$
+windowrule = float on, match:class ^(org\.gnome\.Nautilus)$
+windowrule = float on, match:class ^(xdg-desktop-portal)$
+
+windowrule = no_initial_focus on, match:class ^(steam)$, match:title ^(notificationtoasts)
+windowrule = pin on, match:class ^(steam)$, match:title ^(notificationtoasts)
+
+windowrule = float on, match:class ^(firefox)$, match:title ^(Picture-in-Picture)$
+windowrule = float on, match:class ^(zoom)$
+
+# DMS windows floating by default
+# ! Hyprland doesn't size these windows correctly so disabling by default here
+# windowrule = float on, match:class ^(org.quickshell)$
+
+layerrule = no_anim on, match:namespace ^(quickshell)$
+layerrule = no_anim on, match:namespace ^dms:.*
+
+source = ./dms/colors.conf
+source = ./dms/outputs.conf
+source = ./dms/layout.conf
+source = ./dms/cursor.conf
+source = ./dms/binds.conf
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/kitty-tabs.conf b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/kitty-tabs.conf
new file mode 100644
index 0000000..b2305c8
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/kitty-tabs.conf
@@ -0,0 +1,24 @@
+tab_bar_edge top
+tab_bar_style powerline
+tab_powerline_style slanted
+tab_bar_align left
+tab_bar_min_tabs 2
+tab_bar_margin_width 0.0
+tab_bar_margin_height 2.5 1.5
+tab_bar_margin_color #101418
+
+tab_bar_background #101418
+
+active_tab_foreground #cfe5ff
+active_tab_background #124a73
+active_tab_font_style bold
+
+inactive_tab_foreground #c2c7cf
+inactive_tab_background #101418
+inactive_tab_font_style normal
+
+tab_activity_symbol " ● "
+tab_numbers_style 1
+
+tab_title_template "{fmt.fg.red}{bell_symbol}{activity_symbol}{fmt.fg.tab}{title[:30]}{title[30:] and '…'} [{index}]"
+active_tab_title_template "{fmt.fg.red}{bell_symbol}{activity_symbol}{fmt.fg.tab}{title[:30]}{title[30:] and '…'} [{index}]"
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/kitty-theme.conf b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/kitty-theme.conf
new file mode 100644
index 0000000..2587bcf
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/kitty-theme.conf
@@ -0,0 +1,24 @@
+cursor #e0e2e8
+cursor_text_color #c2c7cf
+
+foreground #e0e2e8
+background #101418
+selection_foreground #243240
+selection_background #b9c8da
+url_color #9dcbfb
+color0 #101418
+color1 #d75a59
+color2 #8ed88c
+color3 #e0d99d
+color4 #4087bc
+color5 #839fbc
+color6 #9dcbfb
+color7 #abb2bf
+color8 #5c6370
+color9 #e57e7e
+color10 #a2e5a0
+color11 #efe9b3
+color12 #a7d9ff
+color13 #3d8197
+color14 #5c7ba3
+color15 #ffffff
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/kitty.conf b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/kitty.conf
new file mode 100644
index 0000000..2ae96a0
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/kitty.conf
@@ -0,0 +1,37 @@
+# Font Configuration
+font_size 12.0
+
+# Window Configuration
+window_padding_width 12
+background_opacity 1.0
+background_blur 32
+hide_window_decorations yes
+
+# Cursor Configuration
+cursor_shape block
+cursor_blink_interval 1
+
+# Scrollback
+scrollback_lines 3000
+
+# Terminal features
+copy_on_select yes
+strip_trailing_spaces smart
+
+# Key bindings for common actions
+map ctrl+shift+n new_window
+map ctrl+t new_tab
+map ctrl+plus change_font_size all +1.0
+map ctrl+minus change_font_size all -1.0
+map ctrl+0 change_font_size all 0
+
+# Tab configuration
+tab_bar_style powerline
+tab_bar_align left
+
+# Shell integration
+shell_integration enabled
+
+# Dank color generation
+include dank-tabs.conf
+include dank-theme.conf
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-alttab.kdl b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-alttab.kdl
new file mode 100644
index 0000000..5f9bdcd
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-alttab.kdl
@@ -0,0 +1,10 @@
+// ! DO NOT EDIT !
+// ! AUTO-GENERATED BY DMS !
+// ! CHANGES WILL BE OVERWRITTEN !
+// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
+
+recent-windows {
+ highlight {
+ corner-radius 12
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-binds.kdl b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-binds.kdl
new file mode 100644
index 0000000..27cb860
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-binds.kdl
@@ -0,0 +1,221 @@
+binds {
+ // === System & Overview ===
+ Mod+D repeat=false { toggle-overview; }
+ Mod+Tab repeat=false { toggle-overview; }
+ Mod+Shift+Slash { show-hotkey-overlay; }
+
+ // === Application Launchers ===
+ Mod+T hotkey-overlay-title="Open Terminal" { spawn "{{TERMINAL_COMMAND}}"; }
+ Mod+Space hotkey-overlay-title="Application Launcher" {
+ spawn "dms" "ipc" "call" "spotlight" "toggle";
+ }
+ Mod+V hotkey-overlay-title="Clipboard Manager" {
+ spawn "dms" "ipc" "call" "clipboard" "toggle";
+ }
+ Mod+M hotkey-overlay-title="Task Manager" {
+ spawn "dms" "ipc" "call" "processlist" "focusOrToggle";
+ }
+
+ Super+X hotkey-overlay-title="Power Menu: Toggle" { spawn "dms" "ipc" "call" "powermenu" "toggle"; }
+ Mod+Comma hotkey-overlay-title="Settings" {
+ spawn "dms" "ipc" "call" "settings" "focusOrToggle";
+ }
+ Mod+Y hotkey-overlay-title="Browse Wallpapers" {
+ spawn "dms" "ipc" "call" "dankdash" "wallpaper";
+ }
+ Mod+N hotkey-overlay-title="Notification Center" { spawn "dms" "ipc" "call" "notifications" "toggle"; }
+ Mod+Shift+N hotkey-overlay-title="Notepad" { spawn "dms" "ipc" "call" "notepad" "toggle"; }
+
+ // === Security ===
+ Mod+Alt+L hotkey-overlay-title="Lock Screen" {
+ spawn "dms" "ipc" "call" "lock" "lock";
+ }
+ Mod+Shift+E { quit; }
+ Ctrl+Alt+Delete hotkey-overlay-title="Task Manager" {
+ spawn "dms" "ipc" "call" "processlist" "focusOrToggle";
+ }
+
+ // === Audio Controls ===
+ XF86AudioRaiseVolume allow-when-locked=true {
+ spawn "dms" "ipc" "call" "audio" "increment" "3";
+ }
+ XF86AudioLowerVolume allow-when-locked=true {
+ spawn "dms" "ipc" "call" "audio" "decrement" "3";
+ }
+ XF86AudioMute allow-when-locked=true {
+ spawn "dms" "ipc" "call" "audio" "mute";
+ }
+ XF86AudioMicMute allow-when-locked=true {
+ spawn "dms" "ipc" "call" "audio" "micmute";
+ }
+ XF86AudioPause allow-when-locked=true {
+ spawn "dms" "ipc" "call" "mpris" "playPause";
+ }
+ XF86AudioPlay allow-when-locked=true {
+ spawn "dms" "ipc" "call" "mpris" "playPause";
+ }
+ XF86AudioPrev allow-when-locked=true {
+ spawn "dms" "ipc" "call" "mpris" "previous";
+ }
+ XF86AudioNext allow-when-locked=true {
+ spawn "dms" "ipc" "call" "mpris" "next";
+ }
+ Ctrl+XF86AudioRaiseVolume allow-when-locked=true {
+ spawn "dms" "ipc" "call" "mpris" "increment" "3";
+ }
+ Ctrl+XF86AudioLowerVolume allow-when-locked=true {
+ spawn "dms" "ipc" "call" "mpris" "decrement" "3";
+ }
+
+ // === Brightness Controls ===
+ XF86MonBrightnessUp allow-when-locked=true {
+ spawn "dms" "ipc" "call" "brightness" "increment" "5" "";
+ }
+ XF86MonBrightnessDown allow-when-locked=true {
+ spawn "dms" "ipc" "call" "brightness" "decrement" "5" "";
+ }
+
+ // === Window Management ===
+ Mod+Q repeat=false { close-window; }
+ Mod+F { maximize-column; }
+ Mod+Shift+F { fullscreen-window; }
+ Mod+Shift+T { toggle-window-floating; }
+ Mod+Shift+V { switch-focus-between-floating-and-tiling; }
+ Mod+W { toggle-column-tabbed-display; }
+ Mod+Shift+W hotkey-overlay-title="Create window rule" { spawn "dms" "ipc" "call" "window-rules" "toggle"; }
+
+ // === Focus Navigation ===
+ Mod+Left { focus-column-left; }
+ Mod+Down { focus-window-down; }
+ Mod+Up { focus-window-up; }
+ Mod+Right { focus-column-right; }
+ Mod+H { focus-column-left; }
+ Mod+J { focus-window-down; }
+ Mod+K { focus-window-up; }
+ Mod+L { focus-column-right; }
+
+ // === Window Movement ===
+ Mod+Shift+Left { move-column-left; }
+ Mod+Shift+Down { move-window-down; }
+ Mod+Shift+Up { move-window-up; }
+ Mod+Shift+Right { move-column-right; }
+ Mod+Shift+H { move-column-left; }
+ Mod+Shift+J { move-window-down; }
+ Mod+Shift+K { move-window-up; }
+ Mod+Shift+L { move-column-right; }
+
+ // === Column Navigation ===
+ Mod+Home { focus-column-first; }
+ Mod+End { focus-column-last; }
+ Mod+Ctrl+Home { move-column-to-first; }
+ Mod+Ctrl+End { move-column-to-last; }
+
+ // === Monitor Navigation ===
+ Mod+Ctrl+Left { focus-monitor-left; }
+ //Mod+Ctrl+Down { focus-monitor-down; }
+ //Mod+Ctrl+Up { focus-monitor-up; }
+ Mod+Ctrl+Right { focus-monitor-right; }
+ Mod+Ctrl+H { focus-monitor-left; }
+ Mod+Ctrl+J { focus-monitor-down; }
+ Mod+Ctrl+K { focus-monitor-up; }
+ Mod+Ctrl+L { focus-monitor-right; }
+
+ // === Move to Monitor ===
+ Mod+Shift+Ctrl+Left { move-column-to-monitor-left; }
+ Mod+Shift+Ctrl+Down { move-column-to-monitor-down; }
+ Mod+Shift+Ctrl+Up { move-column-to-monitor-up; }
+ Mod+Shift+Ctrl+Right { move-column-to-monitor-right; }
+ Mod+Shift+Ctrl+H { move-column-to-monitor-left; }
+ Mod+Shift+Ctrl+J { move-column-to-monitor-down; }
+ Mod+Shift+Ctrl+K { move-column-to-monitor-up; }
+ Mod+Shift+Ctrl+L { move-column-to-monitor-right; }
+
+ // === Workspace Navigation ===
+ Mod+Page_Down { focus-workspace-down; }
+ Mod+Page_Up { focus-workspace-up; }
+ Mod+U { focus-workspace-down; }
+ Mod+I { focus-workspace-up; }
+ Mod+Ctrl+Down { move-column-to-workspace-down; }
+ Mod+Ctrl+Up { move-column-to-workspace-up; }
+ Mod+Ctrl+U { move-column-to-workspace-down; }
+ Mod+Ctrl+I { move-column-to-workspace-up; }
+
+ // === Workspace Management ===
+ Ctrl+Shift+R hotkey-overlay-title="Rename Workspace" {
+ spawn "dms" "ipc" "call" "workspace-rename" "open";
+ }
+
+ // === Move Workspaces ===
+ Mod+Shift+Page_Down { move-workspace-down; }
+ Mod+Shift+Page_Up { move-workspace-up; }
+ Mod+Shift+U { move-workspace-down; }
+ Mod+Shift+I { move-workspace-up; }
+
+ // === Mouse Wheel Navigation ===
+ Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
+ Mod+WheelScrollUp cooldown-ms=150 { focus-workspace-up; }
+ Mod+Ctrl+WheelScrollDown cooldown-ms=150 { move-column-to-workspace-down; }
+ Mod+Ctrl+WheelScrollUp cooldown-ms=150 { move-column-to-workspace-up; }
+
+ Mod+WheelScrollRight { focus-column-right; }
+ Mod+WheelScrollLeft { focus-column-left; }
+ Mod+Ctrl+WheelScrollRight { move-column-right; }
+ Mod+Ctrl+WheelScrollLeft { move-column-left; }
+
+ Mod+Shift+WheelScrollDown { focus-column-right; }
+ Mod+Shift+WheelScrollUp { focus-column-left; }
+ Mod+Ctrl+Shift+WheelScrollDown { move-column-right; }
+ Mod+Ctrl+Shift+WheelScrollUp { move-column-left; }
+
+ // === Numbered Workspaces ===
+ Mod+1 { focus-workspace 1; }
+ Mod+2 { focus-workspace 2; }
+ Mod+3 { focus-workspace 3; }
+ Mod+4 { focus-workspace 4; }
+ Mod+5 { focus-workspace 5; }
+ Mod+6 { focus-workspace 6; }
+ Mod+7 { focus-workspace 7; }
+ Mod+8 { focus-workspace 8; }
+ Mod+9 { focus-workspace 9; }
+
+ // === Move to Numbered Workspaces ===
+ Mod+Shift+1 { move-column-to-workspace 1; }
+ Mod+Shift+2 { move-column-to-workspace 2; }
+ Mod+Shift+3 { move-column-to-workspace 3; }
+ Mod+Shift+4 { move-column-to-workspace 4; }
+ Mod+Shift+5 { move-column-to-workspace 5; }
+ Mod+Shift+6 { move-column-to-workspace 6; }
+ Mod+Shift+7 { move-column-to-workspace 7; }
+ Mod+Shift+8 { move-column-to-workspace 8; }
+ Mod+Shift+9 { move-column-to-workspace 9; }
+
+ // === Column Management ===
+ Mod+BracketLeft { consume-or-expel-window-left; }
+ Mod+BracketRight { consume-or-expel-window-right; }
+ Mod+Period { expel-window-from-column; }
+
+ // === Sizing & Layout ===
+ Mod+R { switch-preset-column-width; }
+ Mod+Shift+R { switch-preset-window-height; }
+ Mod+Ctrl+R { reset-window-height; }
+ Mod+Ctrl+F { expand-column-to-available-width; }
+ Mod+C { center-column; }
+ Mod+Ctrl+C { center-visible-columns; }
+
+ // === Manual Sizing ===
+ Mod+Minus { set-column-width "-10%"; }
+ Mod+Equal { set-column-width "+10%"; }
+ Mod+Shift+Minus { set-window-height "-10%"; }
+ Mod+Shift+Equal { set-window-height "+10%"; }
+
+ // === Screenshots ===
+ XF86Launch1 { screenshot; }
+ Ctrl+XF86Launch1 { screenshot-screen; }
+ Alt+XF86Launch1 { screenshot-window; }
+ Print { screenshot; }
+ Ctrl+Print { screenshot-screen; }
+ Alt+Print { screenshot-window; }
+ // === System Controls ===
+ Mod+Escape allow-inhibiting=false { toggle-keyboard-shortcuts-inhibit; }
+ Mod+Shift+P { power-off-monitors; }
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-colors.kdl b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-colors.kdl
new file mode 100644
index 0000000..145a179
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-colors.kdl
@@ -0,0 +1,39 @@
+// ! Auto-generated file. Do not edit directly.
+// Remove `include "dms/colors.kdl"` from your config to override.
+
+layout {
+ background-color "transparent"
+
+ focus-ring {
+ active-color "#d0bcff"
+ inactive-color "#948f99"
+ urgent-color "#f2b8b5"
+ }
+
+ border {
+ active-color "#d0bcff"
+ inactive-color "#948f99"
+ urgent-color "#f2b8b5"
+ }
+
+ shadow {
+ color "#00000070"
+ }
+
+ tab-indicator {
+ active-color "#d0bcff"
+ inactive-color "#948f99"
+ urgent-color "#f2b8b5"
+ }
+
+ insert-hint {
+ color "#d0bcff80"
+ }
+}
+
+recent-windows {
+ highlight {
+ active-color "#4f378b"
+ urgent-color "#f2b8b5"
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-greeter.kdl b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-greeter.kdl
new file mode 100644
index 0000000..5653a16
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-greeter.kdl
@@ -0,0 +1,17 @@
+hotkey-overlay {
+ skip-at-startup
+}
+
+environment {
+ DMS_RUN_GREETER "1"
+}
+
+gestures {
+ hot-corners {
+ off
+ }
+}
+
+layout {
+ background-color "#000000"
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-layout.kdl b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-layout.kdl
new file mode 100644
index 0000000..1951500
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri-layout.kdl
@@ -0,0 +1,22 @@
+// ! DO NOT EDIT !
+// ! AUTO-GENERATED BY DMS !
+// ! CHANGES WILL BE OVERWRITTEN !
+// ! PLACE YOUR CUSTOM CONFIGURATION ELSEWHERE !
+
+layout {
+ gaps 4
+
+ border {
+ width 2
+ }
+
+ focus-ring {
+ width 2
+ }
+}
+window-rule {
+ geometry-corner-radius 12
+ clip-to-geometry true
+ tiled-state true
+ draw-border-with-background false
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri.kdl b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri.kdl
new file mode 100644
index 0000000..0a759e4
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/niri.kdl
@@ -0,0 +1,279 @@
+// This config is in the KDL format: https://kdl.dev
+// "/-" comments out the following node.
+// Check the wiki for a full description of the configuration:
+// https://github.com/YaLTeR/niri/wiki/Configuration:-Introduction
+config-notification {
+ disable-failed
+}
+
+gestures {
+ hot-corners {
+ off
+ }
+}
+
+// Input device configuration.
+// Find the full list of options on the wiki:
+// https://github.com/YaLTeR/niri/wiki/Configuration:-Input
+input {
+ keyboard {
+ xkb {
+ // You can set rules, model, layout, variant and options.
+ // For more information, see xkeyboard-config(7).
+
+ // For example:
+ // layout "us,ru"
+ // options "grp:win_space_toggle,compose:ralt,ctrl:nocaps"
+
+ // If this section is empty, niri will fetch xkb settings
+ // from org.freedesktop.locale1. You can control these using
+ // localectl set-x11-keymap.
+ }
+
+ // Enable numlock on startup, omitting this setting disables it.
+ numlock
+ }
+
+ // Next sections include libinput settings.
+ // Omitting settings disables them, or leaves them at their default values.
+ // All commented-out settings here are examples, not defaults.
+ touchpad {
+ // off
+ tap
+ // dwt
+ // dwtp
+ // drag false
+ // drag-lock
+ natural-scroll
+ // accel-speed 0.2
+ // accel-profile "flat"
+ // scroll-method "two-finger"
+ // disabled-on-external-mouse
+ }
+
+ mouse {
+ // off
+ // natural-scroll
+ // accel-speed 0.2
+ // accel-profile "flat"
+ // scroll-method "no-scroll"
+ }
+
+ trackpoint {
+ // off
+ // natural-scroll
+ // accel-speed 0.2
+ // accel-profile "flat"
+ // scroll-method "on-button-down"
+ // scroll-button 273
+ // scroll-button-lock
+ // middle-emulation
+ }
+
+ // Uncomment this to make the mouse warp to the center of newly focused windows.
+ // warp-mouse-to-focus
+
+ // Focus windows and outputs automatically when moving the mouse into them.
+ // Setting max-scroll-amount="0%" makes it work only on windows already fully on screen.
+ // focus-follows-mouse max-scroll-amount="0%"
+}
+// You can configure outputs by their name, which you can find
+// by running `niri msg outputs` while inside a niri instance.
+// The built-in laptop monitor is usually called "eDP-1".
+// Find more information on the wiki:
+// https://github.com/YaLTeR/niri/wiki/Configuration:-Outputs
+// Remember to uncomment the node by removing "/-"!
+/-output "eDP-2" {
+ mode "2560x1600@239.998993"
+ position x=2560 y=0
+ variable-refresh-rate
+}
+// Settings that influence how windows are positioned and sized.
+// Find more information on the wiki:
+// https://github.com/YaLTeR/niri/wiki/Configuration:-Layout
+layout {
+ // Set gaps around windows in logical pixels.
+ background-color "transparent"
+ // When to center a column when changing focus, options are:
+ // - "never", default behavior, focusing an off-screen column will keep at the left
+ // or right edge of the screen.
+ // - "always", the focused column will always be centered.
+ // - "on-overflow", focusing a column will center it if it doesn't fit
+ // together with the previously focused column.
+ center-focused-column "never"
+ // You can customize the widths that "switch-preset-column-width" (Mod+R) toggles between.
+ preset-column-widths {
+ // Proportion sets the width as a fraction of the output width, taking gaps into account.
+ // For example, you can perfectly fit four windows sized "proportion 0.25" on an output.
+ // The default preset widths are 1/3, 1/2 and 2/3 of the output.
+ proportion 0.33333
+ proportion 0.5
+ proportion 0.66667
+ // Fixed sets the width in logical pixels exactly.
+ // fixed 1920
+ }
+ // You can also customize the heights that "switch-preset-window-height" (Mod+Shift+R) toggles between.
+ // preset-window-heights { }
+ // You can change the default width of the new windows.
+ default-column-width { proportion 0.5; }
+ // If you leave the brackets empty, the windows themselves will decide their initial width.
+ // default-column-width {}
+ // By default focus ring and border are rendered as a solid background rectangle
+ // behind windows. That is, they will show up through semitransparent windows.
+ // This is because windows using client-side decorations can have an arbitrary shape.
+ //
+ // If you don't like that, you should uncomment `prefer-no-csd` below.
+ // Niri will draw focus ring and border *around* windows that agree to omit their
+ // client-side decorations.
+ //
+ // Alternatively, you can override it with a window rule called
+ // `draw-border-with-background`.
+ border {
+ off
+ width 4
+ active-color "#707070" // Neutral gray
+ inactive-color "#d0d0d0" // Light gray
+ urgent-color "#cc4444" // Softer red
+ }
+ shadow {
+ softness 30
+ spread 5
+ offset x=0 y=5
+ color "#0007"
+ }
+ struts {
+ }
+}
+layer-rule {
+ match namespace="^quickshell$"
+ place-within-backdrop true
+}
+overview {
+ workspace-shadow {
+ off
+ }
+}
+// Add lines like this to spawn processes at startup.
+// Note that running niri as a session supports xdg-desktop-autostart,
+// which may be more convenient to use.
+// See the binds section below for more spawn examples.
+// This line starts waybar, a commonly used bar for Wayland compositors.
+environment {
+ XDG_CURRENT_DESKTOP "niri"
+}
+hotkey-overlay {
+ skip-at-startup
+}
+prefer-no-csd
+screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"
+animations {
+ workspace-switch {
+ spring damping-ratio=0.80 stiffness=523 epsilon=0.0001
+ }
+ window-open {
+ duration-ms 150
+ curve "ease-out-expo"
+ }
+ window-close {
+ duration-ms 150
+ curve "ease-out-quad"
+ }
+ horizontal-view-movement {
+ spring damping-ratio=0.85 stiffness=423 epsilon=0.0001
+ }
+ window-movement {
+ spring damping-ratio=0.75 stiffness=323 epsilon=0.0001
+ }
+ window-resize {
+ spring damping-ratio=0.85 stiffness=423 epsilon=0.0001
+ }
+ config-notification-open-close {
+ spring damping-ratio=0.65 stiffness=923 epsilon=0.001
+ }
+ screenshot-ui-open {
+ duration-ms 200
+ curve "ease-out-quad"
+ }
+ overview-open-close {
+ spring damping-ratio=0.85 stiffness=800 epsilon=0.0001
+ }
+}
+// Window rules let you adjust behavior for individual windows.
+// Find more information on the wiki:
+// https://github.com/YaLTeR/niri/wiki/Configuration:-Window-Rules
+// Work around WezTerm's initial configure bug
+// by setting an empty default-column-width.
+window-rule {
+ // This regular expression is intentionally made as specific as possible,
+ // since this is the default config, and we want no false positives.
+ // You can get away with just app-id="wezterm" if you want.
+ match app-id=r#"^org\.wezfurlong\.wezterm$"#
+ default-column-width {}
+}
+window-rule {
+ match app-id=r#"^org\.gnome\."#
+ draw-border-with-background false
+ geometry-corner-radius 12
+ clip-to-geometry true
+}
+window-rule {
+ match app-id=r#"^gnome-control-center$"#
+ match app-id=r#"^pavucontrol$"#
+ match app-id=r#"^nm-connection-editor$"#
+ default-column-width { proportion 0.5; }
+ open-floating false
+}
+window-rule {
+ match app-id=r#"^org\.gnome\.Calculator$"#
+ match app-id=r#"^gnome-calculator$"#
+ match app-id=r#"^galculator$"#
+ match app-id=r#"^blueman-manager$"#
+ match app-id=r#"^org\.gnome\.Nautilus$"#
+ match app-id=r#"^xdg-desktop-portal$"#
+ open-floating true
+}
+window-rule {
+ match app-id=r#"^steam$"# title=r#"^notificationtoasts_\d+_desktop$"#
+ default-floating-position x=10 y=10 relative-to="bottom-right"
+ open-focused false
+}
+window-rule {
+ match app-id=r#"^org\.wezfurlong\.wezterm$"#
+ match app-id="Alacritty"
+ match app-id="zen"
+ match app-id="com.mitchellh.ghostty"
+ match app-id="kitty"
+ draw-border-with-background false
+}
+window-rule {
+ match app-id=r#"firefox$"# title="^Picture-in-Picture$"
+ match app-id="zoom"
+ open-floating true
+}
+// Open dms windows as floating by default
+window-rule {
+ match app-id=r#"org.quickshell$"#
+ match app-id=r#"com.danklinux.dms$"#
+ open-floating true
+}
+debug {
+ honor-xdg-activation-with-invalid-serial
+}
+
+// Override to disable super+tab
+recent-windows {
+ binds {
+ Alt+Tab { next-window scope="output"; }
+ Alt+Shift+Tab { previous-window scope="output"; }
+ Alt+grave { next-window filter="app-id"; }
+ Alt+Shift+grave { previous-window filter="app-id"; }
+ }
+}
+
+// Include dms files
+include "dms/colors.kdl"
+include "dms/layout.kdl"
+include "dms/alttab.kdl"
+include "dms/binds.kdl"
+include "dms/outputs.kdl"
+include "dms/cursor.kdl"
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/testpage.pdf b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/testpage.pdf
new file mode 100644
index 0000000..03e05c4
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/embedded/testpage.pdf
Binary files differ
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/hyprland.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/hyprland.go
new file mode 100644
index 0000000..f6bb964
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/hyprland.go
@@ -0,0 +1,15 @@
+package config
+
+import _ "embed"
+
+//go:embed embedded/hyprland.conf
+var HyprlandConfig string
+
+//go:embed embedded/hypr-colors.conf
+var HyprColorsConfig string
+
+//go:embed embedded/hypr-layout.conf
+var HyprLayoutConfig string
+
+//go:embed embedded/hypr-binds.conf
+var HyprBindsConfig string
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/niri.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/niri.go
new file mode 100644
index 0000000..01801ca
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/niri.go
@@ -0,0 +1,21 @@
+package config
+
+import _ "embed"
+
+//go:embed embedded/niri.kdl
+var NiriConfig string
+
+//go:embed embedded/niri-colors.kdl
+var NiriColorsConfig string
+
+//go:embed embedded/niri-layout.kdl
+var NiriLayoutConfig string
+
+//go:embed embedded/niri-alttab.kdl
+var NiriAlttabConfig string
+
+//go:embed embedded/niri-binds.kdl
+var NiriBindsConfig string
+
+//go:embed embedded/niri-greeter.kdl
+var NiriGreeterConfig string
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/terminals.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/terminals.go
new file mode 100644
index 0000000..a5bba04
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/terminals.go
@@ -0,0 +1,24 @@
+package config
+
+import _ "embed"
+
+//go:embed embedded/ghostty.conf
+var GhosttyConfig string
+
+//go:embed embedded/ghostty-colors.conf
+var GhosttyColorConfig string
+
+//go:embed embedded/kitty.conf
+var KittyConfig string
+
+//go:embed embedded/kitty-theme.conf
+var KittyThemeConfig string
+
+//go:embed embedded/kitty-tabs.conf
+var KittyTabsConfig string
+
+//go:embed embedded/alacritty.toml
+var AlacrittyConfig string
+
+//go:embed embedded/alacritty-theme.toml
+var AlacrittyThemeConfig string
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/testpage.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/testpage.go
new file mode 100644
index 0000000..2c5bbc7
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/config/testpage.go
@@ -0,0 +1,6 @@
+package config
+
+import _ "embed"
+
+//go:embed embedded/testpage.pdf
+var TestPage string