summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base.go
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base.go')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base.go721
1 files changed, 0 insertions, 721 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base.go
deleted file mode 100644
index 63555c8..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/distros/base.go
+++ /dev/null
@@ -1,721 +0,0 @@
-package distros
-
-import (
- "bufio"
- "context"
- _ "embed"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "runtime"
- "strings"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/version"
-)
-
-const (
- forceQuickshellGit = false
- forceDMSGit = false
-)
-
-// BaseDistribution provides common functionality for all distributions
-type BaseDistribution struct {
- logChan chan<- string
-}
-
-// NewBaseDistribution creates a new base distribution
-func NewBaseDistribution(logChan chan<- string) *BaseDistribution {
- return &BaseDistribution{
- logChan: logChan,
- }
-}
-
-// Common helper methods
-func (b *BaseDistribution) commandExists(cmd string) bool {
- _, err := exec.LookPath(cmd)
- return err == nil
-}
-
-func (b *BaseDistribution) CommandExists(cmd string) bool {
- return b.commandExists(cmd)
-}
-
-func (b *BaseDistribution) log(message string) {
- if b.logChan != nil {
- b.logChan <- message
- }
-}
-
-func (b *BaseDistribution) logError(message string, err error) {
- errorMsg := fmt.Sprintf("ERROR: %s: %v", message, err)
- b.log(errorMsg)
-}
-
-// escapeSingleQuotes escapes single quotes in a string for safe use in bash single-quoted strings.
-// It replaces each ' with '\” which closes the quote, adds an escaped quote, and reopens the quote.
-// This prevents shell injection and syntax errors when passwords contain single quotes or apostrophes.
-func escapeSingleQuotes(s string) string {
- return strings.ReplaceAll(s, "'", "'\\''")
-}
-
-// MakeSudoCommand creates a command string that safely passes password to sudo.
-// This helper escapes special characters in the password to prevent shell injection
-// and syntax errors when passwords contain single quotes, apostrophes, or other special chars.
-func MakeSudoCommand(sudoPassword string, command string) string {
- return fmt.Sprintf("echo '%s' | sudo -S %s", escapeSingleQuotes(sudoPassword), command)
-}
-
-// ExecSudoCommand creates an exec.Cmd that runs a command with sudo using the provided password.
-// The password is properly escaped to prevent shell injection and syntax errors.
-func ExecSudoCommand(ctx context.Context, sudoPassword string, command string) *exec.Cmd {
- cmdStr := MakeSudoCommand(sudoPassword, command)
- return exec.CommandContext(ctx, "bash", "-c", cmdStr)
-}
-
-func (b *BaseDistribution) detectCommand(name, description string) deps.Dependency {
- status := deps.StatusMissing
- if b.commandExists(name) {
- status = deps.StatusInstalled
- }
- return deps.Dependency{
- Name: name,
- Status: status,
- Description: description,
- Required: true,
- }
-}
-
-func (b *BaseDistribution) detectPackage(name, description string, installed bool) deps.Dependency {
- status := deps.StatusMissing
- if installed {
- status = deps.StatusInstalled
- }
- return deps.Dependency{
- Name: name,
- Status: status,
- Description: description,
- Required: true,
- }
-}
-
-func (b *BaseDistribution) detectOptionalPackage(name, description string, installed bool) deps.Dependency {
- status := deps.StatusMissing
- if installed {
- status = deps.StatusInstalled
- }
- return deps.Dependency{
- Name: name,
- Status: status,
- Description: description,
- Required: false,
- }
-}
-
-func (b *BaseDistribution) detectGit() deps.Dependency {
- return b.detectCommand("git", "Version control system")
-}
-
-func (b *BaseDistribution) detectMatugen() deps.Dependency {
- return b.detectCommand("matugen", "Material Design color generation tool")
-}
-
-func (b *BaseDistribution) detectDgop() deps.Dependency {
- return b.detectCommand("dgop", "Desktop portal management tool")
-}
-
-func (b *BaseDistribution) detectDMS() deps.Dependency {
- dmsPath := filepath.Join(os.Getenv("HOME"), ".config/quickshell/dms")
-
- status := deps.StatusMissing
- currentVersion := ""
-
- if _, err := os.Stat(dmsPath); err == nil {
- status = deps.StatusInstalled
-
- // Only get current version, don't check for updates (lazy loading)
- current, err := version.GetCurrentDMSVersion()
- if err == nil {
- currentVersion = current
- }
- }
-
- dep := deps.Dependency{
- Name: "dms (DankMaterialShell)",
- Status: status,
- Description: "Desktop Management System configuration",
- Required: true,
- CanToggle: true,
- }
-
- if currentVersion != "" {
- dep.Version = currentVersion
- }
-
- return dep
-}
-
-func (b *BaseDistribution) detectSpecificTerminal(terminal deps.Terminal) deps.Dependency {
- switch terminal {
- case deps.TerminalGhostty:
- status := deps.StatusMissing
- if b.commandExists("ghostty") {
- status = deps.StatusInstalled
- }
- return deps.Dependency{
- Name: "ghostty",
- Status: status,
- Description: "A fast, native terminal emulator built in Zig.",
- Required: true,
- }
- case deps.TerminalKitty:
- status := deps.StatusMissing
- if b.commandExists("kitty") {
- status = deps.StatusInstalled
- }
- return deps.Dependency{
- Name: "kitty",
- Status: status,
- Description: "A feature-rich, customizable terminal emulator.",
- Required: true,
- }
- case deps.TerminalAlacritty:
- status := deps.StatusMissing
- if b.commandExists("alacritty") {
- status = deps.StatusInstalled
- }
- return deps.Dependency{
- Name: "alacritty",
- Status: status,
- Description: "A simple terminal emulator. (No dynamic theming)",
- Required: true,
- }
- default:
- return b.detectSpecificTerminal(deps.TerminalGhostty)
- }
-}
-
-func (b *BaseDistribution) detectHyprlandTools() []deps.Dependency {
- var dependencies []deps.Dependency
-
- tools := []struct {
- name string
- description string
- }{
- {"hyprctl", "Hyprland control utility"},
- {"jq", "JSON processor"},
- }
-
- for _, tool := range tools {
- status := deps.StatusMissing
- if b.commandExists(tool.name) {
- status = deps.StatusInstalled
- }
-
- dependencies = append(dependencies, deps.Dependency{
- Name: tool.name,
- Status: status,
- Description: tool.description,
- Required: true,
- })
- }
-
- return dependencies
-}
-
-func (b *BaseDistribution) detectQuickshell() deps.Dependency {
- if !b.commandExists("qs") {
- return deps.Dependency{
- Name: "quickshell",
- Status: deps.StatusMissing,
- Description: "QtQuick based desktop shell toolkit",
- Required: true,
- Variant: deps.VariantStable,
- CanToggle: true,
- }
- }
-
- cmd := exec.Command("qs", "--version")
- output, err := cmd.Output()
- if err != nil {
- return deps.Dependency{
- Name: "quickshell",
- Status: deps.StatusNeedsReinstall,
- Description: "QtQuick based desktop shell toolkit (version check failed)",
- Required: true,
- Variant: deps.VariantStable,
- CanToggle: true,
- }
- }
-
- versionStr := string(output)
- versionRegex := regexp.MustCompile(`quickshell (\d+\.\d+\.\d+)`)
- matches := versionRegex.FindStringSubmatch(versionStr)
-
- if len(matches) < 2 {
- return deps.Dependency{
- Name: "quickshell",
- Status: deps.StatusNeedsReinstall,
- Description: "QtQuick based desktop shell toolkit (unknown version)",
- Required: true,
- Variant: deps.VariantStable,
- CanToggle: true,
- }
- }
-
- version := matches[1]
- variant := deps.VariantStable
- if strings.Contains(versionStr, "git") || strings.Contains(versionStr, "+") {
- variant = deps.VariantGit
- }
-
- if b.versionCompare(version, "0.2.0") >= 0 {
- return deps.Dependency{
- Name: "quickshell",
- Status: deps.StatusInstalled,
- Version: version,
- Description: "QtQuick based desktop shell toolkit",
- Required: true,
- Variant: variant,
- CanToggle: true,
- }
- }
-
- return deps.Dependency{
- Name: "quickshell",
- Status: deps.StatusNeedsUpdate,
- Variant: variant,
- CanToggle: true,
- Version: version,
- Description: "QtQuick based desktop shell toolkit (needs 0.2.0+)",
- Required: true,
- }
-}
-
-func (b *BaseDistribution) detectWindowManager(wm deps.WindowManager) deps.Dependency {
- switch wm {
- case deps.WindowManagerHyprland:
- status := deps.StatusMissing
- variant := deps.VariantStable
- version := ""
-
- if b.commandExists("hyprland") || b.commandExists("Hyprland") {
- status = deps.StatusInstalled
- cmd := exec.Command("hyprctl", "version")
- if output, err := cmd.Output(); err == nil {
- outStr := string(output)
- if strings.Contains(outStr, "git") || strings.Contains(outStr, "dirty") {
- variant = deps.VariantGit
- }
- if versionRegex := regexp.MustCompile(`v(\d+\.\d+\.\d+)`); versionRegex.MatchString(outStr) {
- matches := versionRegex.FindStringSubmatch(outStr)
- if len(matches) > 1 {
- version = matches[1]
- }
- }
- }
- }
- return deps.Dependency{
- Name: "hyprland",
- Status: status,
- Version: version,
- Description: "Dynamic tiling Wayland compositor",
- Required: true,
- Variant: variant,
- CanToggle: true,
- }
- case deps.WindowManagerNiri:
- status := deps.StatusMissing
- variant := deps.VariantStable
- version := ""
-
- if b.commandExists("niri") {
- status = deps.StatusInstalled
- cmd := exec.Command("niri", "--version")
- if output, err := cmd.Output(); err == nil {
- outStr := string(output)
- if strings.Contains(outStr, "git") || strings.Contains(outStr, "+") {
- variant = deps.VariantGit
- }
- if versionRegex := regexp.MustCompile(`niri (\d+\.\d+)`); versionRegex.MatchString(outStr) {
- matches := versionRegex.FindStringSubmatch(outStr)
- if len(matches) > 1 {
- version = matches[1]
- }
- }
- }
- }
- return deps.Dependency{
- Name: "niri",
- Status: status,
- Version: version,
- Description: "Scrollable-tiling Wayland compositor",
- Required: true,
- Variant: variant,
- CanToggle: true,
- }
- default:
- return deps.Dependency{
- Name: "unknown-wm",
- Status: deps.StatusMissing,
- Description: "Unknown window manager",
- Required: true,
- }
- }
-}
-
-// Version comparison helper
-func (b *BaseDistribution) versionCompare(v1, v2 string) int {
- parts1 := strings.Split(v1, ".")
- parts2 := strings.Split(v2, ".")
-
- for i := 0; i < len(parts1) && i < len(parts2); i++ {
- if parts1[i] < parts2[i] {
- return -1
- }
- if parts1[i] > parts2[i] {
- return 1
- }
- }
-
- if len(parts1) < len(parts2) {
- return -1
- }
- if len(parts1) > len(parts2) {
- return 1
- }
-
- return 0
-}
-
-// Common installation helper
-func (b *BaseDistribution) runWithProgress(cmd *exec.Cmd, progressChan chan<- InstallProgressMsg, phase InstallPhase, startProgress, endProgress float64) error {
- return b.runWithProgressTimeout(cmd, progressChan, phase, startProgress, endProgress, 20*time.Minute)
-}
-
-func (b *BaseDistribution) runWithProgressTimeout(cmd *exec.Cmd, progressChan chan<- InstallProgressMsg, phase InstallPhase, startProgress, endProgress float64, timeout time.Duration) error {
- return b.runWithProgressStepTimeout(cmd, progressChan, phase, startProgress, endProgress, "Installing...", timeout)
-}
-
-func (b *BaseDistribution) runWithProgressStep(cmd *exec.Cmd, progressChan chan<- InstallProgressMsg, phase InstallPhase, startProgress, endProgress float64, stepMessage string) error {
- return b.runWithProgressStepTimeout(cmd, progressChan, phase, startProgress, endProgress, stepMessage, 20*time.Minute)
-}
-
-func (b *BaseDistribution) runWithProgressStepTimeout(cmd *exec.Cmd, progressChan chan<- InstallProgressMsg, phase InstallPhase, startProgress, endProgress float64, stepMessage string, timeoutDuration time.Duration) error {
- stdout, err := cmd.StdoutPipe()
- if err != nil {
- return fmt.Errorf("failed to create stdout pipe: %w", err)
- }
- stderr, err := cmd.StderrPipe()
- if err != nil {
- return fmt.Errorf("failed to create stderr pipe: %w", err)
- }
-
- if err := cmd.Start(); err != nil {
- return err
- }
-
- outputChan := make(chan string, 100)
- done := make(chan error, 1)
-
- go func() {
- scanner := bufio.NewScanner(stdout)
- for scanner.Scan() {
- line := scanner.Text()
- b.log(line)
- outputChan <- line
- }
- }()
-
- go func() {
- scanner := bufio.NewScanner(stderr)
- for scanner.Scan() {
- line := scanner.Text()
- b.log(line)
- outputChan <- line
- }
- }()
-
- go func() {
- done <- cmd.Wait()
- close(outputChan)
- }()
-
- ticker := time.NewTicker(200 * time.Millisecond)
- defer ticker.Stop()
-
- progress := startProgress
- progressStep := (endProgress - startProgress) / 50
- lastOutput := ""
-
- var timeout *time.Timer
- var timeoutChan <-chan time.Time
- if timeoutDuration > 0 {
- timeout = time.NewTimer(timeoutDuration)
- defer timeout.Stop()
- timeoutChan = timeout.C
- }
-
- for {
- select {
- case err := <-done:
- if err != nil {
- b.logError("Command execution failed", err)
- b.log(fmt.Sprintf("Last output before failure: %s", lastOutput))
- progressChan <- InstallProgressMsg{
- Phase: phase,
- Progress: startProgress,
- Step: "Command failed",
- IsComplete: false,
- LogOutput: lastOutput,
- Error: err,
- }
- return err
- }
- progressChan <- InstallProgressMsg{
- Phase: phase,
- Progress: endProgress,
- Step: "Installation step complete",
- IsComplete: false,
- LogOutput: lastOutput,
- }
- return nil
- case output, ok := <-outputChan:
- if ok {
- lastOutput = output
- progressChan <- InstallProgressMsg{
- Phase: phase,
- Progress: progress,
- Step: stepMessage,
- IsComplete: false,
- LogOutput: output,
- }
- if timeout != nil {
- timeout.Reset(timeoutDuration)
- }
- }
- case <-timeoutChan:
- if cmd.Process != nil {
- cmd.Process.Kill()
- }
- err := fmt.Errorf("installation timed out after %v", timeoutDuration)
- progressChan <- InstallProgressMsg{
- Phase: phase,
- Progress: startProgress,
- Step: "Installation timed out",
- IsComplete: false,
- LogOutput: lastOutput,
- Error: err,
- }
- return err
- case <-ticker.C:
- if progress < endProgress-0.01 {
- progress += progressStep
- progressChan <- InstallProgressMsg{
- Phase: phase,
- Progress: progress,
- Step: "Installing...",
- IsComplete: false,
- LogOutput: lastOutput,
- }
- }
- }
- }
-}
-
-func (b *BaseDistribution) DetectTerminalFromDeps(dependencies []deps.Dependency) deps.Terminal {
- for _, dep := range dependencies {
- switch dep.Name {
- case "ghostty":
- return deps.TerminalGhostty
- case "kitty":
- return deps.TerminalKitty
- case "alacritty":
- return deps.TerminalAlacritty
- }
- }
- return deps.TerminalGhostty
-}
-
-func (b *BaseDistribution) WriteEnvironmentConfig(terminal deps.Terminal) error {
- homeDir, err := os.UserHomeDir()
- if err != nil {
- return fmt.Errorf("failed to get home directory: %w", err)
- }
-
- envDir := filepath.Join(homeDir, ".config", "environment.d")
- if err := os.MkdirAll(envDir, 0o755); err != nil {
- return fmt.Errorf("failed to create environment.d directory: %w", err)
- }
-
- var terminalCmd string
- switch terminal {
- case deps.TerminalGhostty:
- terminalCmd = "ghostty"
- case deps.TerminalKitty:
- terminalCmd = "kitty"
- case deps.TerminalAlacritty:
- terminalCmd = "alacritty"
- default:
- terminalCmd = "ghostty"
- }
-
- content := fmt.Sprintf(`ELECTRON_OZONE_PLATFORM_HINT=auto
-TERMINAL=%s
-`, terminalCmd)
-
- envFile := filepath.Join(envDir, "90-dms.conf")
- if err := os.WriteFile(envFile, []byte(content), 0o644); err != nil {
- return fmt.Errorf("failed to write environment config: %w", err)
- }
-
- b.log(fmt.Sprintf("Wrote environment config to %s", envFile))
- return nil
-}
-
-func (b *BaseDistribution) EnableDMSService(ctx context.Context, wm deps.WindowManager) error {
- switch wm {
- case deps.WindowManagerNiri:
- if err := exec.CommandContext(ctx, "systemctl", "--user", "add-wants", "niri.service", "dms").Run(); err != nil {
- b.log("Warning: failed to add dms as a want for niri.service")
- }
- case deps.WindowManagerHyprland:
- if err := exec.CommandContext(ctx, "systemctl", "--user", "add-wants", "hyprland-session.target", "dms").Run(); err != nil {
- b.log("Warning: failed to add dms as a want for hyprland-session.target")
- }
- }
-
- return nil
-}
-
-func (b *BaseDistribution) WriteWindowManagerConfig(wm deps.WindowManager) error {
- if wm == deps.WindowManagerHyprland {
- if err := b.WriteHyprlandSessionTarget(); err != nil {
- return fmt.Errorf("failed to write hyprland session target: %w", err)
- }
- }
- return nil
-}
-
-func (b *BaseDistribution) WriteHyprlandSessionTarget() error {
- homeDir, err := os.UserHomeDir()
- if err != nil {
- return fmt.Errorf("failed to get home directory: %w", err)
- }
-
- targetDir := filepath.Join(homeDir, ".config", "systemd", "user")
- if err := os.MkdirAll(targetDir, 0o755); err != nil {
- return fmt.Errorf("failed to create systemd user directory: %w", err)
- }
-
- targetPath := filepath.Join(targetDir, "hyprland-session.target")
- content := `[Unit]
-Description=Hyprland Session Target
-Requires=graphical-session.target
-After=graphical-session.target
-`
-
- if err := os.WriteFile(targetPath, []byte(content), 0o644); err != nil {
- return fmt.Errorf("failed to write hyprland-session.target: %w", err)
- }
-
- b.log(fmt.Sprintf("Wrote hyprland-session.target to %s", targetPath))
- return nil
-}
-
-// installDMSBinary installs the DMS binary from GitHub releases
-func (b *BaseDistribution) installDMSBinary(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
- b.log("Installing/updating DMS binary...")
-
- // Detect architecture
- arch := runtime.GOARCH
- switch arch {
- case "amd64":
- case "arm64":
- default:
- return fmt.Errorf("unsupported architecture for DMS: %s", arch)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseConfiguration,
- Progress: 0.80,
- Step: "Downloading DMS binary...",
- IsComplete: false,
- CommandInfo: fmt.Sprintf("Downloading dms-%s.gz", arch),
- }
-
- // Get latest release version
- latestVersionCmd := exec.CommandContext(ctx, "bash", "-c",
- `curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/'`)
- versionOutput, err := latestVersionCmd.Output()
- if err != nil {
- return fmt.Errorf("failed to get latest DMS version: %w", err)
- }
- version := strings.TrimSpace(string(versionOutput))
- if version == "" {
- return fmt.Errorf("could not determine latest DMS version")
- }
-
- homeDir, err := os.UserHomeDir()
- if err != nil {
- return fmt.Errorf("failed to get user home directory: %w", err)
- }
- tmpDir := filepath.Join(homeDir, ".cache", "dankinstall", "manual-builds")
- if err := os.MkdirAll(tmpDir, 0o755); err != nil {
- return fmt.Errorf("failed to create temp directory: %w", err)
- }
- defer os.RemoveAll(tmpDir)
-
- // Download the gzipped binary
- downloadURL := fmt.Sprintf("https://github.com/AvengeMedia/DankMaterialShell/releases/download/%s/dms-cli-%s.gz", version, arch)
- gzPath := filepath.Join(tmpDir, "dms.gz")
-
- downloadCmd := exec.CommandContext(ctx, "curl", "-L", downloadURL, "-o", gzPath)
- if err := downloadCmd.Run(); err != nil {
- return fmt.Errorf("failed to download DMS binary: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseConfiguration,
- Progress: 0.85,
- Step: "Extracting DMS binary...",
- IsComplete: false,
- CommandInfo: "gunzip dms.gz",
- }
-
- // Extract the binary
- extractCmd := exec.CommandContext(ctx, "gunzip", gzPath)
- if err := extractCmd.Run(); err != nil {
- return fmt.Errorf("failed to extract DMS binary: %w", err)
- }
-
- binaryPath := filepath.Join(tmpDir, "dms")
-
- // Make it executable
- chmodCmd := exec.CommandContext(ctx, "chmod", "+x", binaryPath)
- if err := chmodCmd.Run(); err != nil {
- return fmt.Errorf("failed to make DMS binary executable: %w", err)
- }
-
- progressChan <- InstallProgressMsg{
- Phase: PhaseConfiguration,
- Progress: 0.88,
- Step: "Installing DMS to /usr/local/bin...",
- IsComplete: false,
- NeedsSudo: true,
- CommandInfo: "sudo cp dms /usr/local/bin/",
- }
-
- // Install to /usr/local/bin
- installCmd := ExecSudoCommand(ctx, sudoPassword,
- fmt.Sprintf("cp %s /usr/local/bin/dms", binaryPath))
- if err := installCmd.Run(); err != nil {
- return fmt.Errorf("failed to install DMS binary: %w", err)
- }
-
- b.log("DMS binary installed successfully")
- return nil
-}