From 654661fd66838a3a4a0e861a3b98665252a70375 Mon Sep 17 00:00:00 2001 From: bbedward Date: Fri, 6 Feb 2026 09:53:41 -0500 Subject: [PATCH] cli/setup: add subcommands for individual includes --- core/cmd/dms/commands_setup.go | 239 +++++++++++++++++++++++++++++ core/cmd/dms/main.go | 3 + core/cmd/dms/main_distro.go | 3 + quickshell/Common/SettingsData.qml | 2 + 4 files changed, 247 insertions(+) diff --git a/core/cmd/dms/commands_setup.go b/core/cmd/dms/commands_setup.go index acde3f2a..de36882e 100644 --- a/core/cmd/dms/commands_setup.go +++ b/core/cmd/dms/commands_setup.go @@ -9,7 +9,9 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/internal/config" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps" + "github.com/AvengeMedia/DankMaterialShell/core/internal/greeter" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" + "github.com/AvengeMedia/DankMaterialShell/core/internal/utils" "github.com/spf13/cobra" ) @@ -24,6 +26,243 @@ var setupCmd = &cobra.Command{ }, } +var setupBindsCmd = &cobra.Command{ + Use: "binds", + Short: "Deploy default keybinds config", + Run: func(cmd *cobra.Command, args []string) { + if err := runSetupDmsConfig("binds"); err != nil { + log.Fatalf("Error: %v", err) + } + }, +} + +var setupLayoutCmd = &cobra.Command{ + Use: "layout", + Short: "Deploy default layout config", + Run: func(cmd *cobra.Command, args []string) { + if err := runSetupDmsConfig("layout"); err != nil { + log.Fatalf("Error: %v", err) + } + }, +} + +var setupColorsCmd = &cobra.Command{ + Use: "colors", + Short: "Deploy default colors config", + Run: func(cmd *cobra.Command, args []string) { + if err := runSetupDmsConfig("colors"); err != nil { + log.Fatalf("Error: %v", err) + } + }, +} + +var setupAlttabCmd = &cobra.Command{ + Use: "alttab", + Short: "Deploy default alt-tab config (niri only)", + Run: func(cmd *cobra.Command, args []string) { + if err := runSetupDmsConfig("alttab"); err != nil { + log.Fatalf("Error: %v", err) + } + }, +} + +var setupOutputsCmd = &cobra.Command{ + Use: "outputs", + Short: "Deploy default outputs config", + Run: func(cmd *cobra.Command, args []string) { + if err := runSetupDmsConfig("outputs"); err != nil { + log.Fatalf("Error: %v", err) + } + }, +} + +var setupCursorCmd = &cobra.Command{ + Use: "cursor", + Short: "Deploy default cursor config", + Run: func(cmd *cobra.Command, args []string) { + if err := runSetupDmsConfig("cursor"); err != nil { + log.Fatalf("Error: %v", err) + } + }, +} + +var setupWindowrulesCmd = &cobra.Command{ + Use: "windowrules", + Short: "Deploy default window rules config", + Run: func(cmd *cobra.Command, args []string) { + if err := runSetupDmsConfig("windowrules"); err != nil { + log.Fatalf("Error: %v", err) + } + }, +} + +type dmsConfigSpec struct { + niriFile string + hyprFile string + niriContent func(terminal string) string + hyprContent func(terminal string) string +} + +var dmsConfigSpecs = map[string]dmsConfigSpec{ + "binds": { + niriFile: "binds.kdl", + hyprFile: "binds.conf", + niriContent: func(t string) string { + return strings.ReplaceAll(config.NiriBindsConfig, "{{TERMINAL_COMMAND}}", t) + }, + hyprContent: func(t string) string { + return strings.ReplaceAll(config.HyprBindsConfig, "{{TERMINAL_COMMAND}}", t) + }, + }, + "layout": { + niriFile: "layout.kdl", + hyprFile: "layout.conf", + niriContent: func(_ string) string { return config.NiriLayoutConfig }, + hyprContent: func(_ string) string { return config.HyprLayoutConfig }, + }, + "colors": { + niriFile: "colors.kdl", + hyprFile: "colors.conf", + niriContent: func(_ string) string { return config.NiriColorsConfig }, + hyprContent: func(_ string) string { return config.HyprColorsConfig }, + }, + "alttab": { + niriFile: "alttab.kdl", + niriContent: func(_ string) string { return config.NiriAlttabConfig }, + }, + "outputs": { + niriFile: "outputs.kdl", + hyprFile: "outputs.conf", + niriContent: func(_ string) string { return "" }, + hyprContent: func(_ string) string { return "" }, + }, + "cursor": { + niriFile: "cursor.kdl", + hyprFile: "cursor.conf", + niriContent: func(_ string) string { return "" }, + hyprContent: func(_ string) string { return "" }, + }, + "windowrules": { + niriFile: "windowrules.kdl", + hyprFile: "windowrules.conf", + niriContent: func(_ string) string { return "" }, + hyprContent: func(_ string) string { return "" }, + }, +} + +func detectTerminal() (string, error) { + terminals := []string{"ghostty", "foot", "kitty", "alacritty"} + var found []string + for _, t := range terminals { + if utils.CommandExists(t) { + found = append(found, t) + } + } + + switch len(found) { + case 0: + return "ghostty", nil + case 1: + return found[0], nil + } + + fmt.Println("Multiple terminals detected:") + for i, t := range found { + fmt.Printf("%d) %s\n", i+1, t) + } + fmt.Printf("\nChoice (1-%d): ", len(found)) + + var response string + fmt.Scanln(&response) + response = strings.TrimSpace(response) + + choice := 0 + fmt.Sscanf(response, "%d", &choice) + if choice < 1 || choice > len(found) { + return "", fmt.Errorf("invalid choice") + } + return found[choice-1], nil +} + +func detectCompositorForSetup() (string, error) { + compositors := greeter.DetectCompositors() + + switch len(compositors) { + case 0: + return "", fmt.Errorf("no supported compositors found (niri or Hyprland required)") + case 1: + return strings.ToLower(compositors[0]), nil + } + + selected, err := greeter.PromptCompositorChoice(compositors) + if err != nil { + return "", err + } + return strings.ToLower(selected), nil +} + +func runSetupDmsConfig(name string) error { + spec, ok := dmsConfigSpecs[name] + if !ok { + return fmt.Errorf("unknown config: %s", name) + } + + compositor, err := detectCompositorForSetup() + if err != nil { + return err + } + + var filename string + var contentFn func(string) string + switch compositor { + case "niri": + filename = spec.niriFile + contentFn = spec.niriContent + case "hyprland": + filename = spec.hyprFile + contentFn = spec.hyprContent + default: + return fmt.Errorf("unsupported compositor: %s", compositor) + } + + if filename == "" { + return fmt.Errorf("%s is not supported for %s", name, compositor) + } + + var dmsDir string + switch compositor { + case "niri": + dmsDir = filepath.Join(os.Getenv("HOME"), ".config", "niri", "dms") + case "hyprland": + dmsDir = filepath.Join(os.Getenv("HOME"), ".config", "hypr", "dms") + } + + if err := os.MkdirAll(dmsDir, 0o755); err != nil { + return fmt.Errorf("failed to create dms directory: %w", err) + } + + path := filepath.Join(dmsDir, filename) + if info, err := os.Stat(path); err == nil && info.Size() > 0 { + return fmt.Errorf("%s already exists and is not empty: %s", name, path) + } + + terminal := "ghostty" + if contentFn != nil && name == "binds" { + terminal, err = detectTerminal() + if err != nil { + return err + } + } + + content := contentFn(terminal) + if err := os.WriteFile(path, []byte(content), 0o644); err != nil { + return fmt.Errorf("failed to write %s: %w", filename, err) + } + + fmt.Printf("Deployed %s to %s\n", name, path) + return nil +} + func runSetup() error { fmt.Println("=== DMS Configuration Setup ===") diff --git a/core/cmd/dms/main.go b/core/cmd/dms/main.go index 6b09e4dc..6b62c070 100644 --- a/core/cmd/dms/main.go +++ b/core/cmd/dms/main.go @@ -19,6 +19,9 @@ func init() { // Add subcommands to greeter greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd) + // Add subcommands to setup + setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd) + // Add subcommands to update updateCmd.AddCommand(updateCheckCmd) diff --git a/core/cmd/dms/main_distro.go b/core/cmd/dms/main_distro.go index 6496485a..6b7bcdef 100644 --- a/core/cmd/dms/main_distro.go +++ b/core/cmd/dms/main_distro.go @@ -20,6 +20,9 @@ func init() { // Add subcommands to greeter greeterCmd.AddCommand(greeterSyncCmd, greeterEnableCmd, greeterStatusCmd) + // Add subcommands to setup + setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd) + // Add subcommands to plugins pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd) diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 3460372a..6779d955 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -1014,6 +1014,7 @@ Singleton { for config_dir in ${_configDir}/gtk-3.0 ${_configDir}/gtk-4.0; do settings_file="$config_dir/settings.ini" + [ -f "$settings_file" ] && [ ! -w "$settings_file" ] && continue if [ -f "$settings_file" ]; then if grep -q "^gtk-icon-theme-name=" "$settings_file"; then sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=${gtkThemeName}/' "$settings_file" @@ -1863,6 +1864,7 @@ Singleton { const script = ` xresources_file="${xresourcesPath}" + [ -f "$xresources_file" ] && [ ! -w "$xresources_file" ] && exit 0 theme_name="${themeName}" cursor_size="${size}"