mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-15 15:54:00 -04:00
159 lines
5.0 KiB
Go
159 lines
5.0 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var setupHeadlessCmd = &cobra.Command{
|
|
Use: "headless",
|
|
Short: "Deploy DMS configurations without prompts",
|
|
Long: "Deploy compositor and terminal configurations non-interactively, for installers and scripts",
|
|
PersistentPreRunE: requireMutableSystemCommand,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
compositor, _ := cmd.Flags().GetString("compositor")
|
|
terminal, _ := cmd.Flags().GetString("terminal")
|
|
noSystemd, _ := cmd.Flags().GetBool("no-systemd")
|
|
force, _ := cmd.Flags().GetBool("force")
|
|
skipExisting, _ := cmd.Flags().GetBool("skip-existing")
|
|
stockBinds, _ := cmd.Flags().GetBool("stock-binds")
|
|
cmd.SilenceUsage = true
|
|
return runSetupHeadless(compositor, terminal, noSystemd, force, skipExisting, stockBinds)
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
setupHeadlessCmd.Flags().String("compositor", "", "Compositor to configure: niri, hyprland, or mango")
|
|
setupHeadlessCmd.Flags().String("terminal", "", "Also deploy a terminal config: ghostty, kitty, or alacritty (binds otherwise use $TERMINAL or the first installed terminal)")
|
|
setupHeadlessCmd.Flags().Bool("no-systemd", false, "Deploy session config without systemd integration")
|
|
setupHeadlessCmd.Flags().Bool("force", false, "Overwrite existing configs (timestamped backups are created)")
|
|
setupHeadlessCmd.Flags().Bool("skip-existing", false, "Warn and skip instead of failing when a config already exists")
|
|
setupHeadlessCmd.Flags().Bool("stock-binds", false, "Replace existing keybinds with the DMS stock set (a backup is kept)")
|
|
_ = setupHeadlessCmd.MarkFlagRequired("compositor")
|
|
setupHeadlessCmd.MarkFlagsMutuallyExclusive("force", "skip-existing")
|
|
}
|
|
|
|
func runSetupHeadless(compositor, terminal string, noSystemd, force, skipExisting, stockBinds bool) error {
|
|
wm, err := parseHeadlessCompositor(compositor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
term, termSelected, err := parseHeadlessTerminal(terminal)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
deployCompositor := true
|
|
deployTerminal := termSelected
|
|
|
|
if !force {
|
|
if existing := firstExistingPath(compositorPrimaryPaths(wm)); existing != "" {
|
|
if !skipExisting {
|
|
return fmt.Errorf("%s config already exists: %s (use --force to overwrite with backup, or --skip-existing)", compositor, existing)
|
|
}
|
|
fmt.Printf("⚠ Skipping %s config, already exists: %s\n", compositor, existing)
|
|
deployCompositor = false
|
|
}
|
|
if deployTerminal {
|
|
if existing := firstExistingPath([]string{terminalPrimaryPath(term)}); existing != "" {
|
|
if !skipExisting {
|
|
return fmt.Errorf("%s config already exists: %s (use --force to overwrite with backup, or --skip-existing)", terminal, existing)
|
|
}
|
|
fmt.Printf("⚠ Skipping %s config, already exists: %s\n", terminal, existing)
|
|
deployTerminal = false
|
|
}
|
|
}
|
|
}
|
|
|
|
if !deployCompositor && !deployTerminal {
|
|
return nil
|
|
}
|
|
|
|
logChan := make(chan string, 100)
|
|
logDone := make(chan struct{})
|
|
go func() {
|
|
for msg := range logChan {
|
|
fmt.Println(" " + msg)
|
|
}
|
|
close(logDone)
|
|
}()
|
|
deployer := config.NewConfigDeployer(logChan)
|
|
deployer.SetReplaceBinds(stockBinds)
|
|
|
|
var results []config.DeploymentResult
|
|
var deployErr error
|
|
if deployCompositor {
|
|
terminalCommand := defaultTerminalCommand()
|
|
if termSelected {
|
|
terminalCommand = term.Command()
|
|
}
|
|
if wm == deps.WindowManagerHyprland {
|
|
deployer.SetHyprlandVersion(installedHyprlandVersion())
|
|
}
|
|
result, err := deployer.DeployCompositor(wm, terminalCommand, headlessUseSystemd(wm, noSystemd))
|
|
results = append(results, result)
|
|
deployErr = err
|
|
}
|
|
if deployErr == nil && deployTerminal {
|
|
terminalResults, err := deployer.DeployTerminal(term)
|
|
results = append(results, terminalResults...)
|
|
deployErr = err
|
|
}
|
|
|
|
close(logChan)
|
|
<-logDone
|
|
|
|
for _, result := range results {
|
|
if !result.Deployed {
|
|
continue
|
|
}
|
|
fmt.Printf("✓ %s: %s\n", result.ConfigType, result.Path)
|
|
if result.BackupPath != "" {
|
|
fmt.Printf(" Backup: %s\n", result.BackupPath)
|
|
}
|
|
}
|
|
|
|
if deployErr != nil {
|
|
return fmt.Errorf("deployment failed: %w", deployErr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseHeadlessCompositor(name string) (deps.WindowManager, error) {
|
|
switch name {
|
|
case "niri":
|
|
return deps.WindowManagerNiri, nil
|
|
case "hyprland":
|
|
return deps.WindowManagerHyprland, nil
|
|
case "mango":
|
|
return deps.WindowManagerMango, nil
|
|
default:
|
|
return 0, fmt.Errorf("unknown compositor %q (expected niri, hyprland, or mango)", name)
|
|
}
|
|
}
|
|
|
|
func parseHeadlessTerminal(name string) (deps.Terminal, bool, error) {
|
|
switch name {
|
|
case "":
|
|
return deps.TerminalGhostty, false, nil
|
|
case "ghostty":
|
|
return deps.TerminalGhostty, true, nil
|
|
case "kitty":
|
|
return deps.TerminalKitty, true, nil
|
|
case "alacritty":
|
|
return deps.TerminalAlacritty, true, nil
|
|
default:
|
|
return 0, false, fmt.Errorf("unknown terminal %q (expected ghostty, kitty, or alacritty)", name)
|
|
}
|
|
}
|
|
|
|
func headlessUseSystemd(wm deps.WindowManager, noSystemd bool) bool {
|
|
if noSystemd {
|
|
return false
|
|
}
|
|
return !isVoidSetup()
|
|
}
|