summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dankinstall/main.go219
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/assets/cli-policy.default.json10
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_auth.go76
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_blur.go40
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_brightness.go311
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_chroma.go300
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_clipboard.go1004
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_colorpicker.go139
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_common.go530
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_config.go318
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_dank16.go126
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_doctor.go1107
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_download.go99
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_dpms.go105
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_features.go491
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_greeter.go1850
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_greeter_test.go87
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_keybinds.go265
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_matugen.go202
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_notify.go68
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_open.go205
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_randr.go58
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_root.go74
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_screenshot.go422
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_setup.go436
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_windowrules.go338
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/dpms_client.go339
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/immutable_policy.go271
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/main.go43
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/main_distro.go40
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/randr_client.go172
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/server_client.go114
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/shell.go739
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/ui.go53
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/utils.go89
35 files changed, 0 insertions, 10740 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dankinstall/main.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dankinstall/main.go
deleted file mode 100644
index 042506e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dankinstall/main.go
+++ /dev/null
@@ -1,219 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/headless"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/spf13/cobra"
-)
-
-var Version = "dev"
-
-// Flag variables bound via pflag
-var (
- compositor string
- term string
- includeDeps []string
- excludeDeps []string
- replaceConfigs []string
- replaceConfigsAll bool
- yes bool
-)
-
-var rootCmd = &cobra.Command{
- Use: "dankinstall",
- Short: "Install DankMaterialShell and its dependencies",
- Long: `dankinstall sets up DankMaterialShell with your chosen compositor and terminal.
-
-Without flags, it launches an interactive TUI. Providing either --compositor
-or --term activates headless (unattended) mode, which requires both flags.
-
-Headless mode requires cached sudo credentials. Run 'sudo -v' beforehand, or
-configure passwordless sudo for your user.`,
- Args: cobra.NoArgs,
- RunE: runDankinstall,
- SilenceErrors: true,
- SilenceUsage: true,
-}
-
-func init() {
- rootCmd.Flags().StringVarP(&compositor, "compositor", "c", "", "Compositor/WM to install: niri or hyprland (enables headless mode)")
- rootCmd.Flags().StringVarP(&term, "term", "t", "", "Terminal emulator to install: ghostty, kitty, or alacritty (enables headless mode)")
- rootCmd.Flags().StringSliceVar(&includeDeps, "include-deps", []string{}, "Optional deps to enable (e.g. dms-greeter)")
- rootCmd.Flags().StringSliceVar(&excludeDeps, "exclude-deps", []string{}, "Deps to skip during installation")
- rootCmd.Flags().StringSliceVar(&replaceConfigs, "replace-configs", []string{}, "Deploy only named configs (e.g. niri,ghostty)")
- rootCmd.Flags().BoolVar(&replaceConfigsAll, "replace-configs-all", false, "Deploy and replace all configurations")
- rootCmd.Flags().BoolVarP(&yes, "yes", "y", false, "Auto-confirm all prompts")
-}
-
-func main() {
- if os.Getuid() == 0 {
- fmt.Fprintln(os.Stderr, "Error: dankinstall must not be run as root")
- os.Exit(1)
- }
-
- if err := rootCmd.Execute(); err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- os.Exit(1)
- }
-}
-
-func runDankinstall(cmd *cobra.Command, args []string) error {
- headlessMode := compositor != "" || term != ""
-
- if !headlessMode {
- // Reject headless-only flags when running in TUI mode.
- headlessOnly := []string{
- "include-deps",
- "exclude-deps",
- "replace-configs",
- "replace-configs-all",
- "yes",
- }
- var set []string
- for _, name := range headlessOnly {
- if cmd.Flags().Changed(name) {
- set = append(set, "--"+name)
- }
- }
- if len(set) > 0 {
- return fmt.Errorf("flags %s are only valid in headless mode (requires both --compositor and --term)", strings.Join(set, ", "))
- }
- }
-
- if headlessMode {
- return runHeadless()
- }
- return runTUI()
-}
-
-func runHeadless() error {
- // Validate required flags
- if compositor == "" {
- return fmt.Errorf("--compositor is required for headless mode (niri or hyprland)")
- }
- if term == "" {
- return fmt.Errorf("--term is required for headless mode (ghostty, kitty, or alacritty)")
- }
-
- cfg := headless.Config{
- Compositor: compositor,
- Terminal: term,
- IncludeDeps: includeDeps,
- ExcludeDeps: excludeDeps,
- ReplaceConfigs: replaceConfigs,
- ReplaceConfigsAll: replaceConfigsAll,
- Yes: yes,
- }
-
- runner := headless.NewRunner(cfg)
-
- // Set up file logging
- fileLogger, err := log.NewFileLogger()
- if err != nil {
- fmt.Fprintf(os.Stderr, "Warning: Failed to create log file: %v\n", err)
- }
-
- if fileLogger != nil {
- fmt.Printf("Logging to: %s\n", fileLogger.GetLogPath())
- fileLogger.StartListening(runner.GetLogChan())
- defer func() {
- if err := fileLogger.Close(); err != nil {
- fmt.Fprintf(os.Stderr, "Warning: Failed to close log file: %v\n", err)
- }
- }()
- } else {
- // Drain the log channel to prevent blocking sends from deadlocking
- // downstream components (distros, config deployer) that write to it.
- // Use an explicit stop signal because this code does not own the
- // runner log channel and cannot assume it will be closed.
- defer drainLogChan(runner.GetLogChan())()
- }
-
- if err := runner.Run(); err != nil {
- if fileLogger != nil {
- fmt.Fprintf(os.Stderr, "\nFull logs are available at: %s\n", fileLogger.GetLogPath())
- }
- return err
- }
-
- if fileLogger != nil {
- fmt.Printf("\nFull logs are available at: %s\n", fileLogger.GetLogPath())
- }
- return nil
-}
-
-func runTUI() error {
- fileLogger, err := log.NewFileLogger()
- if err != nil {
- fmt.Printf("Warning: Failed to create log file: %v\n", err)
- fmt.Println("Continuing without file logging...")
- }
-
- logFilePath := ""
- if fileLogger != nil {
- logFilePath = fileLogger.GetLogPath()
- fmt.Printf("Logging to: %s\n", logFilePath)
- defer func() {
- if err := fileLogger.Close(); err != nil {
- fmt.Printf("Warning: Failed to close log file: %v\n", err)
- }
- }()
- }
-
- model := tui.NewModel(Version, logFilePath)
-
- if fileLogger != nil {
- fileLogger.StartListening(model.GetLogChan())
- } else {
- // Drain the log channel to prevent blocking sends from deadlocking
- // downstream components (distros, config deployer) that write to it.
- // Use an explicit stop signal because this code does not own the
- // model log channel and cannot assume it will be closed.
- defer drainLogChan(model.GetLogChan())()
- }
-
- p := tea.NewProgram(model, tea.WithAltScreen())
- if _, err := p.Run(); err != nil {
- if logFilePath != "" {
- fmt.Fprintf(os.Stderr, "\nFull logs are available at: %s\n", logFilePath)
- }
- return fmt.Errorf("error running program: %w", err)
- }
-
- if logFilePath != "" {
- fmt.Printf("\nFull logs are available at: %s\n", logFilePath)
- }
- return nil
-}
-
-// drainLogChan starts a goroutine that discards all messages from logCh,
-// preventing blocking sends from deadlocking downstream components. It returns
-// a cleanup function that signals the goroutine to stop and waits for it to
-// exit. Callers should defer the returned function.
-func drainLogChan(logCh <-chan string) func() {
- drainStop := make(chan struct{})
- drainDone := make(chan struct{})
- go func() {
- defer close(drainDone)
- for {
- select {
- case <-drainStop:
- return
- case _, ok := <-logCh:
- if !ok {
- return
- }
- }
- }
- }()
- return func() {
- close(drainStop)
- <-drainDone
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/assets/cli-policy.default.json b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/assets/cli-policy.default.json
deleted file mode 100644
index de9b8ad..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/assets/cli-policy.default.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "policy_version": 1,
- "blocked_commands": [
- "greeter install",
- "greeter enable",
- "greeter uninstall",
- "setup"
- ],
- "message": "This command is disabled on immutable/image-based systems. Use your distro-native workflow for system-level changes."
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_auth.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_auth.go
deleted file mode 100644
index 727700e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_auth.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package main
-
-import (
- "fmt"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- sharedpam "github.com/AvengeMedia/DankMaterialShell/core/internal/pam"
- "github.com/spf13/cobra"
-)
-
-var authCmd = &cobra.Command{
- Use: "auth",
- Short: "Manage DMS authentication sync",
- Long: "Manage shared PAM/authentication setup for DMS greeter and lock screen",
-}
-
-var authSyncCmd = &cobra.Command{
- Use: "sync",
- Short: "Sync DMS authentication configuration",
- Long: "Apply shared PAM/authentication changes for the lock screen and greeter based on current DMS settings",
- Run: func(cmd *cobra.Command, args []string) {
- yes, _ := cmd.Flags().GetBool("yes")
- term, _ := cmd.Flags().GetBool("terminal")
- if term {
- if err := syncAuthInTerminal(yes); err != nil {
- log.Fatalf("Error launching auth sync in terminal: %v", err)
- }
- return
- }
- if err := syncAuth(yes); err != nil {
- log.Fatalf("Error syncing authentication: %v", err)
- }
- },
-}
-
-func init() {
- authSyncCmd.Flags().BoolP("yes", "y", false, "Non-interactive mode: skip prompts")
- authSyncCmd.Flags().BoolP("terminal", "t", false, "Run auth sync in a new terminal (for entering sudo password)")
-}
-
-func syncAuth(nonInteractive bool) error {
- if !nonInteractive {
- fmt.Println("=== DMS Authentication Sync ===")
- fmt.Println()
- }
-
- logFunc := func(msg string) {
- fmt.Println(msg)
- }
-
- if err := sharedpam.SyncAuthConfig(logFunc, "", sharedpam.SyncAuthOptions{}); err != nil {
- return err
- }
-
- if !nonInteractive {
- fmt.Println("\n=== Authentication Sync Complete ===")
- fmt.Println("\nAuthentication changes have been applied.")
- }
-
- return nil
-}
-
-func syncAuthInTerminal(nonInteractive bool) error {
- syncFlags := make([]string, 0, 1)
- if nonInteractive {
- syncFlags = append(syncFlags, "--yes")
- }
-
- shellSyncCmd := "dms auth sync"
- if len(syncFlags) > 0 {
- shellSyncCmd += " " + strings.Join(syncFlags, " ")
- }
- shellCmd := shellSyncCmd + `; echo; echo "Authentication sync finished. Closing in 3 seconds..."; sleep 3`
- return runCommandInTerminal(shellCmd)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_blur.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_blur.go
deleted file mode 100644
index d01eb93..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_blur.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/blur"
- "github.com/spf13/cobra"
-)
-
-var blurCmd = &cobra.Command{
- Use: "blur",
- Short: "Background blur utilities",
-}
-
-var blurCheckCmd = &cobra.Command{
- Use: "check",
- Short: "Check if the compositor supports background blur (ext-background-effect-v1)",
- Args: cobra.NoArgs,
- Run: runBlurCheck,
-}
-
-func init() {
- blurCmd.AddCommand(blurCheckCmd)
-}
-
-func runBlurCheck(cmd *cobra.Command, args []string) {
- supported, err := blur.ProbeSupport()
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- os.Exit(1)
- }
-
- switch supported {
- case true:
- fmt.Println("supported")
- default:
- fmt.Println("unsupported")
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_brightness.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_brightness.go
deleted file mode 100644
index 0f73493..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_brightness.go
+++ /dev/null
@@ -1,311 +0,0 @@
-package main
-
-import (
- "fmt"
- "strings"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
- "github.com/spf13/cobra"
-)
-
-var brightnessCmd = &cobra.Command{
- Use: "brightness",
- Short: "Control device brightness",
- Long: "Control brightness for backlight and LED devices (use --ddc to include DDC/I2C monitors)",
-}
-
-var brightnessListCmd = &cobra.Command{
- Use: "list",
- Short: "List all brightness devices",
- Long: "List all available brightness devices with their current values",
- Run: runBrightnessList,
-}
-
-var brightnessSetCmd = &cobra.Command{
- Use: "set <device_id> <percent>",
- Short: "Set brightness for a device",
- Long: "Set brightness percentage (0-100) for a specific device",
- Args: cobra.ExactArgs(2),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) != 0 {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
- includeDDC, _ := cmd.Flags().GetBool("ddc")
- return getBrightnessDevices(includeDDC), cobra.ShellCompDirectiveNoFileComp
- },
- Run: runBrightnessSet,
-}
-
-var brightnessGetCmd = &cobra.Command{
- Use: "get <device_id>",
- Short: "Get brightness for a device",
- Long: "Get current brightness percentage for a specific device",
- Args: cobra.ExactArgs(1),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) != 0 {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
- includeDDC, _ := cmd.Flags().GetBool("ddc")
- return getBrightnessDevices(includeDDC), cobra.ShellCompDirectiveNoFileComp
- },
- Run: runBrightnessGet,
-}
-
-func init() {
- brightnessListCmd.Flags().Bool("ddc", false, "Include DDC/I2C monitors (slower)")
- brightnessSetCmd.Flags().Bool("ddc", false, "Include DDC/I2C monitors (slower)")
- brightnessSetCmd.Flags().Bool("exponential", false, "Use exponential brightness scaling")
- brightnessSetCmd.Flags().Float64("exponent", 1.2, "Exponent for exponential scaling (default 1.2)")
- brightnessGetCmd.Flags().Bool("ddc", false, "Include DDC/I2C monitors (slower)")
-
- brightnessCmd.SetHelpTemplate(`{{.Long}}
-
-Usage:
- {{.UseLine}}{{if .HasAvailableSubCommands}}
-
-Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
- {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
-
-Flags:
-{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
-
-Global Flags:
-{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
-
-Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
- {{rpad .Name .NamePadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
-
-Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
-`)
-
- brightnessListCmd.SetHelpTemplate(`{{.Long}}
-
-Usage:
- {{.UseLine}}{{if .HasAvailableLocalFlags}}
-
-Flags:
-{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
-
-Global Flags:
-{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}
-`)
-
- brightnessSetCmd.SetHelpTemplate(`{{.Long}}
-
-Usage:
- {{.UseLine}}{{if .HasAvailableLocalFlags}}
-
-Flags:
-{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
-
-Global Flags:
-{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}
-`)
-
- brightnessGetCmd.SetHelpTemplate(`{{.Long}}
-
-Usage:
- {{.UseLine}}{{if .HasAvailableLocalFlags}}
-
-Flags:
-{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
-
-Global Flags:
-{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}
-`)
-
- brightnessCmd.AddCommand(brightnessListCmd, brightnessSetCmd, brightnessGetCmd)
-}
-
-func getAllBrightnessDevices(includeDDC bool) []brightness.Device {
- allDevices := []brightness.Device{}
-
- sysfs, err := brightness.NewSysfsBackend()
- if err != nil {
- log.Debugf("Failed to initialize sysfs backend: %v", err)
- } else {
- devices, err := sysfs.GetDevices()
- if err != nil {
- log.Debugf("Failed to get sysfs devices: %v", err)
- } else {
- allDevices = append(allDevices, devices...)
- }
- }
-
- if includeDDC {
- ddc, err := brightness.NewDDCBackend()
- if err != nil {
- fmt.Printf("Warning: Failed to initialize DDC backend: %v\n", err)
- } else {
- time.Sleep(100 * time.Millisecond)
- devices, err := ddc.GetDevices()
- if err != nil {
- fmt.Printf("Warning: Failed to get DDC devices: %v\n", err)
- } else {
- allDevices = append(allDevices, devices...)
- }
- ddc.Close()
- }
- }
-
- return allDevices
-}
-
-func runBrightnessList(cmd *cobra.Command, args []string) {
- includeDDC, _ := cmd.Flags().GetBool("ddc")
- allDevices := getAllBrightnessDevices(includeDDC)
-
- if len(allDevices) == 0 {
- fmt.Println("No brightness devices found")
- return
- }
-
- maxIDLen := len("Device")
- maxNameLen := len("Name")
- for _, dev := range allDevices {
- if len(dev.ID) > maxIDLen {
- maxIDLen = len(dev.ID)
- }
- if len(dev.Name) > maxNameLen {
- maxNameLen = len(dev.Name)
- }
- }
-
- idPad := maxIDLen + 2
- namePad := maxNameLen + 2
-
- fmt.Printf("%-*s %-12s %-*s %s\n", idPad, "Device", "Class", namePad, "Name", "Brightness")
-
- sepLen := idPad + 2 + 12 + 2 + namePad + 2 + 15
- for range sepLen {
- fmt.Print("─")
- }
- fmt.Println()
-
- for _, device := range allDevices {
- fmt.Printf("%-*s %-12s %-*s %3d%%\n",
- idPad,
- device.ID,
- device.Class,
- namePad,
- device.Name,
- device.CurrentPercent,
- )
- }
-}
-
-func runBrightnessSet(cmd *cobra.Command, args []string) {
- deviceID := args[0]
- var percent int
- if _, err := fmt.Sscanf(args[1], "%d", &percent); err != nil {
- log.Fatalf("Invalid percent value: %s", args[1])
- }
-
- if percent < 0 || percent > 100 {
- log.Fatalf("Percent must be between 0 and 100")
- }
-
- includeDDC, _ := cmd.Flags().GetBool("ddc")
- exponential, _ := cmd.Flags().GetBool("exponential")
- exponent, _ := cmd.Flags().GetFloat64("exponent")
-
- parts := strings.SplitN(deviceID, ":", 2)
- if len(parts) == 2 && (parts[0] == "backlight" || parts[0] == "leds") {
- if ok := tryLogindBrightness(parts[0], parts[1], deviceID, percent, exponential, exponent); ok {
- return
- }
- }
-
- sysfs, err := brightness.NewSysfsBackend()
- if err == nil {
- if err := sysfs.SetBrightnessWithExponent(deviceID, percent, exponential, exponent); err == nil {
- fmt.Printf("Set %s to %d%%\n", deviceID, percent)
- return
- }
- log.Debugf("sysfs.SetBrightness failed: %v", err)
- } else {
- log.Debugf("NewSysfsBackend failed: %v", err)
- }
-
- // Try DDC if requested
- if includeDDC {
- ddc, err := brightness.NewDDCBackend()
- if err == nil {
- defer ddc.Close()
- time.Sleep(100 * time.Millisecond)
- if err := ddc.SetBrightnessWithExponent(deviceID, percent, exponential, exponent, nil); err == nil {
- ddc.WaitPending()
- fmt.Printf("Set %s to %d%%\n", deviceID, percent)
- return
- }
- log.Debugf("ddc.SetBrightness failed: %v", err)
- } else {
- log.Debugf("NewDDCBackend failed: %v", err)
- }
- }
-
- log.Fatalf("Failed to set brightness for device: %s", deviceID)
-}
-
-func tryLogindBrightness(subsystem, name, deviceID string, percent int, exponential bool, exponent float64) bool {
- sysfs, err := brightness.NewSysfsBackend()
- if err != nil {
- log.Debugf("NewSysfsBackend failed: %v", err)
- return false
- }
-
- logind, err := brightness.NewLogindBackend()
- if err != nil {
- log.Debugf("NewLogindBackend failed: %v", err)
- return false
- }
- defer logind.Close()
-
- dev, err := sysfs.GetDevice(deviceID)
- if err != nil {
- log.Debugf("sysfs.GetDeviceByID failed: %v", err)
- return false
- }
-
- value := sysfs.PercentToValueWithExponent(percent, dev, exponential, exponent)
- if err := logind.SetBrightness(subsystem, name, uint32(value)); err != nil {
- log.Debugf("logind.SetBrightness failed: %v", err)
- return false
- }
-
- log.Debugf("set %s to %d%% (%d) via logind", deviceID, percent, value)
- fmt.Printf("Set %s to %d%%\n", deviceID, percent)
- return true
-}
-
-func getBrightnessDevices(includeDDC bool) []string {
- allDevices := getAllBrightnessDevices(includeDDC)
-
- var deviceIDs []string
- for _, device := range allDevices {
- deviceIDs = append(deviceIDs, device.ID)
- }
- return deviceIDs
-}
-
-func runBrightnessGet(cmd *cobra.Command, args []string) {
- deviceID := args[0]
- includeDDC, _ := cmd.Flags().GetBool("ddc")
- allDevices := getAllBrightnessDevices(includeDDC)
-
- for _, device := range allDevices {
- if device.ID == deviceID {
- fmt.Printf("%s: %d%% (%d/%d)\n",
- device.ID,
- device.CurrentPercent,
- device.Current,
- device.Max,
- )
- return
- }
- }
-
- log.Fatalf("Device not found: %s", deviceID)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_chroma.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_chroma.go
deleted file mode 100644
index 4945f33..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_chroma.go
+++ /dev/null
@@ -1,300 +0,0 @@
-package main
-
-import (
- "bytes"
- "fmt"
- "io"
- "os"
- "strings"
- "sync"
-
- "github.com/alecthomas/chroma/v2"
- "github.com/alecthomas/chroma/v2/formatters/html"
- "github.com/alecthomas/chroma/v2/lexers"
- "github.com/alecthomas/chroma/v2/styles"
- "github.com/spf13/cobra"
- "github.com/yuin/goldmark"
- highlighting "github.com/yuin/goldmark-highlighting/v2"
- "github.com/yuin/goldmark/extension"
- "github.com/yuin/goldmark/parser"
- ghtml "github.com/yuin/goldmark/renderer/html"
-)
-
-var (
- chromaLanguage string
- chromaStyle string
- chromaInline bool
- chromaMarkdown bool
- chromaLineNumbers bool
-
- // Caching layer for performance
- lexerCache = make(map[string]chroma.Lexer)
- styleCache = make(map[string]*chroma.Style)
- formatterCache = make(map[string]*html.Formatter)
- cacheMutex sync.RWMutex
- maxFileSize = int64(5 * 1024 * 1024) // 5MB default
-)
-
-var chromaCmd = &cobra.Command{
- Use: "chroma [file]",
- Short: "Syntax highlight source code",
- Long: `Generate syntax-highlighted HTML from source code.
-
-Reads from file or stdin, outputs HTML with syntax highlighting.
-Language is auto-detected from filename or can be specified with --language.
-
-Examples:
- dms chroma main.go
- dms chroma --language python script.py
- echo "def foo(): pass" | dms chroma -l python
- cat code.rs | dms chroma -l rust --style dracula
- dms chroma --markdown README.md
- dms chroma --markdown --style github-dark notes.md
- dms chroma list-languages
- dms chroma list-styles`,
- Args: cobra.MaximumNArgs(1),
- Run: runChroma,
-}
-
-var chromaListLanguagesCmd = &cobra.Command{
- Use: "list-languages",
- Short: "List all supported languages",
- Run: func(cmd *cobra.Command, args []string) {
- for _, name := range lexers.Names(true) {
- fmt.Println(name)
- }
- },
-}
-
-var chromaListStylesCmd = &cobra.Command{
- Use: "list-styles",
- Short: "List all available color styles",
- Run: func(cmd *cobra.Command, args []string) {
- for _, name := range styles.Names() {
- fmt.Println(name)
- }
- },
-}
-
-func init() {
- chromaCmd.Flags().StringVarP(&chromaLanguage, "language", "l", "", "Language for highlighting (auto-detect if not specified)")
- chromaCmd.Flags().StringVarP(&chromaStyle, "style", "s", "monokai", "Color style (monokai, dracula, github, etc.)")
- chromaCmd.Flags().BoolVar(&chromaInline, "inline", false, "Output inline styles instead of CSS classes")
- chromaCmd.Flags().BoolVar(&chromaLineNumbers, "line-numbers", false, "Show line numbers in output")
- chromaCmd.Flags().BoolVarP(&chromaMarkdown, "markdown", "m", false, "Render markdown with syntax-highlighted code blocks")
- chromaCmd.Flags().Int64Var(&maxFileSize, "max-size", 5*1024*1024, "Maximum file size to process without warning (bytes)")
-
- chromaCmd.AddCommand(chromaListLanguagesCmd)
- chromaCmd.AddCommand(chromaListStylesCmd)
-}
-
-func getCachedLexer(key string, fallbackFunc func() chroma.Lexer) chroma.Lexer {
- cacheMutex.RLock()
- if lexer, ok := lexerCache[key]; ok {
- cacheMutex.RUnlock()
- return lexer
- }
- cacheMutex.RUnlock()
-
- lexer := fallbackFunc()
- if lexer != nil {
- cacheMutex.Lock()
- lexerCache[key] = lexer
- cacheMutex.Unlock()
- }
- return lexer
-}
-
-func getCachedStyle(name string) *chroma.Style {
- cacheMutex.RLock()
- if style, ok := styleCache[name]; ok {
- cacheMutex.RUnlock()
- return style
- }
- cacheMutex.RUnlock()
-
- style := styles.Get(name)
- if style == nil {
- fmt.Fprintf(os.Stderr, "Warning: Style '%s' not found, using fallback\n", name)
- style = styles.Fallback
- }
-
- cacheMutex.Lock()
- styleCache[name] = style
- cacheMutex.Unlock()
- return style
-}
-
-func getCachedFormatter(inline bool, lineNumbers bool) *html.Formatter {
- key := fmt.Sprintf("inline=%t,lineNumbers=%t", inline, lineNumbers)
-
- cacheMutex.RLock()
- if formatter, ok := formatterCache[key]; ok {
- cacheMutex.RUnlock()
- return formatter
- }
- cacheMutex.RUnlock()
-
- var opts []html.Option
- if inline {
- opts = append(opts, html.WithClasses(false))
- } else {
- opts = append(opts, html.WithClasses(true))
- }
- opts = append(opts, html.TabWidth(4))
-
- if lineNumbers {
- opts = append(opts, html.WithLineNumbers(true))
- opts = append(opts, html.LineNumbersInTable(false))
- opts = append(opts, html.WithLinkableLineNumbers(false, ""))
- }
-
- formatter := html.New(opts...)
-
- cacheMutex.Lock()
- formatterCache[key] = formatter
- cacheMutex.Unlock()
- return formatter
-}
-
-func runChroma(cmd *cobra.Command, args []string) {
- var source string
- var filename string
-
- // Read from file or stdin
- if len(args) > 0 {
- filename = args[0]
-
- // Check file size before reading
- fileInfo, err := os.Stat(filename)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error reading file info: %v\n", err)
- os.Exit(1)
- }
-
- if fileInfo.Size() > maxFileSize {
- fmt.Fprintf(os.Stderr, "Warning: File size (%d bytes) exceeds recommended limit (%d bytes)\n",
- fileInfo.Size(), maxFileSize)
- fmt.Fprintf(os.Stderr, "Processing may be slow. Consider using smaller files.\n")
- }
-
- content, err := os.ReadFile(filename)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
- os.Exit(1)
- }
- source = string(content)
- } else {
- stat, _ := os.Stdin.Stat()
- if (stat.Mode() & os.ModeCharDevice) != 0 {
- _ = cmd.Help()
- os.Exit(0)
- }
-
- content, err := io.ReadAll(os.Stdin)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error reading stdin: %v\n", err)
- os.Exit(1)
- }
- source = string(content)
- }
-
- // Handle empty input
- if strings.TrimSpace(source) == "" {
- return
- }
-
- // Handle Markdown rendering
- if chromaMarkdown {
- md := goldmark.New(
- goldmark.WithExtensions(
- extension.GFM,
- highlighting.NewHighlighting(
- highlighting.WithStyle(chromaStyle),
- highlighting.WithFormatOptions(
- html.WithClasses(!chromaInline),
- ),
- ),
- ),
- goldmark.WithParserOptions(
- parser.WithAutoHeadingID(),
- ),
- goldmark.WithRendererOptions(
- ghtml.WithHardWraps(),
- ghtml.WithXHTML(),
- ),
- )
-
- var buf bytes.Buffer
- if err := md.Convert([]byte(source), &buf); err != nil {
- fmt.Fprintf(os.Stderr, "Markdown rendering error: %v\n", err)
- os.Exit(1)
- }
- fmt.Print(buf.String())
- return
- }
-
- // Detect or use specified lexer
- var lexer chroma.Lexer
- if chromaLanguage != "" {
- lexer = getCachedLexer(chromaLanguage, func() chroma.Lexer {
- l := lexers.Get(chromaLanguage)
- if l == nil {
- fmt.Fprintf(os.Stderr, "Unknown language: %s\n", chromaLanguage)
- os.Exit(1)
- }
- return l
- })
- } else if filename != "" {
- lexer = getCachedLexer("file:"+filename, func() chroma.Lexer {
- return lexers.Match(filename)
- })
- }
-
- // Try content analysis if no lexer found (limit to first 1KB for performance)
- if lexer == nil {
- analyzeContent := source
- if len(source) > 1024 {
- analyzeContent = source[:1024]
- }
- lexer = lexers.Analyse(analyzeContent)
- }
-
- // Fallback to plaintext
- if lexer == nil {
- lexer = lexers.Fallback
- }
-
- lexer = chroma.Coalesce(lexer)
-
- // Get cached style
- style := getCachedStyle(chromaStyle)
-
- // Get cached formatter
- formatter := getCachedFormatter(chromaInline, chromaLineNumbers)
-
- // Tokenize
- iterator, err := lexer.Tokenise(nil, source)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Tokenization error: %v\n", err)
- os.Exit(1)
- }
-
- // Format and output
- if chromaLineNumbers {
- var buf bytes.Buffer
- if err := formatter.Format(&buf, style, iterator); err != nil {
- fmt.Fprintf(os.Stderr, "Formatting error: %v\n", err)
- os.Exit(1)
- }
- // Add spacing between line numbers
- output := buf.String()
- output = strings.ReplaceAll(output, "</span><span>", "</span>\u00A0\u00A0<span>")
- fmt.Print(output)
- } else {
- if err := formatter.Format(os.Stdout, style, iterator); err != nil {
- fmt.Fprintf(os.Stderr, "Formatting error: %v\n", err)
- os.Exit(1)
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_clipboard.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_clipboard.go
deleted file mode 100644
index 8a9b858..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_clipboard.go
+++ /dev/null
@@ -1,1004 +0,0 @@
-package main
-
-import (
- "bytes"
- "context"
- "encoding/base64"
- "encoding/binary"
- "encoding/json"
- "fmt"
- "image"
- _ "image/gif"
- _ "image/jpeg"
- _ "image/png"
- "io"
- "net/http"
- "net/url"
- "os"
- "os/exec"
- "os/signal"
- "path/filepath"
- "strconv"
- "strings"
- "syscall"
- "time"
-
- bolt "go.etcd.io/bbolt"
- _ "golang.org/x/image/bmp"
- _ "golang.org/x/image/tiff"
- _ "golang.org/x/image/webp"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/spf13/cobra"
-)
-
-var clipboardCmd = &cobra.Command{
- Use: "clipboard",
- Aliases: []string{"cl"},
- Short: "Manage clipboard",
- Long: "Interact with the clipboard manager",
-}
-
-var clipCopyCmd = &cobra.Command{
- Use: "copy [text]",
- Short: "Copy text to clipboard",
- Long: "Copy text to clipboard. If no text provided, reads from stdin. Works without server.",
- Run: runClipCopy,
-}
-
-var (
- clipCopyForeground bool
- clipCopyPasteOnce bool
- clipCopyType string
- clipCopyDownload bool
- clipJSONOutput bool
-)
-
-var clipPasteCmd = &cobra.Command{
- Use: "paste",
- Short: "Paste text from clipboard",
- Long: "Paste text from clipboard to stdout. Works without server.",
- Run: runClipPaste,
-}
-
-var clipWatchCmd = &cobra.Command{
- Use: "watch [command]",
- Short: "Watch clipboard for changes",
- Long: `Watch clipboard for changes and optionally execute a command.
-Works like wl-paste --watch. Does not require server.
-
-If a command is provided, it will be executed each time the clipboard changes,
-with the clipboard content piped to its stdin.
-
-Examples:
- dms cl watch # Print clipboard changes to stdout
- dms cl watch cat # Same as above
- dms cl watch notify-send # Send notification on clipboard change`,
- Run: runClipWatch,
-}
-
-var clipHistoryCmd = &cobra.Command{
- Use: "history",
- Short: "Show clipboard history",
- Long: "Show clipboard history with previews (requires server)",
- Run: runClipHistory,
-}
-
-var clipGetCmd = &cobra.Command{
- Use: "get <id>",
- Short: "Get clipboard entry by ID",
- Long: "Get full clipboard entry data by ID (requires server). Use --copy to copy it to clipboard.",
- Args: cobra.ExactArgs(1),
- Run: runClipGet,
-}
-
-var clipGetCopy bool
-
-var clipDeleteCmd = &cobra.Command{
- Use: "delete <id>",
- Short: "Delete clipboard entry",
- Long: "Delete a clipboard history entry by ID (requires server)",
- Args: cobra.ExactArgs(1),
- Run: runClipDelete,
-}
-
-var clipClearCmd = &cobra.Command{
- Use: "clear",
- Short: "Clear clipboard history",
- Long: "Clear all clipboard history (requires server)",
- Run: runClipClear,
-}
-
-var clipWatchStore bool
-var clipWatchMimes bool
-
-var clipSearchCmd = &cobra.Command{
- Use: "search [query]",
- Short: "Search clipboard history",
- Long: "Search clipboard history with filters (requires server)",
- Run: runClipSearch,
-}
-
-var (
- clipSearchLimit int
- clipSearchOffset int
- clipSearchMimeType string
- clipSearchImages bool
- clipSearchText bool
-)
-
-var clipConfigCmd = &cobra.Command{
- Use: "config",
- Short: "Manage clipboard config",
- Long: "Get or set clipboard configuration (requires server)",
-}
-
-var clipConfigGetCmd = &cobra.Command{
- Use: "get",
- Short: "Get clipboard config",
- Run: runClipConfigGet,
-}
-
-var clipConfigSetCmd = &cobra.Command{
- Use: "set",
- Short: "Set clipboard config",
- Long: `Set clipboard configuration options.
-
-Examples:
- dms cl config set --max-history 200
- dms cl config set --auto-clear-days 7
- dms cl config set --clear-at-startup`,
- Run: runClipConfigSet,
-}
-
-var (
- clipConfigMaxHistory int
- clipConfigAutoClearDays int
- clipConfigClearAtStartup bool
- clipConfigNoClearStartup bool
- clipConfigDisabled bool
- clipConfigEnabled bool
-)
-
-var clipExportCmd = &cobra.Command{
- Use: "export [file]",
- Short: "Export clipboard history to JSON",
- Long: "Export clipboard history to JSON file. If no file specified, writes to stdout.",
- Run: runClipExport,
-}
-
-var clipImportCmd = &cobra.Command{
- Use: "import <file>",
- Short: "Import clipboard history from JSON",
- Long: "Import clipboard history from JSON file exported by 'dms cl export'.",
- Args: cobra.ExactArgs(1),
- Run: runClipImport,
-}
-
-var clipMigrateCmd = &cobra.Command{
- Use: "cliphist-migrate [db-path]",
- Short: "Migrate from cliphist",
- Long: "Migrate clipboard history from cliphist. Uses default cliphist path if not specified.",
- Run: runClipMigrate,
-}
-
-var clipMigrateDelete bool
-
-func init() {
- clipCopyCmd.Flags().BoolVarP(&clipCopyForeground, "foreground", "f", false, "Stay in foreground instead of forking")
- clipCopyCmd.Flags().BoolVarP(&clipCopyPasteOnce, "paste-once", "o", false, "Exit after first paste")
- clipCopyCmd.Flags().StringVarP(&clipCopyType, "type", "t", "text/plain;charset=utf-8", "MIME type")
- clipCopyCmd.Flags().BoolVarP(&clipCopyDownload, "download", "d", false, "Download URL as image and copy as file")
-
- clipWatchCmd.Flags().BoolVar(&clipJSONOutput, "json", false, "Output as JSON")
- clipHistoryCmd.Flags().BoolVar(&clipJSONOutput, "json", false, "Output as JSON")
- clipGetCmd.Flags().BoolVar(&clipJSONOutput, "json", false, "Output as JSON")
- clipGetCmd.Flags().BoolVarP(&clipGetCopy, "copy", "C", false, "Copy entry to clipboard")
-
- clipSearchCmd.Flags().IntVarP(&clipSearchLimit, "limit", "l", 50, "Max results")
- clipSearchCmd.Flags().IntVarP(&clipSearchOffset, "offset", "o", 0, "Result offset")
- clipSearchCmd.Flags().StringVarP(&clipSearchMimeType, "mime", "m", "", "Filter by MIME type")
- clipSearchCmd.Flags().BoolVar(&clipSearchImages, "images", false, "Only images")
- clipSearchCmd.Flags().BoolVar(&clipSearchText, "text", false, "Only text")
- clipSearchCmd.Flags().BoolVar(&clipJSONOutput, "json", false, "Output as JSON")
-
- clipConfigSetCmd.Flags().IntVar(&clipConfigMaxHistory, "max-history", 0, "Max history entries")
- clipConfigSetCmd.Flags().IntVar(&clipConfigAutoClearDays, "auto-clear-days", -1, "Auto-clear entries older than N days (0 to disable)")
- clipConfigSetCmd.Flags().BoolVar(&clipConfigClearAtStartup, "clear-at-startup", false, "Clear history on startup")
- clipConfigSetCmd.Flags().BoolVar(&clipConfigNoClearStartup, "no-clear-at-startup", false, "Don't clear history on startup")
- clipConfigSetCmd.Flags().BoolVar(&clipConfigDisabled, "disable", false, "Disable clipboard tracking")
- clipConfigSetCmd.Flags().BoolVar(&clipConfigEnabled, "enable", false, "Enable clipboard tracking")
-
- clipWatchCmd.Flags().BoolVarP(&clipWatchStore, "store", "s", false, "Store clipboard changes to history (no server required)")
- clipWatchCmd.Flags().BoolVarP(&clipWatchMimes, "mimes", "m", false, "Show all offered MIME types")
-
- clipMigrateCmd.Flags().BoolVar(&clipMigrateDelete, "delete", false, "Delete cliphist db after successful migration")
-
- clipConfigCmd.AddCommand(clipConfigGetCmd, clipConfigSetCmd)
- clipboardCmd.AddCommand(clipCopyCmd, clipPasteCmd, clipWatchCmd, clipHistoryCmd, clipGetCmd, clipDeleteCmd, clipClearCmd, clipSearchCmd, clipConfigCmd, clipExportCmd, clipImportCmd, clipMigrateCmd)
-}
-
-func runClipCopy(cmd *cobra.Command, args []string) {
- var data []byte
- copyFromStdin := false
-
- switch {
- case len(args) > 0:
- data = []byte(args[0])
- case clipCopyDownload || clipCopyType == "__multi__":
- var err error
- data, err = io.ReadAll(os.Stdin)
- if err != nil {
- log.Fatalf("read stdin: %v", err)
- }
- default:
- copyFromStdin = true
- }
-
- if clipCopyDownload {
- filePath, err := downloadToTempFile(strings.TrimSpace(string(data)))
- if err != nil {
- log.Fatalf("download: %v", err)
- }
- if err := copyFileToClipboard(filePath); err != nil {
- log.Fatalf("copy file: %v", err)
- }
- fmt.Printf("Downloaded and copied: %s\n", filePath)
- return
- }
-
- if clipCopyType == "__multi__" {
- offers, err := parseMultiOffers(data)
- if err != nil {
- log.Fatalf("parse multi offers: %v", err)
- }
- if err := clipboard.CopyMulti(offers, true, clipCopyPasteOnce); err != nil {
- log.Fatalf("copy multi: %v", err)
- }
- return
- }
-
- if copyFromStdin {
- if err := clipboard.CopyReader(os.Stdin, clipCopyType, clipCopyForeground, clipCopyPasteOnce); err != nil {
- log.Fatalf("copy: %v", err)
- }
- return
- }
-
- if err := clipboard.CopyOpts(data, clipCopyType, clipCopyForeground, clipCopyPasteOnce); err != nil {
- log.Fatalf("copy: %v", err)
- }
-}
-
-func parseMultiOffers(data []byte) ([]clipboard.Offer, error) {
- var offers []clipboard.Offer
- pos := 0
-
- for pos < len(data) {
- mimeEnd := bytes.IndexByte(data[pos:], 0)
- if mimeEnd == -1 {
- break
- }
- mimeType := string(data[pos : pos+mimeEnd])
- pos += mimeEnd + 1
-
- lenEnd := bytes.IndexByte(data[pos:], 0)
- if lenEnd == -1 {
- break
- }
- dataLen, err := strconv.Atoi(string(data[pos : pos+lenEnd]))
- if err != nil {
- return nil, fmt.Errorf("parse length: %w", err)
- }
- pos += lenEnd + 1
-
- if pos+dataLen > len(data) {
- return nil, fmt.Errorf("data truncated")
- }
- offerData := data[pos : pos+dataLen]
- pos += dataLen
-
- offers = append(offers, clipboard.Offer{MimeType: mimeType, Data: offerData})
- }
-
- return offers, nil
-}
-
-func runClipPaste(cmd *cobra.Command, args []string) {
- data, _, err := clipboard.Paste()
- if err != nil {
- log.Fatalf("paste: %v", err)
- }
- os.Stdout.Write(data)
-}
-
-func runClipWatch(cmd *cobra.Command, args []string) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- sigCh := make(chan os.Signal, 1)
- signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
- go func() {
- <-sigCh
- cancel()
- }()
-
- switch {
- case len(args) > 0:
- if err := clipboard.Watch(ctx, func(data []byte, mimeType string) {
- runCommand(args, data)
- }); err != nil && err != context.Canceled {
- log.Fatalf("Watch error: %v", err)
- }
- case clipWatchStore:
- if err := clipboard.Watch(ctx, func(data []byte, mimeType string) {
- if err := clipboard.Store(data, mimeType); err != nil {
- log.Errorf("store: %v", err)
- }
- }); err != nil && err != context.Canceled {
- log.Fatalf("Watch error: %v", err)
- }
- case clipWatchMimes:
- if err := clipboard.WatchAll(ctx, func(data []byte, mimeType string, allMimes []string) {
- if clipJSONOutput {
- out := map[string]any{
- "data": string(data),
- "mimeType": mimeType,
- "mimeTypes": allMimes,
- "timestamp": time.Now().Format(time.RFC3339),
- "size": len(data),
- }
- j, _ := json.Marshal(out)
- fmt.Println(string(j))
- return
- }
- fmt.Printf("=== Clipboard Change ===\n")
- fmt.Printf("Selected: %s\n", mimeType)
- fmt.Printf("All MIME types:\n")
- for _, m := range allMimes {
- fmt.Printf(" - %s\n", m)
- }
- fmt.Printf("Size: %d bytes\n\n", len(data))
- }); err != nil && err != context.Canceled {
- log.Fatalf("Watch error: %v", err)
- }
- case clipJSONOutput:
- if err := clipboard.Watch(ctx, func(data []byte, mimeType string) {
- out := map[string]any{
- "data": string(data),
- "mimeType": mimeType,
- "timestamp": time.Now().Format(time.RFC3339),
- "size": len(data),
- }
- j, _ := json.Marshal(out)
- fmt.Println(string(j))
- }); err != nil && err != context.Canceled {
- log.Fatalf("Watch error: %v", err)
- }
- default:
- if err := clipboard.Watch(ctx, func(data []byte, mimeType string) {
- os.Stdout.Write(data)
- os.Stdout.WriteString("\n")
- }); err != nil && err != context.Canceled {
- log.Fatalf("Watch error: %v", err)
- }
- }
-}
-
-func runCommand(args []string, stdin []byte) {
- cmd := exec.Command(args[0], args[1:]...)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
-
- if len(stdin) == 0 {
- cmd.Run()
- return
- }
-
- r, w, err := os.Pipe()
- if err != nil {
- cmd.Run()
- return
- }
-
- cmd.Stdin = r
- go func() {
- w.Write(stdin)
- w.Close()
- }()
- cmd.Run()
-}
-
-func runClipHistory(cmd *cobra.Command, args []string) {
- req := models.Request{
- ID: 1,
- Method: "clipboard.getHistory",
- }
-
- resp, err := sendServerRequest(req)
- if err != nil {
- log.Fatalf("Failed to get clipboard history: %v", err)
- }
-
- if resp.Error != "" {
- log.Fatalf("Error: %s", resp.Error)
- }
-
- if resp.Result == nil {
- if clipJSONOutput {
- fmt.Println("[]")
- } else {
- fmt.Println("No clipboard history")
- }
- return
- }
-
- historyList, ok := (*resp.Result).([]any)
- if !ok {
- log.Fatal("Invalid response format")
- }
-
- if clipJSONOutput {
- out, _ := json.MarshalIndent(historyList, "", " ")
- fmt.Println(string(out))
- return
- }
-
- if len(historyList) == 0 {
- fmt.Println("No clipboard history")
- return
- }
-
- fmt.Println("Clipboard History:")
- fmt.Println()
-
- for _, item := range historyList {
- entry, ok := item.(map[string]any)
- if !ok {
- continue
- }
-
- id := uint64(entry["id"].(float64))
- preview := entry["preview"].(string)
- timestamp := entry["timestamp"].(string)
- isImage := entry["isImage"].(bool)
-
- typeStr := "text"
- if isImage {
- typeStr = "image"
- }
-
- fmt.Printf("ID: %d | %s | %s\n", id, typeStr, timestamp)
- fmt.Printf(" %s\n", preview)
- fmt.Println()
- }
-}
-
-func runClipGet(cmd *cobra.Command, args []string) {
- id, err := strconv.ParseUint(args[0], 10, 64)
- if err != nil {
- log.Fatalf("Invalid ID: %v", err)
- }
-
- if clipGetCopy {
- req := models.Request{
- ID: 1,
- Method: "clipboard.copyEntry",
- Params: map[string]any{"id": id},
- }
-
- resp, err := sendServerRequest(req)
- if err != nil {
- log.Fatalf("Failed to copy clipboard entry: %v", err)
- }
- if resp.Error != "" {
- log.Fatalf("Error: %s", resp.Error)
- }
-
- fmt.Printf("Copied entry %d to clipboard\n", id)
- return
- }
-
- req := models.Request{
- ID: 1,
- Method: "clipboard.getEntry",
- Params: map[string]any{
- "id": id,
- },
- }
-
- resp, err := sendServerRequest(req)
- if err != nil {
- log.Fatalf("Failed to get clipboard entry: %v", err)
- }
-
- if resp.Error != "" {
- log.Fatalf("Error: %s", resp.Error)
- }
-
- if resp.Result == nil {
- log.Fatal("Entry not found")
- }
-
- entry, ok := (*resp.Result).(map[string]any)
- if !ok {
- log.Fatal("Invalid response format")
- }
-
- switch {
- case clipJSONOutput:
- output, _ := json.MarshalIndent(entry, "", " ")
- fmt.Println(string(output))
- default:
- if data, ok := entry["data"].(string); ok {
- fmt.Print(data)
- } else {
- output, _ := json.MarshalIndent(entry, "", " ")
- fmt.Println(string(output))
- }
- }
-}
-
-func runClipDelete(cmd *cobra.Command, args []string) {
- id, err := strconv.ParseUint(args[0], 10, 64)
- if err != nil {
- log.Fatalf("Invalid ID: %v", err)
- }
-
- req := models.Request{
- ID: 1,
- Method: "clipboard.deleteEntry",
- Params: map[string]any{
- "id": id,
- },
- }
-
- resp, err := sendServerRequest(req)
- if err != nil {
- log.Fatalf("Failed to delete clipboard entry: %v", err)
- }
-
- if resp.Error != "" {
- log.Fatalf("Error: %s", resp.Error)
- }
-
- fmt.Printf("Deleted entry %d\n", id)
-}
-
-func runClipClear(cmd *cobra.Command, args []string) {
- req := models.Request{
- ID: 1,
- Method: "clipboard.clearHistory",
- }
-
- resp, err := sendServerRequest(req)
- if err != nil {
- log.Fatalf("Failed to clear clipboard history: %v", err)
- }
-
- if resp.Error != "" {
- log.Fatalf("Error: %s", resp.Error)
- }
-
- fmt.Println("Clipboard history cleared")
-}
-
-func runClipSearch(cmd *cobra.Command, args []string) {
- params := map[string]any{
- "limit": clipSearchLimit,
- "offset": clipSearchOffset,
- }
-
- if len(args) > 0 {
- params["query"] = args[0]
- }
- if clipSearchMimeType != "" {
- params["mimeType"] = clipSearchMimeType
- }
- if clipSearchImages {
- params["isImage"] = true
- } else if clipSearchText {
- params["isImage"] = false
- }
-
- req := models.Request{
- ID: 1,
- Method: "clipboard.search",
- Params: params,
- }
-
- resp, err := sendServerRequest(req)
- if err != nil {
- log.Fatalf("Failed to search clipboard: %v", err)
- }
-
- if resp.Error != "" {
- log.Fatalf("Error: %s", resp.Error)
- }
-
- if resp.Result == nil {
- log.Fatal("No results")
- }
-
- result, ok := (*resp.Result).(map[string]any)
- if !ok {
- log.Fatal("Invalid response format")
- }
-
- if clipJSONOutput {
- out, _ := json.MarshalIndent(result, "", " ")
- fmt.Println(string(out))
- return
- }
-
- entries, _ := result["entries"].([]any)
- total := int(result["total"].(float64))
- hasMore := result["hasMore"].(bool)
-
- if len(entries) == 0 {
- fmt.Println("No results found")
- return
- }
-
- fmt.Printf("Results: %d of %d\n\n", len(entries), total)
-
- for _, item := range entries {
- entry, ok := item.(map[string]any)
- if !ok {
- continue
- }
-
- id := uint64(entry["id"].(float64))
- preview := entry["preview"].(string)
- timestamp := entry["timestamp"].(string)
- isImage := entry["isImage"].(bool)
-
- typeStr := "text"
- if isImage {
- typeStr = "image"
- }
-
- fmt.Printf("ID: %d | %s | %s\n", id, typeStr, timestamp)
- fmt.Printf(" %s\n\n", preview)
- }
-
- if hasMore {
- fmt.Printf("Use --offset %d to see more results\n", clipSearchOffset+clipSearchLimit)
- }
-}
-
-func runClipConfigGet(cmd *cobra.Command, args []string) {
- req := models.Request{
- ID: 1,
- Method: "clipboard.getConfig",
- }
-
- resp, err := sendServerRequest(req)
- if err != nil {
- log.Fatalf("Failed to get config: %v", err)
- }
-
- if resp.Error != "" {
- log.Fatalf("Error: %s", resp.Error)
- }
-
- if resp.Result == nil {
- log.Fatal("No config returned")
- }
-
- cfg, ok := (*resp.Result).(map[string]any)
- if !ok {
- log.Fatal("Invalid response format")
- }
-
- output, _ := json.MarshalIndent(cfg, "", " ")
- fmt.Println(string(output))
-}
-
-func runClipConfigSet(cmd *cobra.Command, args []string) {
- params := map[string]any{}
-
- if cmd.Flags().Changed("max-history") {
- params["maxHistory"] = clipConfigMaxHistory
- }
- if cmd.Flags().Changed("auto-clear-days") {
- params["autoClearDays"] = clipConfigAutoClearDays
- }
- if clipConfigClearAtStartup {
- params["clearAtStartup"] = true
- }
- if clipConfigNoClearStartup {
- params["clearAtStartup"] = false
- }
- if clipConfigDisabled {
- params["disabled"] = true
- }
- if clipConfigEnabled {
- params["disabled"] = false
- }
-
- if len(params) == 0 {
- fmt.Println("No config options specified")
- return
- }
-
- req := models.Request{
- ID: 1,
- Method: "clipboard.setConfig",
- Params: params,
- }
-
- resp, err := sendServerRequest(req)
- if err != nil {
- log.Fatalf("Failed to set config: %v", err)
- }
-
- if resp.Error != "" {
- log.Fatalf("Error: %s", resp.Error)
- }
-
- fmt.Println("Config updated")
-}
-
-func runClipExport(cmd *cobra.Command, args []string) {
- req := models.Request{
- ID: 1,
- Method: "clipboard.getHistory",
- }
-
- resp, err := sendServerRequest(req)
- if err != nil {
- log.Fatalf("Failed to get clipboard history: %v", err)
- }
- if resp.Error != "" {
- log.Fatalf("Error: %s", resp.Error)
- }
- if resp.Result == nil {
- log.Fatal("No clipboard history")
- }
-
- out, err := json.MarshalIndent(resp.Result, "", " ")
- if err != nil {
- log.Fatalf("Failed to marshal: %v", err)
- }
-
- if len(args) == 0 {
- fmt.Println(string(out))
- return
- }
-
- if err := os.WriteFile(args[0], out, 0o644); err != nil {
- log.Fatalf("Failed to write file: %v", err)
- }
- fmt.Printf("Exported to %s\n", args[0])
-}
-
-func runClipImport(cmd *cobra.Command, args []string) {
- data, err := os.ReadFile(args[0])
- if err != nil {
- log.Fatalf("Failed to read file: %v", err)
- }
-
- var entries []map[string]any
- if err := json.Unmarshal(data, &entries); err != nil {
- log.Fatalf("Failed to parse JSON: %v", err)
- }
-
- var imported int
- for _, entry := range entries {
- dataStr, ok := entry["data"].(string)
- if !ok {
- continue
- }
- mimeType, _ := entry["mimeType"].(string)
- if mimeType == "" {
- mimeType = "text/plain"
- }
-
- var entryData []byte
- if decoded, err := base64.StdEncoding.DecodeString(dataStr); err == nil {
- entryData = decoded
- } else {
- entryData = []byte(dataStr)
- }
-
- if err := clipboard.Store(entryData, mimeType); err != nil {
- log.Errorf("Failed to store entry: %v", err)
- continue
- }
- imported++
- }
-
- fmt.Printf("Imported %d entries\n", imported)
-}
-
-func runClipMigrate(cmd *cobra.Command, args []string) {
- dbPath := getCliphistPath()
- if len(args) > 0 {
- dbPath = args[0]
- }
-
- if _, err := os.Stat(dbPath); err != nil {
- log.Fatalf("Cliphist db not found: %s", dbPath)
- }
-
- db, err := bolt.Open(dbPath, 0o644, &bolt.Options{
- ReadOnly: true,
- Timeout: 1 * time.Second,
- })
- if err != nil {
- log.Fatalf("Failed to open cliphist db: %v", err)
- }
- defer db.Close()
-
- var migrated int
- err = db.View(func(tx *bolt.Tx) error {
- b := tx.Bucket([]byte("b"))
- if b == nil {
- return fmt.Errorf("cliphist bucket not found")
- }
-
- c := b.Cursor()
- for k, v := c.First(); k != nil; k, v = c.Next() {
- if len(v) == 0 {
- continue
- }
-
- mimeType := detectMimeType(v)
- if err := clipboard.Store(v, mimeType); err != nil {
- log.Errorf("Failed to store entry %d: %v", btoi(k), err)
- continue
- }
- migrated++
- }
- return nil
- })
- if err != nil {
- log.Fatalf("Migration failed: %v", err)
- }
-
- fmt.Printf("Migrated %d entries from cliphist\n", migrated)
-
- if !clipMigrateDelete {
- return
- }
-
- db.Close()
- if err := os.Remove(dbPath); err != nil {
- log.Errorf("Failed to delete cliphist db: %v", err)
- return
- }
- os.Remove(filepath.Dir(dbPath))
- fmt.Println("Deleted cliphist db")
-}
-
-func getCliphistPath() string {
- cacheDir, err := os.UserCacheDir()
- if err != nil {
- return filepath.Join(os.Getenv("HOME"), ".cache", "cliphist", "db")
- }
- return filepath.Join(cacheDir, "cliphist", "db")
-}
-
-func detectMimeType(data []byte) string {
- if _, _, err := image.DecodeConfig(bytes.NewReader(data)); err == nil {
- return "image/png"
- }
- return "text/plain"
-}
-
-func btoi(v []byte) uint64 {
- return binary.BigEndian.Uint64(v)
-}
-
-func downloadToTempFile(rawURL string) (string, error) {
- if !strings.HasPrefix(rawURL, "http://") && !strings.HasPrefix(rawURL, "https://") {
- return "", fmt.Errorf("invalid URL: %s", rawURL)
- }
-
- parsedURL, err := url.Parse(rawURL)
- if err != nil {
- return "", fmt.Errorf("parse URL: %w", err)
- }
-
- ext := filepath.Ext(parsedURL.Path)
- if ext == "" {
- ext = ".png"
- }
-
- client := &http.Client{Timeout: 30 * time.Second}
-
- var data []byte
- var contentType string
- var lastErr error
-
- for attempt := 0; attempt < 3; attempt++ {
- if attempt > 0 {
- time.Sleep(time.Duration(attempt) * 500 * time.Millisecond)
- }
-
- req, err := http.NewRequest("GET", rawURL, nil)
- if err != nil {
- lastErr = fmt.Errorf("create request: %w", err)
- continue
- }
- req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
- req.Header.Set("Accept", "image/*,video/*,*/*")
-
- resp, err := client.Do(req)
- if err != nil {
- lastErr = fmt.Errorf("download (attempt %d): %w", attempt+1, err)
- continue
- }
-
- if resp.StatusCode != http.StatusOK {
- resp.Body.Close()
- lastErr = fmt.Errorf("download failed (attempt %d): status %d", attempt+1, resp.StatusCode)
- continue
- }
-
- data, err = io.ReadAll(resp.Body)
- resp.Body.Close()
- if err != nil {
- lastErr = fmt.Errorf("read response (attempt %d): %w", attempt+1, err)
- continue
- }
-
- contentType = resp.Header.Get("Content-Type")
- if idx := strings.Index(contentType, ";"); idx != -1 {
- contentType = strings.TrimSpace(contentType[:idx])
- }
-
- lastErr = nil
- break
- }
-
- if lastErr != nil {
- return "", lastErr
- }
-
- if len(data) == 0 {
- return "", fmt.Errorf("downloaded empty file")
- }
-
- if !strings.HasPrefix(contentType, "image/") && !strings.HasPrefix(contentType, "video/") {
- if _, _, err := image.DecodeConfig(bytes.NewReader(data)); err != nil {
- return "", fmt.Errorf("not a valid media file (content-type: %s)", contentType)
- }
- }
-
- cacheDir, err := os.UserCacheDir()
- if err != nil {
- cacheDir = "/tmp"
- }
- clipDir := filepath.Join(cacheDir, "dms", "clipboard")
- if err := os.MkdirAll(clipDir, 0o755); err != nil {
- return "", fmt.Errorf("create cache dir: %w", err)
- }
-
- filePath := filepath.Join(clipDir, fmt.Sprintf("%d%s", time.Now().UnixNano(), ext))
- if err := os.WriteFile(filePath, data, 0o644); err != nil {
- return "", fmt.Errorf("write file: %w", err)
- }
-
- return filePath, nil
-}
-
-func copyFileToClipboard(filePath string) error {
- req := models.Request{
- ID: 1,
- Method: "clipboard.copyFile",
- Params: map[string]any{"filePath": filePath},
- }
-
- resp, err := sendServerRequest(req)
- if err != nil {
- return fmt.Errorf("server request: %w", err)
- }
- if resp.Error != "" {
- return fmt.Errorf("server error: %s", resp.Error)
- }
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_colorpicker.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_colorpicker.go
deleted file mode 100644
index 7c366e8..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_colorpicker.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/colorpicker"
- "github.com/spf13/cobra"
-)
-
-var (
- colorOutputFmt string
- colorAutocopy bool
- colorNotify bool
- colorLowercase bool
-)
-
-var colorCmd = &cobra.Command{
- Use: "color",
- Short: "Color utilities",
- Long: "Color utilities including picking colors from the screen",
-}
-
-var colorPickCmd = &cobra.Command{
- Use: "pick",
- Short: "Pick a color from the screen",
- Long: `Pick a color from anywhere on your screen using an interactive color picker.
-
-Click on any pixel to capture its color, or press Escape to cancel.
-
-Output format flags (mutually exclusive, default: --hex):
- --hex - Hexadecimal (#RRGGBB)
- --rgb - RGB values (R G B)
- --hsl - HSL values (H S% L%)
- --hsv - HSV values (H S% V%)
- --cmyk - CMYK values (C% M% Y% K%)
- --json - JSON with all formats
-
-Optional:
- --raw - Removes ANSI escape codes and background colors. Use this when piping to other commands
-
-Examples:
- dms color pick # Pick color, output as hex
- dms color pick --rgb # Output as RGB
- dms color pick --json # Output all formats as JSON
- dms color pick --hex -l # Output hex in lowercase
- dms color pick -a # Auto-copy result to clipboard`,
- Run: runColorPick,
-}
-
-func init() {
- colorPickCmd.Flags().Bool("hex", false, "Output as hexadecimal (#RRGGBB)")
- colorPickCmd.Flags().Bool("rgb", false, "Output as RGB (R G B)")
- colorPickCmd.Flags().Bool("hsl", false, "Output as HSL (H S% L%)")
- colorPickCmd.Flags().Bool("hsv", false, "Output as HSV (H S% V%)")
- colorPickCmd.Flags().Bool("cmyk", false, "Output as CMYK (C% M% Y% K%)")
- colorPickCmd.Flags().Bool("json", false, "Output all formats as JSON")
- colorPickCmd.Flags().Bool("raw", false, "Removes ANSI escape codes and background colors. Use this when piping to other commands")
- colorPickCmd.Flags().StringVarP(&colorOutputFmt, "output-format", "o", "", "Custom output format template")
- colorPickCmd.Flags().BoolVarP(&colorAutocopy, "autocopy", "a", false, "Copy result to clipboard")
- colorPickCmd.Flags().BoolVarP(&colorLowercase, "lowercase", "l", false, "Output hex in lowercase")
-
- colorPickCmd.MarkFlagsMutuallyExclusive("hex", "rgb", "hsl", "hsv", "cmyk", "json")
-
- colorCmd.AddCommand(colorPickCmd)
-}
-
-func runColorPick(cmd *cobra.Command, args []string) {
- format := colorpicker.FormatHex // default
- jsonOutput, _ := cmd.Flags().GetBool("json")
-
- if rgb, _ := cmd.Flags().GetBool("rgb"); rgb {
- format = colorpicker.FormatRGB
- } else if hsl, _ := cmd.Flags().GetBool("hsl"); hsl {
- format = colorpicker.FormatHSL
- } else if hsv, _ := cmd.Flags().GetBool("hsv"); hsv {
- format = colorpicker.FormatHSV
- } else if cmyk, _ := cmd.Flags().GetBool("cmyk"); cmyk {
- format = colorpicker.FormatCMYK
- }
-
- config := colorpicker.Config{
- Format: format,
- CustomFormat: colorOutputFmt,
- Lowercase: colorLowercase,
- Autocopy: colorAutocopy,
- Notify: colorNotify,
- }
-
- picker := colorpicker.New(config)
- color, err := picker.Run()
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- os.Exit(1)
- }
-
- if color == nil {
- os.Exit(0)
- }
-
- var output string
- if jsonOutput {
- jsonStr, err := color.ToJSON()
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- os.Exit(1)
- }
- output = jsonStr
- } else {
- output = color.Format(config.Format, config.Lowercase, config.CustomFormat)
- }
-
- if colorAutocopy {
- copyToClipboard(output)
- }
-
- if jsonOutput {
- fmt.Println(output)
- return
- }
-
- if raw, _ := cmd.Flags().GetBool("raw"); raw {
- fmt.Printf("%s\n", output)
- return
- }
-
- if color.IsDark() {
- fmt.Printf("\033[48;2;%d;%d;%dm\033[97m %s \033[0m\n", color.R, color.G, color.B, output)
- } else {
- fmt.Printf("\033[48;2;%d;%d;%dm\033[30m %s \033[0m\n", color.R, color.G, color.B, output)
- }
-}
-
-func copyToClipboard(text string) {
- if err := clipboard.CopyText(text); err != nil {
- fmt.Fprintln(os.Stderr, "clipboard copy failed:", err)
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_common.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_common.go
deleted file mode 100644
index 6557860..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_common.go
+++ /dev/null
@@ -1,530 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
- "regexp"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server"
- "github.com/spf13/cobra"
-)
-
-var versionCmd = &cobra.Command{
- Use: "version",
- Short: "Show version information",
- Run: runVersion,
-}
-
-var runCmd = &cobra.Command{
- Use: "run",
- Short: "Launch quickshell with DMS configuration",
- Long: "Launch quickshell with DMS configuration (qs -c dms)",
- PreRunE: findConfig,
- Run: func(cmd *cobra.Command, args []string) {
- daemon, _ := cmd.Flags().GetBool("daemon")
- session, _ := cmd.Flags().GetBool("session")
- if daemon {
- runShellDaemon(session)
- } else {
- runShellInteractive(session)
- }
- },
-}
-
-var restartCmd = &cobra.Command{
- Use: "restart",
- Short: "Restart quickshell with DMS configuration",
- Long: "Kill existing DMS shell processes and restart quickshell with DMS configuration",
- PreRunE: findConfig,
- Run: func(cmd *cobra.Command, args []string) {
- restartShell()
- },
-}
-
-var restartDetachedCmd = &cobra.Command{
- Use: "restart-detached <pid>",
- Hidden: true,
- Args: cobra.ExactArgs(1),
- PreRunE: findConfig,
- Run: func(cmd *cobra.Command, args []string) {
- runDetachedRestart(args[0])
- },
-}
-
-var killCmd = &cobra.Command{
- Use: "kill",
- Short: "Kill running DMS shell processes",
- Long: "Kill all running quickshell processes with DMS configuration",
- Run: func(cmd *cobra.Command, args []string) {
- killShell()
- },
-}
-
-var ipcCmd = &cobra.Command{
- Use: "ipc [target] [function] [args...]",
- Short: "Send IPC commands to running DMS shell",
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- _ = findConfig(cmd, args)
- return getShellIPCCompletions(args, toComplete), cobra.ShellCompDirectiveNoFileComp
- },
- Run: func(cmd *cobra.Command, args []string) {
- runShellIPCCommand(args)
- },
-}
-
-func init() {
- ipcCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
- _ = findConfig(cmd, args)
- printIPCHelp()
- })
-}
-
-var debugSrvCmd = &cobra.Command{
- Use: "debug-srv",
- Short: "Start the debug server",
- Long: "Start the Unix socket debug server for DMS",
- Run: func(cmd *cobra.Command, args []string) {
- if err := startDebugServer(); err != nil {
- log.Fatalf("Error starting debug server: %v", err)
- }
- },
-}
-
-var pluginsCmd = &cobra.Command{
- Use: "plugins",
- Short: "Manage DMS plugins",
- Long: "Browse and manage DMS plugins from the registry",
-}
-
-var pluginsBrowseCmd = &cobra.Command{
- Use: "browse",
- Short: "Browse available plugins",
- Long: "Browse available plugins from the DMS plugin registry",
- Run: func(cmd *cobra.Command, args []string) {
- if err := browsePlugins(); err != nil {
- log.Fatalf("Error browsing plugins: %v", err)
- }
- },
-}
-
-var pluginsListCmd = &cobra.Command{
- Use: "list",
- Short: "List installed plugins",
- Long: "List all installed DMS plugins",
- Run: func(cmd *cobra.Command, args []string) {
- if err := listInstalledPlugins(); err != nil {
- log.Fatalf("Error listing plugins: %v", err)
- }
- },
-}
-
-var pluginsInstallCmd = &cobra.Command{
- Use: "install <plugin-id>",
- Short: "Install a plugin by ID",
- Long: "Install a DMS plugin from the registry using its ID (e.g., 'myPlugin'). Plugin names with spaces are also supported for backward compatibility.",
- Args: cobra.ExactArgs(1),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) != 0 {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
- return getAvailablePluginIDs(), cobra.ShellCompDirectiveNoFileComp
- },
- Run: func(cmd *cobra.Command, args []string) {
- if err := installPluginCLI(args[0]); err != nil {
- log.Fatalf("Error installing plugin: %v", err)
- }
- },
-}
-
-var pluginsUninstallCmd = &cobra.Command{
- Use: "uninstall <plugin-id>",
- Short: "Uninstall a plugin by ID",
- Long: "Uninstall a DMS plugin using its ID (e.g., 'myPlugin'). Plugin names with spaces are also supported for backward compatibility.",
- Args: cobra.ExactArgs(1),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) != 0 {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
- return getInstalledPluginIDs(), cobra.ShellCompDirectiveNoFileComp
- },
- Run: func(cmd *cobra.Command, args []string) {
- if err := uninstallPluginCLI(args[0]); err != nil {
- log.Fatalf("Error uninstalling plugin: %v", err)
- }
- },
-}
-
-var pluginsUpdateCmd = &cobra.Command{
- Use: "update <plugin-id>",
- Short: "Update a plugin by ID",
- Long: "Update an installed DMS plugin using its ID (e.g., 'myPlugin'). Plugin names are also supported.",
- Args: cobra.ExactArgs(1),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) != 0 {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
- return getInstalledPluginIDs(), cobra.ShellCompDirectiveNoFileComp
- },
- Run: func(cmd *cobra.Command, args []string) {
- if err := updatePluginCLI(args[0]); err != nil {
- log.Fatalf("Error updating plugin: %v", err)
- }
- },
-}
-
-func runVersion(cmd *cobra.Command, args []string) {
- fmt.Printf("%s\n", formatVersion(Version))
-}
-
-// Git builds: dms (git) v0.6.2-XXXX
-// Stable releases: dms v0.6.2
-func formatVersion(version string) string {
- // Arch/Debian/Ubuntu/OpenSUSE git format: 0.6.2+git2264.c5c5ce84
- re := regexp.MustCompile(`^([\d.]+)\+git(\d+)\.`)
- if matches := re.FindStringSubmatch(version); matches != nil {
- return fmt.Sprintf("dms (git) v%s-%s", matches[1], matches[2])
- }
-
- // Fedora COPR git format: 0.0.git.2267.d430cae9
- re = regexp.MustCompile(`^[\d.]+\.git\.(\d+)\.`)
- if matches := re.FindStringSubmatch(version); matches != nil {
- baseVersion := getBaseVersion()
- return fmt.Sprintf("dms (git) v%s-%s", baseVersion, matches[1])
- }
-
- // Stable release format: 0.6.2
- re = regexp.MustCompile(`^([\d.]+)$`)
- if matches := re.FindStringSubmatch(version); matches != nil {
- return fmt.Sprintf("dms v%s", matches[1])
- }
-
- return fmt.Sprintf("dms %s", version)
-}
-
-func getBaseVersion() string {
- paths := []string{
- "/usr/share/quickshell/dms/VERSION",
- "/usr/local/share/quickshell/dms/VERSION",
- "/etc/xdg/quickshell/dms/VERSION",
- }
-
- for _, path := range paths {
- if content, err := os.ReadFile(path); err == nil {
- ver := strings.TrimSpace(string(content))
- ver = strings.TrimPrefix(ver, "v")
- if re := regexp.MustCompile(`^([\d.]+)`); re.MatchString(ver) {
- if matches := re.FindStringSubmatch(ver); matches != nil {
- return matches[1]
- }
- }
- }
- }
-
- // Fallback
- return "1.0.2"
-}
-
-func startDebugServer() error {
- server.CLIVersion = Version
- return server.Start(true)
-}
-
-func browsePlugins() error {
- registry, err := plugins.NewRegistry()
- if err != nil {
- return fmt.Errorf("failed to create registry: %w", err)
- }
-
- manager, err := plugins.NewManager()
- if err != nil {
- return fmt.Errorf("failed to create manager: %w", err)
- }
-
- fmt.Println("Fetching plugin registry...")
- pluginList, err := registry.List()
- if err != nil {
- return fmt.Errorf("failed to list plugins: %w", err)
- }
-
- if len(pluginList) == 0 {
- fmt.Println("No plugins found in registry.")
- return nil
- }
-
- fmt.Printf("\nAvailable Plugins (%d):\n\n", len(pluginList))
- for _, plugin := range pluginList {
- installed, _ := manager.IsInstalled(plugin)
- installedMarker := ""
- if installed {
- installedMarker = " [Installed]"
- }
-
- fmt.Printf(" %s%s\n", plugin.Name, installedMarker)
- fmt.Printf(" ID: %s\n", plugin.ID)
- fmt.Printf(" Category: %s\n", plugin.Category)
- fmt.Printf(" Author: %s\n", plugin.Author)
- fmt.Printf(" Description: %s\n", plugin.Description)
- fmt.Printf(" Repository: %s\n", plugin.Repo)
- if len(plugin.Capabilities) > 0 {
- fmt.Printf(" Capabilities: %s\n", strings.Join(plugin.Capabilities, ", "))
- }
- if len(plugin.Compositors) > 0 {
- fmt.Printf(" Compositors: %s\n", strings.Join(plugin.Compositors, ", "))
- }
- if len(plugin.Dependencies) > 0 {
- fmt.Printf(" Dependencies: %s\n", strings.Join(plugin.Dependencies, ", "))
- }
- fmt.Println()
- }
-
- return nil
-}
-
-func listInstalledPlugins() error {
- manager, err := plugins.NewManager()
- if err != nil {
- return fmt.Errorf("failed to create manager: %w", err)
- }
-
- registry, err := plugins.NewRegistry()
- if err != nil {
- return fmt.Errorf("failed to create registry: %w", err)
- }
-
- installedNames, err := manager.ListInstalled()
- if err != nil {
- return fmt.Errorf("failed to list installed plugins: %w", err)
- }
-
- if len(installedNames) == 0 {
- fmt.Println("No plugins installed.")
- return nil
- }
-
- allPlugins, err := registry.List()
- if err != nil {
- return fmt.Errorf("failed to list plugins: %w", err)
- }
-
- pluginMap := make(map[string]plugins.Plugin)
- for _, p := range allPlugins {
- pluginMap[p.ID] = p
- }
-
- fmt.Printf("\nInstalled Plugins (%d):\n\n", len(installedNames))
- for _, id := range installedNames {
- if plugin, ok := pluginMap[id]; ok {
- fmt.Printf(" %s\n", plugin.Name)
- fmt.Printf(" ID: %s\n", plugin.ID)
- fmt.Printf(" Category: %s\n", plugin.Category)
- fmt.Printf(" Author: %s\n", plugin.Author)
- fmt.Println()
- } else {
- fmt.Printf(" %s (not in registry)\n\n", id)
- }
- }
-
- return nil
-}
-
-func installPluginCLI(idOrName string) error {
- registry, err := plugins.NewRegistry()
- if err != nil {
- return fmt.Errorf("failed to create registry: %w", err)
- }
-
- manager, err := plugins.NewManager()
- if err != nil {
- return fmt.Errorf("failed to create manager: %w", err)
- }
-
- pluginList, err := registry.List()
- if err != nil {
- return fmt.Errorf("failed to list plugins: %w", err)
- }
-
- // First, try to find by ID (preferred method)
- var plugin *plugins.Plugin
- for _, p := range pluginList {
- if p.ID == idOrName {
- plugin = &p
- break
- }
- }
-
- // Fallback to name for backward compatibility
- if plugin == nil {
- for _, p := range pluginList {
- if p.Name == idOrName {
- plugin = &p
- break
- }
- }
- }
-
- if plugin == nil {
- return fmt.Errorf("plugin not found: %s", idOrName)
- }
-
- installed, err := manager.IsInstalled(*plugin)
- if err != nil {
- return fmt.Errorf("failed to check install status: %w", err)
- }
-
- if installed {
- return fmt.Errorf("plugin already installed: %s", plugin.Name)
- }
-
- fmt.Printf("Installing plugin: %s (ID: %s)\n", plugin.Name, plugin.ID)
- if err := manager.Install(*plugin); err != nil {
- return fmt.Errorf("failed to install plugin: %w", err)
- }
-
- fmt.Printf("Plugin installed successfully: %s\n", plugin.Name)
- return nil
-}
-
-func getAvailablePluginIDs() []string {
- registry, err := plugins.NewRegistry()
- if err != nil {
- return nil
- }
-
- pluginList, err := registry.List()
- if err != nil {
- return nil
- }
-
- var ids []string
- for _, p := range pluginList {
- ids = append(ids, p.ID)
- }
- return ids
-}
-
-func getInstalledPluginIDs() []string {
- manager, err := plugins.NewManager()
- if err != nil {
- return nil
- }
-
- installed, err := manager.ListInstalled()
- if err != nil {
- return nil
- }
-
- return installed
-}
-
-func uninstallPluginCLI(idOrName string) error {
- manager, err := plugins.NewManager()
- if err != nil {
- return fmt.Errorf("failed to create manager: %w", err)
- }
-
- registry, err := plugins.NewRegistry()
- if err != nil {
- return fmt.Errorf("failed to create registry: %w", err)
- }
-
- pluginList, _ := registry.List()
- plugin := plugins.FindByIDOrName(idOrName, pluginList)
-
- if plugin != nil {
- installed, err := manager.IsInstalled(*plugin)
- if err != nil {
- return fmt.Errorf("failed to check install status: %w", err)
- }
- if !installed {
- return fmt.Errorf("plugin not installed: %s", plugin.Name)
- }
-
- fmt.Printf("Uninstalling plugin: %s (ID: %s)\n", plugin.Name, plugin.ID)
- if err := manager.Uninstall(*plugin); err != nil {
- return fmt.Errorf("failed to uninstall plugin: %w", err)
- }
- fmt.Printf("Plugin uninstalled successfully: %s\n", plugin.Name)
- return nil
- }
-
- fmt.Printf("Uninstalling plugin: %s\n", idOrName)
- if err := manager.UninstallByIDOrName(idOrName); err != nil {
- return err
- }
- fmt.Printf("Plugin uninstalled successfully: %s\n", idOrName)
- return nil
-}
-
-func updatePluginCLI(idOrName string) error {
- manager, err := plugins.NewManager()
- if err != nil {
- return fmt.Errorf("failed to create manager: %w", err)
- }
-
- registry, err := plugins.NewRegistry()
- if err != nil {
- return fmt.Errorf("failed to create registry: %w", err)
- }
-
- pluginList, _ := registry.List()
- plugin := plugins.FindByIDOrName(idOrName, pluginList)
-
- if plugin != nil {
- installed, err := manager.IsInstalled(*plugin)
- if err != nil {
- return fmt.Errorf("failed to check install status: %w", err)
- }
- if !installed {
- return fmt.Errorf("plugin not installed: %s", plugin.Name)
- }
-
- fmt.Printf("Updating plugin: %s (ID: %s)\n", plugin.Name, plugin.ID)
- if err := manager.Update(*plugin); err != nil {
- return fmt.Errorf("failed to update plugin: %w", err)
- }
- fmt.Printf("Plugin updated successfully: %s\n", plugin.Name)
- return nil
- }
-
- fmt.Printf("Updating plugin: %s\n", idOrName)
- if err := manager.UpdateByIDOrName(idOrName); err != nil {
- return err
- }
- fmt.Printf("Plugin updated successfully: %s\n", idOrName)
- return nil
-}
-
-func getCommonCommands() []*cobra.Command {
- return []*cobra.Command{
- versionCmd,
- runCmd,
- restartCmd,
- restartDetachedCmd,
- killCmd,
- ipcCmd,
- debugSrvCmd,
- pluginsCmd,
- dank16Cmd,
- brightnessCmd,
- dpmsCmd,
- keybindsCmd,
- greeterCmd,
- setupCmd,
- colorCmd,
- screenshotCmd,
- notifyActionCmd,
- notifyCmd,
- genericNotifyActionCmd,
- matugenCmd,
- clipboardCmd,
- chromaCmd,
- doctorCmd,
- configCmd,
- dlCmd,
- randrCmd,
- blurCmd,
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_config.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_config.go
deleted file mode 100644
index d604256..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_config.go
+++ /dev/null
@@ -1,318 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
- "github.com/spf13/cobra"
-)
-
-var configCmd = &cobra.Command{
- Use: "config",
- Short: "Configuration utilities",
-}
-
-var resolveIncludeCmd = &cobra.Command{
- Use: "resolve-include <compositor> <filename>",
- Short: "Check if a file is included in compositor config",
- Long: "Recursively check if a file is included/sourced in compositor configuration. Returns JSON with exists and included status.",
- Args: cobra.ExactArgs(2),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- switch len(args) {
- case 0:
- return []string{"hyprland", "niri", "mangowc"}, cobra.ShellCompDirectiveNoFileComp
- case 1:
- return []string{"cursor.kdl", "cursor.conf", "outputs.kdl", "outputs.conf", "binds.kdl", "binds.conf"}, cobra.ShellCompDirectiveNoFileComp
- }
- return nil, cobra.ShellCompDirectiveNoFileComp
- },
- Run: runResolveInclude,
-}
-
-func init() {
- configCmd.AddCommand(resolveIncludeCmd)
-}
-
-type IncludeResult struct {
- Exists bool `json:"exists"`
- Included bool `json:"included"`
-}
-
-func runResolveInclude(cmd *cobra.Command, args []string) {
- compositor := strings.ToLower(args[0])
- filename := args[1]
-
- var result IncludeResult
- var err error
-
- switch compositor {
- case "hyprland":
- result, err = checkHyprlandInclude(filename)
- case "niri":
- result, err = checkNiriInclude(filename)
- case "mangowc", "dwl", "mango":
- result, err = checkMangoWCInclude(filename)
- default:
- log.Fatalf("Unknown compositor: %s", compositor)
- }
-
- if err != nil {
- log.Fatalf("Error checking include: %v", err)
- }
-
- output, _ := json.Marshal(result)
- fmt.Fprintln(os.Stdout, string(output))
-}
-
-func checkHyprlandInclude(filename string) (IncludeResult, error) {
- configDir, err := utils.ExpandPath("$HOME/.config/hypr")
- if err != nil {
- return IncludeResult{}, err
- }
-
- targetPath := filepath.Join(configDir, "dms", filename)
- result := IncludeResult{}
-
- if _, err := os.Stat(targetPath); err == nil {
- result.Exists = true
- }
-
- mainConfig := filepath.Join(configDir, "hyprland.conf")
- if _, err := os.Stat(mainConfig); os.IsNotExist(err) {
- return result, nil
- }
-
- processed := make(map[string]bool)
- result.Included = hyprlandFindInclude(mainConfig, "dms/"+filename, processed)
- return result, nil
-}
-
-func hyprlandFindInclude(filePath, target string, processed map[string]bool) bool {
- absPath, err := filepath.Abs(filePath)
- if err != nil {
- return false
- }
-
- if processed[absPath] {
- return false
- }
- processed[absPath] = true
-
- data, err := os.ReadFile(absPath)
- if err != nil {
- return false
- }
-
- baseDir := filepath.Dir(absPath)
- lines := strings.Split(string(data), "\n")
-
- for _, line := range lines {
- trimmed := strings.TrimSpace(line)
- if strings.HasPrefix(trimmed, "#") || trimmed == "" {
- continue
- }
-
- if !strings.HasPrefix(trimmed, "source") {
- continue
- }
-
- parts := strings.SplitN(trimmed, "=", 2)
- if len(parts) < 2 {
- continue
- }
-
- sourcePath := strings.TrimSpace(parts[1])
- if matchesTarget(sourcePath, target) {
- return true
- }
-
- fullPath := sourcePath
- if !filepath.IsAbs(sourcePath) {
- fullPath = filepath.Join(baseDir, sourcePath)
- }
-
- expanded, err := utils.ExpandPath(fullPath)
- if err != nil {
- continue
- }
-
- if hyprlandFindInclude(expanded, target, processed) {
- return true
- }
- }
-
- return false
-}
-
-func checkNiriInclude(filename string) (IncludeResult, error) {
- configDir, err := utils.ExpandPath("$HOME/.config/niri")
- if err != nil {
- return IncludeResult{}, err
- }
-
- targetPath := filepath.Join(configDir, "dms", filename)
- result := IncludeResult{}
-
- if _, err := os.Stat(targetPath); err == nil {
- result.Exists = true
- }
-
- mainConfig := filepath.Join(configDir, "config.kdl")
- if _, err := os.Stat(mainConfig); os.IsNotExist(err) {
- return result, nil
- }
-
- processed := make(map[string]bool)
- result.Included = niriFindInclude(mainConfig, "dms/"+filename, processed)
- return result, nil
-}
-
-func niriFindInclude(filePath, target string, processed map[string]bool) bool {
- absPath, err := filepath.Abs(filePath)
- if err != nil {
- return false
- }
-
- if processed[absPath] {
- return false
- }
- processed[absPath] = true
-
- data, err := os.ReadFile(absPath)
- if err != nil {
- return false
- }
-
- baseDir := filepath.Dir(absPath)
- content := string(data)
-
- for _, line := range strings.Split(content, "\n") {
- trimmed := strings.TrimSpace(line)
- if strings.HasPrefix(trimmed, "//") || trimmed == "" {
- continue
- }
-
- if !strings.HasPrefix(trimmed, "include") {
- continue
- }
-
- startQuote := strings.Index(trimmed, "\"")
- if startQuote == -1 {
- continue
- }
- endQuote := strings.LastIndex(trimmed, "\"")
- if endQuote <= startQuote {
- continue
- }
-
- includePath := trimmed[startQuote+1 : endQuote]
- if matchesTarget(includePath, target) {
- return true
- }
-
- fullPath := includePath
- if !filepath.IsAbs(includePath) {
- fullPath = filepath.Join(baseDir, includePath)
- }
-
- if niriFindInclude(fullPath, target, processed) {
- return true
- }
- }
-
- return false
-}
-
-func checkMangoWCInclude(filename string) (IncludeResult, error) {
- configDir, err := utils.ExpandPath("$HOME/.config/mango")
- if err != nil {
- return IncludeResult{}, err
- }
-
- targetPath := filepath.Join(configDir, "dms", filename)
- result := IncludeResult{}
-
- if _, err := os.Stat(targetPath); err == nil {
- result.Exists = true
- }
-
- mainConfig := filepath.Join(configDir, "config.conf")
- if _, err := os.Stat(mainConfig); os.IsNotExist(err) {
- mainConfig = filepath.Join(configDir, "mango.conf")
- }
- if _, err := os.Stat(mainConfig); os.IsNotExist(err) {
- return result, nil
- }
-
- processed := make(map[string]bool)
- result.Included = mangowcFindInclude(mainConfig, "dms/"+filename, processed)
- return result, nil
-}
-
-func mangowcFindInclude(filePath, target string, processed map[string]bool) bool {
- absPath, err := filepath.Abs(filePath)
- if err != nil {
- return false
- }
-
- if processed[absPath] {
- return false
- }
- processed[absPath] = true
-
- data, err := os.ReadFile(absPath)
- if err != nil {
- return false
- }
-
- baseDir := filepath.Dir(absPath)
- lines := strings.Split(string(data), "\n")
-
- for _, line := range lines {
- trimmed := strings.TrimSpace(line)
- if strings.HasPrefix(trimmed, "#") || trimmed == "" {
- continue
- }
-
- if !strings.HasPrefix(trimmed, "source") {
- continue
- }
-
- parts := strings.SplitN(trimmed, "=", 2)
- if len(parts) < 2 {
- continue
- }
-
- sourcePath := strings.TrimSpace(parts[1])
- if matchesTarget(sourcePath, target) {
- return true
- }
-
- fullPath := sourcePath
- if !filepath.IsAbs(sourcePath) {
- fullPath = filepath.Join(baseDir, sourcePath)
- }
-
- expanded, err := utils.ExpandPath(fullPath)
- if err != nil {
- continue
- }
-
- if mangowcFindInclude(expanded, target, processed) {
- return true
- }
- }
-
- return false
-}
-
-func matchesTarget(path, target string) bool {
- path = strings.TrimPrefix(path, "./")
- target = strings.TrimPrefix(target, "./")
- return path == target || strings.HasSuffix(path, "/"+target)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_dank16.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_dank16.go
deleted file mode 100644
index 0bbbab1..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_dank16.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package main
-
-import (
- "fmt"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/dank16"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/spf13/cobra"
-)
-
-var dank16Cmd = &cobra.Command{
- Use: "dank16 [hex_color]",
- Short: "Generate Base16 color palettes",
- Long: "Generate Base16 color palettes from a color with support for various output formats",
- Args: cobra.MaximumNArgs(1),
- Run: runDank16,
-}
-
-func init() {
- dank16Cmd.Flags().Bool("light", false, "Generate light theme variant (sets default to light)")
- dank16Cmd.Flags().Bool("json", false, "Output in JSON format")
- dank16Cmd.Flags().Bool("kitty", false, "Output in Kitty terminal format")
- dank16Cmd.Flags().Bool("foot", false, "Output in Foot terminal format")
- dank16Cmd.Flags().Bool("neovim", false, "Output in Neovim plugin format")
- dank16Cmd.Flags().Bool("alacritty", false, "Output in Alacritty terminal format")
- dank16Cmd.Flags().Bool("ghostty", false, "Output in Ghostty terminal format")
- dank16Cmd.Flags().Bool("wezterm", false, "Output in Wezterm terminal format")
- dank16Cmd.Flags().String("background", "", "Custom background color")
- dank16Cmd.Flags().String("contrast", "dps", "Contrast algorithm: dps (Delta Phi Star, default) or wcag")
- dank16Cmd.Flags().Bool("variants", false, "Output all variants (dark/light/default) in JSON")
- dank16Cmd.Flags().String("primary-dark", "", "Primary color for dark mode (use with --variants)")
- dank16Cmd.Flags().String("primary-light", "", "Primary color for light mode (use with --variants)")
- _ = dank16Cmd.RegisterFlagCompletionFunc("contrast", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- return []string{"dps", "wcag"}, cobra.ShellCompDirectiveNoFileComp
- })
-}
-
-func runDank16(cmd *cobra.Command, args []string) {
- isLight, _ := cmd.Flags().GetBool("light")
- isJson, _ := cmd.Flags().GetBool("json")
- isKitty, _ := cmd.Flags().GetBool("kitty")
- isFoot, _ := cmd.Flags().GetBool("foot")
- isNeovim, _ := cmd.Flags().GetBool("neovim")
- isAlacritty, _ := cmd.Flags().GetBool("alacritty")
- isGhostty, _ := cmd.Flags().GetBool("ghostty")
- isWezterm, _ := cmd.Flags().GetBool("wezterm")
- background, _ := cmd.Flags().GetString("background")
- contrastAlgo, _ := cmd.Flags().GetString("contrast")
- useVariants, _ := cmd.Flags().GetBool("variants")
- primaryDark, _ := cmd.Flags().GetString("primary-dark")
- primaryLight, _ := cmd.Flags().GetString("primary-light")
-
- if background != "" && !strings.HasPrefix(background, "#") {
- background = "#" + background
- }
- if primaryDark != "" && !strings.HasPrefix(primaryDark, "#") {
- primaryDark = "#" + primaryDark
- }
- if primaryLight != "" && !strings.HasPrefix(primaryLight, "#") {
- primaryLight = "#" + primaryLight
- }
-
- contrastAlgo = strings.ToLower(contrastAlgo)
- if contrastAlgo != "dps" && contrastAlgo != "wcag" {
- log.Fatalf("Invalid contrast algorithm: %s (must be 'dps' or 'wcag')", contrastAlgo)
- }
-
- if useVariants {
- if primaryDark == "" || primaryLight == "" {
- if len(args) == 0 {
- log.Fatalf("--variants requires either a positional color argument or both --primary-dark and --primary-light")
- }
- primaryColor := args[0]
- if !strings.HasPrefix(primaryColor, "#") {
- primaryColor = "#" + primaryColor
- }
- primaryDark = primaryColor
- primaryLight = primaryColor
- }
- variantOpts := dank16.VariantOptions{
- PrimaryDark: primaryDark,
- PrimaryLight: primaryLight,
- Background: background,
- UseDPS: contrastAlgo == "dps",
- IsLightMode: isLight,
- }
- variantColors := dank16.GenerateVariantPalette(variantOpts)
- fmt.Print(dank16.GenerateVariantJSON(variantColors))
- return
- }
-
- if len(args) == 0 {
- log.Fatalf("A color argument is required (or use --variants with --primary-dark and --primary-light)")
- }
- primaryColor := args[0]
- if !strings.HasPrefix(primaryColor, "#") {
- primaryColor = "#" + primaryColor
- }
-
- opts := dank16.PaletteOptions{
- IsLight: isLight,
- Background: background,
- UseDPS: contrastAlgo == "dps",
- }
-
- colors := dank16.GeneratePalette(primaryColor, opts)
-
- if isJson {
- fmt.Print(dank16.GenerateJSON(colors))
- } else if isKitty {
- fmt.Print(dank16.GenerateKittyTheme(colors))
- } else if isFoot {
- fmt.Print(dank16.GenerateFootTheme(colors))
- } else if isAlacritty {
- fmt.Print(dank16.GenerateAlacrittyTheme(colors))
- } else if isGhostty {
- fmt.Print(dank16.GenerateGhosttyTheme(colors))
- } else if isWezterm {
- fmt.Print(dank16.GenerateWeztermTheme(colors))
- } else if isNeovim {
- fmt.Print(dank16.GenerateNeovimTheme(colors))
- } else {
- fmt.Print(dank16.GenerateGhosttyTheme(colors))
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_doctor.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_doctor.go
deleted file mode 100644
index b182d06..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_doctor.go
+++ /dev/null
@@ -1,1107 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "runtime"
- "slices"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/config"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/version"
- "github.com/charmbracelet/lipgloss"
- "github.com/spf13/cobra"
-)
-
-type status string
-
-const (
- statusOK status = "ok"
- statusWarn status = "warn"
- statusError status = "error"
- statusInfo status = "info"
-)
-
-func (s status) IconStyle(styles tui.Styles) (string, lipgloss.Style) {
- switch s {
- case statusOK:
- return "●", styles.Success
- case statusWarn:
- return "●", styles.Warning
- case statusError:
- return "●", styles.Error
- default:
- return "○", styles.Subtle
- }
-}
-
-type DoctorStatus struct {
- Errors []checkResult
- Warnings []checkResult
- OK []checkResult
- Info []checkResult
-}
-
-func (ds *DoctorStatus) Add(r checkResult) {
- switch r.status {
- case statusError:
- ds.Errors = append(ds.Errors, r)
- case statusWarn:
- ds.Warnings = append(ds.Warnings, r)
- case statusOK:
- ds.OK = append(ds.OK, r)
- case statusInfo:
- ds.Info = append(ds.Info, r)
- }
-}
-
-func (ds *DoctorStatus) HasIssues() bool {
- return len(ds.Errors) > 0 || len(ds.Warnings) > 0
-}
-
-func (ds *DoctorStatus) ErrorCount() int {
- return len(ds.Errors)
-}
-
-func (ds *DoctorStatus) WarningCount() int {
- return len(ds.Warnings)
-}
-
-func (ds *DoctorStatus) OKCount() int {
- return len(ds.OK)
-}
-
-var (
- quickshellVersionRegex = regexp.MustCompile(`(?i)quickshell (\d+\.\d+\.\d+)`)
- hyprlandVersionRegex = regexp.MustCompile(`v?(\d+\.\d+\.\d+)`)
- niriVersionRegex = regexp.MustCompile(`niri (\d+\.\d+)`)
- swayVersionRegex = regexp.MustCompile(`sway version (\d+\.\d+)`)
- riverVersionRegex = regexp.MustCompile(`river (\d+\.\d+)`)
- wayfireVersionRegex = regexp.MustCompile(`wayfire (\d+\.\d+)`)
- labwcVersionRegex = regexp.MustCompile(`labwc (\d+\.\d+\.\d+)`)
- mangowcVersionRegex = regexp.MustCompile(`mango (\d+\.\d+\.\d+)`)
-)
-
-var doctorCmd = &cobra.Command{
- Use: "doctor",
- Short: "Diagnose DMS installation and dependencies",
- Long: "Check system health, verify dependencies, and diagnose configuration issues for DMS",
- Run: runDoctor,
-}
-
-var (
- doctorVerbose bool
- doctorJSON bool
- doctorCopy bool
-)
-
-func init() {
- doctorCmd.Flags().BoolVarP(&doctorVerbose, "verbose", "v", false, "Show detailed output including paths and versions")
- doctorCmd.Flags().BoolVarP(&doctorJSON, "json", "j", false, "Output results in JSON format")
- doctorCmd.Flags().BoolVarP(&doctorCopy, "copy", "C", false, "Copy results to clipboard in GitHub-friendly format")
-}
-
-type category int
-
-const (
- catSystem category = iota
- catVersions
- catInstallation
- catCompositor
- catQuickshellFeatures
- catOptionalFeatures
- catConfigFiles
- catServices
- catEnvironment
-)
-
-func (c category) String() string {
- switch c {
- case catSystem:
- return "System"
- case catVersions:
- return "Versions"
- case catInstallation:
- return "Installation"
- case catCompositor:
- return "Compositor"
- case catQuickshellFeatures:
- return "Quickshell Features"
- case catOptionalFeatures:
- return "Optional Features"
- case catConfigFiles:
- return "Config Files"
- case catServices:
- return "Services"
- case catEnvironment:
- return "Environment"
- default:
- return "Unknown"
- }
-}
-
-const (
- checkNameMaxLength = 21
- doctorDocsURL = "https://danklinux.com/docs/dankmaterialshell/cli-doctor"
-)
-
-type checkResult struct {
- category category
- name string
- status status
- message string
- details string
- url string
-}
-
-type checkResultJSON struct {
- Category string `json:"category"`
- Name string `json:"name"`
- Status string `json:"status"`
- Message string `json:"message"`
- Details string `json:"details,omitempty"`
- URL string `json:"url,omitempty"`
-}
-
-type doctorOutputJSON struct {
- Summary struct {
- Errors int `json:"errors"`
- Warnings int `json:"warnings"`
- OK int `json:"ok"`
- Info int `json:"info"`
- } `json:"summary"`
- Results []checkResultJSON `json:"results"`
-}
-
-func (r checkResult) toJSON() checkResultJSON {
- return checkResultJSON{
- Category: r.category.String(),
- Name: r.name,
- Status: string(r.status),
- Message: r.message,
- Details: r.details,
- URL: r.url,
- }
-}
-
-func runDoctor(cmd *cobra.Command, args []string) {
- if !doctorJSON && !doctorCopy {
- printDoctorHeader()
- }
-
- qsFeatures, qsMissingFeatures := checkQuickshellFeatures()
-
- results := slices.Concat(
- checkSystemInfo(),
- checkVersions(qsMissingFeatures),
- checkDMSInstallation(),
- checkWindowManagers(),
- qsFeatures,
- checkOptionalDependencies(),
- checkConfigurationFiles(),
- checkSystemdServices(),
- checkEnvironmentVars(),
- )
-
- switch {
- case doctorCopy:
- text := formatResultsPlain(results)
- if err := clipboard.CopyOpts([]byte(text), "text/plain;charset=utf-8", false, false); err != nil {
- fmt.Fprintf(os.Stderr, "Failed to copy to clipboard: %v\n", err)
- os.Exit(1)
- }
- fmt.Println("Doctor report copied to clipboard")
- case doctorJSON:
- printResultsJSON(results)
- default:
- printResults(results)
- printSummary(results, qsMissingFeatures)
- }
-}
-
-func printDoctorHeader() {
- theme := tui.TerminalTheme()
- styles := tui.NewStyles(theme)
-
- fmt.Println(getThemedASCII())
- fmt.Println(styles.Title.Render("System Health Check"))
- fmt.Println(styles.Subtle.Render("──────────────────────────────────────"))
- fmt.Println()
-}
-
-func checkSystemInfo() []checkResult {
- var results []checkResult
-
- osInfo, err := distros.GetOSInfo()
- if err != nil {
- status, message, details := statusWarn, fmt.Sprintf("Unknown (%v)", err), ""
-
- if strings.Contains(err.Error(), "Unsupported distribution") {
- osRelease := readOSRelease()
- switch {
- case osRelease["ID"] == "nixos":
- status = statusOK
- message = osRelease["PRETTY_NAME"]
- if message == "" {
- message = fmt.Sprintf("NixOS %s", osRelease["VERSION_ID"])
- }
- details = "Supported for runtime (install via NixOS module or Flake)"
- case osRelease["PRETTY_NAME"] != "":
- message = fmt.Sprintf("%s (not supported by dms setup)", osRelease["PRETTY_NAME"])
- details = "DMS may work but automatic installation is not available"
- }
- }
-
- results = append(results, checkResult{catSystem, "Operating System", status, message, details, doctorDocsURL + "#operating-system"})
- } else {
- status := statusOK
- message := osInfo.PrettyName
- if message == "" {
- message = fmt.Sprintf("%s %s", osInfo.Distribution.ID, osInfo.VersionID)
- }
- if distros.IsUnsupportedDistro(osInfo.Distribution.ID, osInfo.VersionID) {
- status = statusWarn
- message += " (version may not be fully supported)"
- }
- results = append(results, checkResult{
- catSystem, "Operating System", status, message,
- fmt.Sprintf("ID: %s, Version: %s, Arch: %s", osInfo.Distribution.ID, osInfo.VersionID, osInfo.Architecture),
- doctorDocsURL + "#operating-system",
- })
- }
-
- arch := runtime.GOARCH
- archStatus := statusOK
- if arch != "amd64" && arch != "arm64" {
- archStatus = statusError
- }
- results = append(results, checkResult{catSystem, "Architecture", archStatus, arch, "", doctorDocsURL + "#architecture"})
-
- waylandDisplay := os.Getenv("WAYLAND_DISPLAY")
- xdgSessionType := os.Getenv("XDG_SESSION_TYPE")
-
- switch {
- case waylandDisplay != "" || xdgSessionType == "wayland":
- results = append(results, checkResult{
- catSystem, "Display Server", statusOK, "Wayland",
- fmt.Sprintf("WAYLAND_DISPLAY=%s", waylandDisplay),
- doctorDocsURL + "#display-server",
- })
- case xdgSessionType == "x11":
- results = append(results, checkResult{catSystem, "Display Server", statusError, "X11 (DMS requires Wayland)", "", doctorDocsURL + "#display-server"})
- default:
- results = append(results, checkResult{
- catSystem, "Display Server", statusWarn, "Unknown (ensure you're running Wayland)",
- fmt.Sprintf("XDG_SESSION_TYPE=%s", xdgSessionType),
- doctorDocsURL + "#display-server",
- })
- }
-
- return results
-}
-
-func checkEnvironmentVars() []checkResult {
- var results []checkResult
- results = append(results, checkEnvVar("QT_QPA_PLATFORMTHEME")...)
- results = append(results, checkEnvVar("QS_ICON_THEME")...)
- return results
-}
-
-func checkEnvVar(name string) []checkResult {
- value := os.Getenv(name)
- if value != "" {
- return []checkResult{{catEnvironment, name, statusInfo, value, "", doctorDocsURL + "#environment-variables"}}
- }
- if doctorVerbose {
- return []checkResult{{catEnvironment, name, statusInfo, "Not set", "", doctorDocsURL + "#environment-variables"}}
- }
- return nil
-}
-
-func readOSRelease() map[string]string {
- result := make(map[string]string)
- data, err := os.ReadFile("/etc/os-release")
- if err != nil {
- return result
- }
- for line := range strings.SplitSeq(string(data), "\n") {
- if parts := strings.SplitN(line, "=", 2); len(parts) == 2 {
- result[parts[0]] = strings.Trim(parts[1], "\"")
- }
- }
- return result
-}
-
-func checkVersions(qsMissingFeatures bool) []checkResult {
- dmsCliPath, _ := os.Executable()
- dmsCliDetails := ""
- if doctorVerbose {
- dmsCliDetails = dmsCliPath
- }
-
- results := []checkResult{
- {catVersions, "DMS CLI", statusOK, formatVersion(Version), dmsCliDetails, doctorDocsURL + "#dms-cli"},
- }
-
- qsVersion, qsStatus, qsPath := getQuickshellVersionInfo(qsMissingFeatures)
- qsDetails := ""
- if doctorVerbose && qsPath != "" {
- qsDetails = qsPath
- }
- results = append(results, checkResult{catVersions, "Quickshell", qsStatus, qsVersion, qsDetails, doctorDocsURL + "#quickshell"})
-
- dmsVersion, dmsPath := getDMSShellVersion()
- if dmsVersion != "" {
- results = append(results, checkResult{catVersions, "DMS Shell", statusOK, dmsVersion, dmsPath, doctorDocsURL + "#dms-shell"})
- } else {
- results = append(results, checkResult{catVersions, "DMS Shell", statusError, "Not installed or not detected", "Run 'dms setup' to install", doctorDocsURL + "#dms-shell"})
- }
-
- return results
-}
-
-func getDMSShellVersion() (version, path string) {
- if err := findConfig(nil, nil); err == nil && configPath != "" {
- versionFile := filepath.Join(configPath, "VERSION")
- if data, err := os.ReadFile(versionFile); err == nil {
- return strings.TrimSpace(string(data)), configPath
- }
- return "installed", configPath
- }
-
- if dmsPath, err := config.LocateDMSConfig(); err == nil {
- versionFile := filepath.Join(dmsPath, "VERSION")
- if data, err := os.ReadFile(versionFile); err == nil {
- return strings.TrimSpace(string(data)), dmsPath
- }
- return "installed", dmsPath
- }
-
- return "", ""
-}
-
-func getQuickshellVersionInfo(missingFeatures bool) (string, status, string) {
- if !utils.CommandExists("qs") {
- return "Not installed", statusError, ""
- }
-
- qsPath, _ := exec.LookPath("qs")
-
- output, err := exec.Command("qs", "--version").Output()
- if err != nil {
- return "Installed (version check failed)", statusWarn, qsPath
- }
-
- fullVersion := strings.TrimSpace(string(output))
- if matches := quickshellVersionRegex.FindStringSubmatch(fullVersion); len(matches) >= 2 {
- if version.CompareVersions(matches[1], "0.2.0") < 0 {
- return fmt.Sprintf("%s (needs >= 0.2.0)", fullVersion), statusError, qsPath
- }
- if missingFeatures {
- return fullVersion, statusWarn, qsPath
- }
- return fullVersion, statusOK, qsPath
- }
-
- return fullVersion, statusWarn, qsPath
-}
-
-func checkDMSInstallation() []checkResult {
- var results []checkResult
-
- dmsPath := ""
- if err := findConfig(nil, nil); err == nil && configPath != "" {
- dmsPath = configPath
- } else if path, err := config.LocateDMSConfig(); err == nil {
- dmsPath = path
- }
-
- if dmsPath == "" {
- return []checkResult{{catInstallation, "DMS Configuration", statusError, "Not found", "shell.qml not found in any config path", doctorDocsURL + "#dms-configuration"}}
- }
-
- results = append(results, checkResult{catInstallation, "DMS Configuration", statusOK, "Found", dmsPath, doctorDocsURL + "#dms-configuration"})
-
- shellQml := filepath.Join(dmsPath, "shell.qml")
- if _, err := os.Stat(shellQml); err != nil {
- results = append(results, checkResult{catInstallation, "shell.qml", statusError, "Missing", shellQml, doctorDocsURL + "#dms-configuration"})
- } else {
- results = append(results, checkResult{catInstallation, "shell.qml", statusOK, "Present", shellQml, doctorDocsURL + "#dms-configuration"})
- }
-
- if doctorVerbose {
- installType := "Unknown"
- switch {
- case strings.Contains(dmsPath, "/nix/store"):
- installType = "Nix store"
- case strings.Contains(dmsPath, ".local/share") || strings.Contains(dmsPath, "/usr/share"):
- installType = "System package"
- case strings.Contains(dmsPath, ".config"):
- installType = "User config"
- }
- results = append(results, checkResult{catInstallation, "Install Type", statusInfo, installType, dmsPath, doctorDocsURL + "#dms-configuration"})
- }
-
- return results
-}
-
-func checkWindowManagers() []checkResult {
- compositors := []struct {
- name, versionCmd, versionArg string
- versionRegex *regexp.Regexp
- commands []string
- }{
- {"Hyprland", "Hyprland", "--version", hyprlandVersionRegex, []string{"hyprland", "Hyprland"}},
- {"niri", "niri", "--version", niriVersionRegex, []string{"niri"}},
- {"Sway", "sway", "--version", swayVersionRegex, []string{"sway"}},
- {"River", "river", "-version", riverVersionRegex, []string{"river"}},
- {"Wayfire", "wayfire", "--version", wayfireVersionRegex, []string{"wayfire"}},
- {"labwc", "labwc", "--version", labwcVersionRegex, []string{"labwc"}},
- {"mangowc", "mango", "-v", mangowcVersionRegex, []string{"mango"}},
- }
-
- var results []checkResult
- foundAny := false
-
- for _, c := range compositors {
- if !slices.ContainsFunc(c.commands, utils.CommandExists) {
- continue
- }
- foundAny = true
- var compositorPath string
- for _, cmd := range c.commands {
- if path, err := exec.LookPath(cmd); err == nil {
- compositorPath = path
- break
- }
- }
- details := ""
- if doctorVerbose && compositorPath != "" {
- details = compositorPath
- }
- results = append(results, checkResult{
- catCompositor, c.name, statusOK,
- getVersionFromCommand(c.versionCmd, c.versionArg, c.versionRegex), details,
- doctorDocsURL + "#compositor-checks",
- })
- }
-
- if !foundAny {
- results = append(results, checkResult{
- catCompositor, "Compositor", statusError,
- "No supported Wayland compositor found",
- "Install Hyprland, niri, Sway, River, or Wayfire",
- doctorDocsURL + "#compositor-checks",
- })
- }
-
- if wm := detectRunningWM(); wm != "" {
- results = append(results, checkResult{catCompositor, "Active", statusInfo, wm, "", doctorDocsURL + "#compositor"})
- }
-
- return results
-}
-
-func getVersionFromCommand(cmd, arg string, regex *regexp.Regexp) string {
- output, err := exec.Command(cmd, arg).CombinedOutput()
- if err != nil && len(output) == 0 {
- return "installed"
- }
-
- outStr := string(output)
- if matches := regex.FindStringSubmatch(outStr); len(matches) > 1 {
- ver := matches[1]
- if strings.Contains(outStr, "git") || strings.Contains(outStr, "dirty") {
- return ver + " (git)"
- }
- return ver
- }
- return strings.TrimSpace(outStr)
-}
-
-func detectRunningWM() string {
- switch {
- case os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") != "":
- return "Hyprland"
- case os.Getenv("NIRI_SOCKET") != "":
- return "niri"
- case os.Getenv("XDG_CURRENT_DESKTOP") != "":
- return os.Getenv("XDG_CURRENT_DESKTOP")
- }
- return ""
-}
-
-func checkQuickshellFeatures() ([]checkResult, bool) {
- if !utils.CommandExists("qs") {
- return nil, false
- }
-
- tmpDir := os.TempDir()
- testScript := filepath.Join(tmpDir, "qs-feature-test.qml")
- defer os.Remove(testScript)
-
- qmlContent := `
-import QtQuick
-import Quickshell
-
-ShellRoot {
- id: root
-
- property bool polkitAvailable: false
- property bool idleMonitorAvailable: false
- property bool idleInhibitorAvailable: false
- property bool shortcutInhibitorAvailable: false
-
- Timer {
- interval: 50
- running: true
- repeat: false
- onTriggered: {
- try {
- var polkitTest = Qt.createQmlObject(
- 'import Quickshell.Services.Polkit; import QtQuick; Item {}',
- root
- )
- root.polkitAvailable = true
- polkitTest.destroy()
- } catch (e) {}
-
- try {
- var testItem = Qt.createQmlObject(
- 'import Quickshell.Wayland; import QtQuick; QtObject { ' +
- 'readonly property bool hasIdleMonitor: typeof IdleMonitor !== "undefined"; ' +
- 'readonly property bool hasIdleInhibitor: typeof IdleInhibitor !== "undefined"; ' +
- 'readonly property bool hasShortcutInhibitor: typeof ShortcutInhibitor !== "undefined" ' +
- '}',
- root
- )
- root.idleMonitorAvailable = testItem.hasIdleMonitor
- root.idleInhibitorAvailable = testItem.hasIdleInhibitor
- root.shortcutInhibitorAvailable = testItem.hasShortcutInhibitor
- testItem.destroy()
- } catch (e) {}
-
- console.warn(root.polkitAvailable ? "FEATURE:Polkit:OK" : "FEATURE:Polkit:UNAVAILABLE")
- console.warn(root.idleMonitorAvailable ? "FEATURE:IdleMonitor:OK" : "FEATURE:IdleMonitor:UNAVAILABLE")
- console.warn(root.idleInhibitorAvailable ? "FEATURE:IdleInhibitor:OK" : "FEATURE:IdleInhibitor:UNAVAILABLE")
- console.warn(root.shortcutInhibitorAvailable ? "FEATURE:ShortcutInhibitor:OK" : "FEATURE:ShortcutInhibitor:UNAVAILABLE")
-
- Quickshell.execDetached(["kill", "-TERM", String(Quickshell.processId)])
- }
- }
-}
-`
-
- if err := os.WriteFile(testScript, []byte(qmlContent), 0o644); err != nil {
- return nil, false
- }
-
- cmd := exec.Command("qs", "-p", testScript)
- cmd.Env = append(os.Environ(), "NO_COLOR=1")
- output, _ := cmd.CombinedOutput()
- outputStr := string(output)
-
- features := []struct{ name, desc string }{
- {"Polkit", "Authentication prompts"},
- {"IdleMonitor", "Idle detection"},
- {"IdleInhibitor", "Prevent idle/sleep"},
- {"ShortcutInhibitor", "Allow shortcut management (niri)"},
- }
-
- var results []checkResult
- missingFeatures := false
-
- for _, f := range features {
- available := strings.Contains(outputStr, fmt.Sprintf("FEATURE:%s:OK", f.name))
- status, message := statusOK, "Available"
- if !available {
- status, message = statusInfo, "Not available"
- missingFeatures = true
- }
- results = append(results, checkResult{catQuickshellFeatures, f.name, status, message, f.desc, doctorDocsURL + "#quickshell-features"})
- }
-
- return results, missingFeatures
-}
-
-func checkI2CAvailability() checkResult {
- ddc, err := brightness.NewDDCBackend()
- if err != nil {
- return checkResult{catOptionalFeatures, "I2C/DDC", statusInfo, "Not available", "External monitor brightness control", doctorDocsURL + "#optional-features"}
- }
- defer ddc.Close()
-
- devices, err := ddc.GetDevices()
- if err != nil || len(devices) == 0 {
- return checkResult{catOptionalFeatures, "I2C/DDC", statusInfo, "No monitors detected", "External monitor brightness control", doctorDocsURL + "#optional-features"}
- }
-
- return checkResult{catOptionalFeatures, "I2C/DDC", statusOK, fmt.Sprintf("%d monitor(s) detected", len(devices)), "External monitor brightness control", doctorDocsURL + "#optional-features"}
-}
-
-func checkImageFormatPlugins() []checkResult {
- url := doctorDocsURL + "#optional-features"
-
- pluginDirs := findQtPluginDirs()
- if len(pluginDirs) == 0 {
- return []checkResult{
- {catOptionalFeatures, "qt6-imageformats", statusInfo, "Cannot detect (plugin dir not found)", "WebP, TIFF, JP2 support", url},
- {catOptionalFeatures, "kimageformats", statusInfo, "Cannot detect (plugin dir not found)", "AVIF, HEIF, JXL support", url},
- }
- }
-
- type pluginCheck struct {
- name string
- desc string
- plugins []struct{ file, format string }
- }
-
- checks := []pluginCheck{
- {
- name: "qt6-imageformats",
- desc: "WebP, TIFF, GIF, JP2 support",
- plugins: []struct{ file, format string }{
- {"libqwebp.so", "WebP"},
- {"libqtiff.so", "TIFF"},
- {"libqgif.so", "GIF"},
- {"libqjp2.so", "JP2"},
- {"libqicns.so", "ICNS"},
- },
- },
- {
- name: "kimageformats",
- desc: "AVIF, HEIF, JXL support",
- plugins: []struct{ file, format string }{
- {"kimg_avif.so", "AVIF"},
- {"kimg_heif.so", "HEIF"},
- {"kimg_jxl.so", "JXL"},
- {"kimg_exr.so", "EXR"},
- },
- },
- }
-
- var results []checkResult
- for _, c := range checks {
- var found []string
- var foundDirs []string
- for _, pluginDir := range pluginDirs {
- imageFormatsDir := filepath.Join(pluginDir, "imageformats")
- for _, p := range c.plugins {
- if _, err := os.Stat(filepath.Join(imageFormatsDir, p.file)); err == nil {
- if !slices.Contains(found, p.format) {
- found = append(found, p.format)
- }
- if !slices.Contains(foundDirs, imageFormatsDir) {
- foundDirs = append(foundDirs, imageFormatsDir)
- }
- }
- }
- }
-
- var result checkResult
- switch {
- case len(found) == 0:
- result = checkResult{catOptionalFeatures, c.name, statusWarn, "Not installed", c.desc, url}
- default:
- details := ""
- if doctorVerbose {
- details = fmt.Sprintf("Formats: %s (%s)", strings.Join(found, ", "), strings.Join(foundDirs, ":"))
- }
- result = checkResult{catOptionalFeatures, c.name, statusOK, fmt.Sprintf("Installed (%d formats)", len(found)), details, url}
- }
- results = append(results, result)
- }
-
- return results
-}
-
-func findQtPluginDirs() []string {
- var dirs []string
-
- addDir := func(dir string) {
- if dir != "" {
- if _, err := os.Stat(filepath.Join(dir, "imageformats")); err == nil {
- dirs = append(dirs, dir)
- }
- }
- }
-
- // Check all paths in QT_PLUGIN_PATH env var (used by NixOS and custom setups)
- if envPath := os.Getenv("QT_PLUGIN_PATH"); envPath != "" {
- for dir := range strings.SplitSeq(envPath, ":") {
- addDir(dir)
- }
- }
-
- // Try qtpaths
- for _, cmd := range []string{"qtpaths6", "qtpaths"} {
- if output, err := exec.Command(cmd, "-query", "QT_INSTALL_PLUGINS").Output(); err == nil {
- addDir(strings.TrimSpace(string(output)))
- }
- }
-
- // Fallback: common distro paths
- for _, dir := range []string{
- "/usr/lib/qt6/plugins",
- "/usr/lib64/qt6/plugins",
- "/usr/lib/x86_64-linux-gnu/qt6/plugins",
- "/usr/lib/aarch64-linux-gnu/qt6/plugins",
- } {
- addDir(dir)
- }
-
- return dirs
-}
-
-func detectNetworkBackend(stackResult *network.DetectResult) string {
- switch stackResult.Backend {
- case network.BackendNetworkManager:
- return "NetworkManager"
- case network.BackendIwd:
- return "iwd"
- case network.BackendNetworkd:
- if stackResult.HasIwd {
- return "iwd + systemd-networkd"
- }
- return "systemd-networkd"
- case network.BackendConnMan:
- return "ConnMan"
- default:
- return ""
- }
-}
-
-func getOptionalDBusStatus(busName string) (status, string) {
- if utils.IsDBusServiceAvailable(busName) {
- return statusOK, "Available"
- } else {
- return statusWarn, "Not available"
- }
-}
-
-func checkOptionalDependencies() []checkResult {
- var results []checkResult
-
- optionalFeaturesURL := doctorDocsURL + "#optional-features"
-
- accountsStatus, accountsMsg := getOptionalDBusStatus("org.freedesktop.Accounts")
- results = append(results, checkResult{catOptionalFeatures, "accountsservice", accountsStatus, accountsMsg, "User accounts", optionalFeaturesURL})
-
- ppdStatus, ppdMsg := getOptionalDBusStatus("org.freedesktop.UPower.PowerProfiles")
- results = append(results, checkResult{catOptionalFeatures, "power-profiles-daemon", ppdStatus, ppdMsg, "Power profile management", optionalFeaturesURL})
-
- logindStatus, logindMsg := getOptionalDBusStatus("org.freedesktop.login1")
- results = append(results, checkResult{catOptionalFeatures, "logind", logindStatus, logindMsg, "Session management", optionalFeaturesURL})
-
- cupsPkHelperBus := "org.opensuse.CupsPkHelper.Mechanism"
- var cupsPkStatus status
- var cupsPkMsg string
- switch {
- case utils.IsDBusServiceAvailable(cupsPkHelperBus):
- cupsPkStatus, cupsPkMsg = statusOK, "Running"
- case utils.IsDBusServiceActivatable(cupsPkHelperBus):
- cupsPkStatus, cupsPkMsg = statusOK, "Available"
- default:
- cupsPkStatus, cupsPkMsg = statusWarn, "Not available (install cups-pk-helper)"
- }
- results = append(results, checkResult{catOptionalFeatures, "cups-pk-helper", cupsPkStatus, cupsPkMsg, "Printer management", optionalFeaturesURL})
-
- results = append(results, checkI2CAvailability())
- results = append(results, checkImageFormatPlugins()...)
-
- terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"}
- terminals = slices.DeleteFunc(terminals, func(t string) bool {
- return !utils.CommandExists(t)
- })
-
- if len(terminals) > 0 {
- results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, strings.Join(terminals, ", "), "", optionalFeaturesURL})
- } else {
- results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, foot or alacritty", optionalFeaturesURL})
- }
-
- networkResult, err := network.DetectNetworkStack()
- networkStatus, networkMessage, networkDetails := statusOK, "Not available", "Network management"
-
- if err == nil && networkResult.Backend != network.BackendNone {
- networkMessage = detectNetworkBackend(networkResult)
- if doctorVerbose {
- networkDetails = networkResult.ChosenReason
- }
- } else {
- networkStatus = statusInfo
- }
-
- results = append(results, checkResult{catOptionalFeatures, "Network", networkStatus, networkMessage, networkDetails, optionalFeaturesURL})
-
- deps := []struct {
- name, cmd, desc string
- important bool
- }{
- {"matugen", "matugen", "Dynamic theming", true},
- {"dgop", "dgop", "System monitoring", true},
- {"cava", "cava", "Audio visualizer", true},
- {"khal", "khal", "Calendar events", false},
- {"danksearch", "dsearch", "File search", false},
- {"fprintd", "fprintd-list", "Fingerprint auth", false},
- }
-
- for _, d := range deps {
- found := utils.CommandExists(d.cmd)
-
- switch {
- case found:
- results = append(results, checkResult{catOptionalFeatures, d.name, statusOK, "Installed", d.desc, optionalFeaturesURL})
- case d.important:
- results = append(results, checkResult{catOptionalFeatures, d.name, statusWarn, "Missing", d.desc, optionalFeaturesURL})
- default:
- results = append(results, checkResult{catOptionalFeatures, d.name, statusInfo, "Not installed", d.desc, optionalFeaturesURL})
- }
- }
-
- return results
-}
-
-func checkConfigurationFiles() []checkResult {
- configDir, _ := os.UserConfigDir()
- cacheDir, _ := os.UserCacheDir()
- dmsDir := "DankMaterialShell"
-
- configFiles := []struct{ name, path string }{
- {"settings.json", filepath.Join(configDir, dmsDir, "settings.json")},
- {"clsettings.json", filepath.Join(configDir, dmsDir, "clsettings.json")},
- {"plugin_settings.json", filepath.Join(configDir, dmsDir, "plugin_settings.json")},
- {"session.json", filepath.Join(utils.XDGStateHome(), dmsDir, "session.json")},
- {"dms-colors.json", filepath.Join(cacheDir, dmsDir, "dms-colors.json")},
- }
-
- var results []checkResult
- for _, cf := range configFiles {
- info, err := os.Stat(cf.path)
- if err != nil {
- results = append(results, checkResult{catConfigFiles, cf.name, statusInfo, "Not yet created", cf.path, doctorDocsURL + "#config-files"})
- continue
- }
-
- status := statusOK
- message := "Present"
- if info.Mode().Perm()&0o200 == 0 {
- status = statusWarn
- message += " (read-only)"
- }
- results = append(results, checkResult{catConfigFiles, cf.name, status, message, cf.path, doctorDocsURL + "#config-files"})
- }
- return results
-}
-
-func checkSystemdServices() []checkResult {
- if !utils.CommandExists("systemctl") {
- return nil
- }
-
- var results []checkResult
-
- dmsState := getServiceState("dms", true)
- if !dmsState.exists {
- results = append(results, checkResult{catServices, "dms.service", statusInfo, "Not installed", "Optional user service", doctorDocsURL + "#services"})
- } else {
- status, message := statusOK, dmsState.enabled
- if dmsState.active != "" {
- message = fmt.Sprintf("%s, %s", dmsState.enabled, dmsState.active)
- }
- switch {
- case dmsState.enabled == "disabled":
- status, message = statusWarn, "Disabled"
- case dmsState.active == "failed" || dmsState.active == "inactive":
- status = statusError
- }
- results = append(results, checkResult{catServices, "dms.service", status, message, "", doctorDocsURL + "#services"})
- }
-
- greetdState := getServiceState("greetd", false)
- switch {
- case greetdState.exists:
- status := statusOK
- if greetdState.enabled == "disabled" {
- status = statusInfo
- }
- results = append(results, checkResult{catServices, "greetd", status, greetdState.enabled, "", doctorDocsURL + "#services"})
- case doctorVerbose:
- results = append(results, checkResult{catServices, "greetd", statusInfo, "Not installed", "Optional greeter service", doctorDocsURL + "#services"})
- }
-
- return results
-}
-
-type serviceState struct {
- exists bool
- enabled string
- active string
-}
-
-func getServiceState(name string, userService bool) serviceState {
- args := []string{"is-enabled", name}
- if userService {
- args = []string{"--user", "is-enabled", name}
- }
-
- output, _ := exec.Command("systemctl", args...).Output()
- enabled := strings.TrimSpace(string(output))
-
- if enabled == "" || enabled == "not-found" {
- return serviceState{}
- }
-
- state := serviceState{exists: true, enabled: enabled}
-
- if userService {
- output, _ = exec.Command("systemctl", "--user", "is-active", name).Output()
- if active := strings.TrimSpace(string(output)); active != "" && active != "unknown" {
- state.active = active
- }
- }
-
- return state
-}
-
-func printResults(results []checkResult) {
- theme := tui.TerminalTheme()
- styles := tui.NewStyles(theme)
-
- currentCategory := category(-1)
- for _, r := range results {
- if r.category != currentCategory {
- if currentCategory != -1 {
- fmt.Println()
- }
- fmt.Printf(" %s\n", styles.Bold.Render(r.category.String()))
- currentCategory = r.category
- }
- printResultLine(r, styles)
- }
-}
-
-func printResultsJSON(results []checkResult) {
- var ds DoctorStatus
- for _, r := range results {
- ds.Add(r)
- }
-
- output := doctorOutputJSON{}
- output.Summary.Errors = ds.ErrorCount()
- output.Summary.Warnings = ds.WarningCount()
- output.Summary.OK = ds.OKCount()
- output.Summary.Info = len(ds.Info)
-
- output.Results = make([]checkResultJSON, 0, len(results))
- for _, r := range results {
- output.Results = append(output.Results, r.toJSON())
- }
-
- encoder := json.NewEncoder(os.Stdout)
- encoder.SetIndent("", " ")
- if err := encoder.Encode(output); err != nil {
- fmt.Fprintf(os.Stderr, "Error encoding JSON: %v\n", err)
- os.Exit(1)
- }
-}
-
-func printResultLine(r checkResult, styles tui.Styles) {
- icon, style := r.status.IconStyle(styles)
-
- name := r.name
- nameLen := len(name)
-
- if nameLen > checkNameMaxLength {
- name = name[:checkNameMaxLength-1] + "…"
- nameLen = checkNameMaxLength
- }
- dots := strings.Repeat("·", checkNameMaxLength-nameLen)
-
- fmt.Printf(" %s %s %s %s\n", style.Render(icon), name, styles.Subtle.Render(dots), r.message)
-
- if doctorVerbose && r.details != "" {
- fmt.Printf(" %s\n", styles.Subtle.Render("└─ "+r.details))
- }
-
- if (r.status == statusError || r.status == statusWarn) && r.url != "" {
- fmt.Printf(" %s\n", styles.Subtle.Render("→ "+r.url))
- }
-}
-
-func printSummary(results []checkResult, qsMissingFeatures bool) {
- theme := tui.TerminalTheme()
- styles := tui.NewStyles(theme)
-
- var ds DoctorStatus
- for _, r := range results {
- ds.Add(r)
- }
-
- fmt.Println()
- fmt.Printf(" %s\n", styles.Subtle.Render("──────────────────────────────────────"))
-
- if !ds.HasIssues() {
- fmt.Printf(" %s\n", styles.Success.Render("✓ All checks passed!"))
- } else {
- var parts []string
-
- if ds.ErrorCount() > 0 {
- parts = append(parts, styles.Error.Render(fmt.Sprintf("%d error(s)", ds.ErrorCount())))
- }
- if ds.WarningCount() > 0 {
- parts = append(parts, styles.Warning.Render(fmt.Sprintf("%d warning(s)", ds.WarningCount())))
- }
- parts = append(parts, styles.Success.Render(fmt.Sprintf("%d ok", ds.OKCount())))
- fmt.Printf(" %s\n", strings.Join(parts, ", "))
-
- if qsMissingFeatures {
- fmt.Println()
- fmt.Printf(" %s\n", styles.Subtle.Render("→ Consider using quickshell-git for full feature support"))
- }
- }
- fmt.Println()
-}
-
-func formatResultsPlain(results []checkResult) string {
- var sb strings.Builder
- sb.WriteString("## DMS Doctor Report\n\n")
-
- currentCategory := category(-1)
- for _, r := range results {
- if r.category != currentCategory {
- if currentCategory != -1 {
- sb.WriteString("\n")
- }
- fmt.Fprintf(&sb, "**%s**\n", r.category.String())
- currentCategory = r.category
- }
-
- fmt.Fprintf(&sb, "- [%s] %s: %s\n", r.status, r.name, r.message)
-
- if doctorVerbose && r.details != "" {
- fmt.Fprintf(&sb, " - %s\n", r.details)
- }
- }
-
- var ds DoctorStatus
- for _, r := range results {
- ds.Add(r)
- }
-
- sb.WriteString("\n---\n")
- fmt.Fprintf(&sb, "**Summary:** %d error(s), %d warning(s), %d ok\n",
- ds.ErrorCount(), ds.WarningCount(), ds.OKCount())
-
- return sb.String()
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_download.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_download.go
deleted file mode 100644
index e9caeb0..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_download.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package main
-
-import (
- "context"
- "fmt"
- "io"
- "net"
- "net/http"
- "os"
- "path/filepath"
- "time"
-
- "github.com/spf13/cobra"
-)
-
-var dlOutput string
-var dlUserAgent string
-var dlTimeout int
-var dlIPv4Only bool
-
-var dlCmd = &cobra.Command{
- Use: "dl <url>",
- Short: "Download a URL to stdout or file",
- Args: cobra.ExactArgs(1),
- Run: func(cmd *cobra.Command, args []string) {
- if err := runDownload(args[0]); err != nil {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
- },
-}
-
-func init() {
- dlCmd.Flags().StringVarP(&dlOutput, "output", "o", "", "Output file path (default: stdout)")
- dlCmd.Flags().StringVar(&dlUserAgent, "user-agent", "", "Custom User-Agent header")
- dlCmd.Flags().IntVar(&dlTimeout, "timeout", 10, "Request timeout in seconds")
- dlCmd.Flags().BoolVarP(&dlIPv4Only, "ipv4", "4", false, "Force IPv4 only")
-}
-
-func runDownload(url string) error {
- ctx, cancel := context.WithTimeout(context.Background(), time.Duration(dlTimeout)*time.Second)
- defer cancel()
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
- if err != nil {
- return fmt.Errorf("invalid request: %w", err)
- }
-
- switch {
- case dlUserAgent != "":
- req.Header.Set("User-Agent", dlUserAgent)
- default:
- req.Header.Set("User-Agent", "DankMaterialShell/1.0 (Linux)")
- }
-
- dialer := &net.Dialer{Timeout: 5 * time.Second}
- transport := &http.Transport{DialContext: dialer.DialContext}
- if dlIPv4Only {
- transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
- return dialer.DialContext(ctx, "tcp4", addr)
- }
- }
- client := &http.Client{Transport: transport}
-
- resp, err := client.Do(req)
- if err != nil {
- return fmt.Errorf("download failed: %w", err)
- }
- defer resp.Body.Close()
-
- if resp.StatusCode < 200 || resp.StatusCode >= 300 {
- return fmt.Errorf("HTTP %d", resp.StatusCode)
- }
-
- if dlOutput == "" {
- _, err = io.Copy(os.Stdout, resp.Body)
- return err
- }
-
- if dir := filepath.Dir(dlOutput); dir != "." {
- if err := os.MkdirAll(dir, 0755); err != nil {
- return fmt.Errorf("mkdir failed: %w", err)
- }
- }
-
- f, err := os.Create(dlOutput)
- if err != nil {
- return fmt.Errorf("create failed: %w", err)
- }
- defer f.Close()
-
- if _, err := io.Copy(f, resp.Body); err != nil {
- os.Remove(dlOutput)
- return fmt.Errorf("write failed: %w", err)
- }
-
- fmt.Println(dlOutput)
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_dpms.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_dpms.go
deleted file mode 100644
index 77eff1a..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_dpms.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package main
-
-import (
- "fmt"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/spf13/cobra"
-)
-
-var dpmsCmd = &cobra.Command{
- Use: "dpms",
- Short: "Control display power management",
-}
-
-var dpmsOnCmd = &cobra.Command{
- Use: "on [output]",
- Short: "Turn display(s) on",
- Args: cobra.MaximumNArgs(1),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) != 0 {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
- return getDPMSOutputs(), cobra.ShellCompDirectiveNoFileComp
- },
- Run: runDPMSOn,
-}
-
-var dpmsOffCmd = &cobra.Command{
- Use: "off [output]",
- Short: "Turn display(s) off",
- Args: cobra.MaximumNArgs(1),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) != 0 {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
- return getDPMSOutputs(), cobra.ShellCompDirectiveNoFileComp
- },
- Run: runDPMSOff,
-}
-
-var dpmsListCmd = &cobra.Command{
- Use: "list",
- Short: "List outputs",
- Args: cobra.NoArgs,
- Run: runDPMSList,
-}
-
-func init() {
- dpmsCmd.AddCommand(dpmsOnCmd, dpmsOffCmd, dpmsListCmd)
-}
-
-func runDPMSOn(cmd *cobra.Command, args []string) {
- outputName := ""
- if len(args) > 0 {
- outputName = args[0]
- }
-
- client, err := newDPMSClient()
- if err != nil {
- log.Fatalf("%v", err)
- }
- defer client.Close()
-
- if err := client.SetDPMS(outputName, true); err != nil {
- log.Fatalf("%v", err)
- }
-}
-
-func runDPMSOff(cmd *cobra.Command, args []string) {
- outputName := ""
- if len(args) > 0 {
- outputName = args[0]
- }
-
- client, err := newDPMSClient()
- if err != nil {
- log.Fatalf("%v", err)
- }
- defer client.Close()
-
- if err := client.SetDPMS(outputName, false); err != nil {
- log.Fatalf("%v", err)
- }
-}
-
-func getDPMSOutputs() []string {
- client, err := newDPMSClient()
- if err != nil {
- return nil
- }
- defer client.Close()
- return client.ListOutputs()
-}
-
-func runDPMSList(cmd *cobra.Command, args []string) {
- client, err := newDPMSClient()
- if err != nil {
- log.Fatalf("%v", err)
- }
- defer client.Close()
-
- for _, output := range client.ListOutputs() {
- fmt.Println(output)
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_features.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_features.go
deleted file mode 100644
index 7ec5437..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_features.go
+++ /dev/null
@@ -1,491 +0,0 @@
-//go:build !distro_binary
-
-package main
-
-import (
- "bufio"
- "errors"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/version"
- "github.com/spf13/cobra"
-)
-
-var updateCmd = &cobra.Command{
- Use: "update",
- Short: "Update DankMaterialShell to the latest version",
- Long: "Update DankMaterialShell to the latest version using the appropriate package manager for your distribution",
- PreRunE: findConfig,
- Run: func(cmd *cobra.Command, args []string) {
- runUpdate()
- },
-}
-
-var updateCheckCmd = &cobra.Command{
- Use: "check",
- Short: "Check if updates are available for DankMaterialShell",
- Long: "Check for available updates without performing the actual update",
- Run: func(cmd *cobra.Command, args []string) {
- runUpdateCheck()
- },
-}
-
-func runUpdateCheck() {
- fmt.Println("Checking for DankMaterialShell updates...")
- fmt.Println()
-
- versionInfo, err := version.GetDMSVersionInfo()
- if err != nil {
- log.Fatalf("Error checking for updates: %v", err)
- }
-
- fmt.Printf("Current version: %s\n", versionInfo.Current)
- fmt.Printf("Latest version: %s\n", versionInfo.Latest)
- fmt.Println()
-
- if versionInfo.HasUpdate {
- fmt.Println("✓ Update available!")
- fmt.Println()
- fmt.Println("Run 'dms update' to install the latest version.")
- os.Exit(0)
- } else {
- fmt.Println("✓ You are running the latest version.")
- os.Exit(0)
- }
-}
-
-func runUpdate() {
- osInfo, err := distros.GetOSInfo()
- if err != nil {
- log.Fatalf("Error detecting OS: %v", err)
- }
-
- config, exists := distros.Registry[osInfo.Distribution.ID]
- if !exists {
- log.Fatalf("Unsupported distribution: %s", osInfo.Distribution.ID)
- }
-
- var updateErr error
- switch config.Family {
- case distros.FamilyArch:
- updateErr = updateArchLinux()
- case distros.FamilySUSE:
- updateErr = updateOtherDistros()
- default:
- updateErr = updateOtherDistros()
- }
-
- if updateErr != nil {
- if errors.Is(updateErr, errdefs.ErrUpdateCancelled) {
- log.Info("Update cancelled.")
- return
- }
- if errors.Is(updateErr, errdefs.ErrNoUpdateNeeded) {
- return
- }
- log.Fatalf("Error updating DMS: %v", updateErr)
- }
-
- log.Info("Update complete! Restarting DMS...")
- restartShell()
-}
-
-func updateArchLinux() error {
- homeDir, err := os.UserHomeDir()
- if err == nil {
- dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
- if _, err := os.Stat(dmsPath); err == nil {
- return updateOtherDistros()
- }
- }
-
- var packageName string
- var isAUR bool
- if isArchPackageInstalled("dms-shell") {
- packageName = "dms-shell"
- } else if isArchPackageInstalled("dms-shell-git") {
- packageName = "dms-shell-git"
- isAUR = true
- } else if isArchPackageInstalled("dms-shell-bin") {
- packageName = "dms-shell-bin"
- isAUR = true
- } else {
- fmt.Println("Info: No dms-shell package found.")
- fmt.Println("Info: Falling back to git-based update method...")
- return updateOtherDistros()
- }
-
- if !isAUR {
- fmt.Printf("This will update %s using pacman.\n", packageName)
- if !confirmUpdate() {
- return errdefs.ErrUpdateCancelled
- }
-
- fmt.Printf("\nRunning: sudo pacman -S %s\n", packageName)
- cmd := exec.Command("sudo", "pacman", "-S", "--noconfirm", packageName)
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- if err := cmd.Run(); err != nil {
- fmt.Printf("Error: Failed to update using pacman: %v\n", err)
- return err
- }
-
- fmt.Println("dms successfully updated")
- return nil
- }
-
- var helper string
- var updateCmd *exec.Cmd
-
- if utils.CommandExists("yay") {
- helper = "yay"
- updateCmd = exec.Command("yay", "-S", packageName)
- } else if utils.CommandExists("paru") {
- helper = "paru"
- updateCmd = exec.Command("paru", "-S", packageName)
- } else {
- fmt.Println("Error: Neither yay nor paru found - please install an AUR helper")
- fmt.Println("Info: Falling back to git-based update method...")
- return updateOtherDistros()
- }
-
- fmt.Printf("This will update DankMaterialShell using %s.\n", helper)
- if !confirmUpdate() {
- return errdefs.ErrUpdateCancelled
- }
-
- fmt.Printf("\nRunning: %s -S %s\n", helper, packageName)
- updateCmd.Stdout = os.Stdout
- updateCmd.Stderr = os.Stderr
- err = updateCmd.Run()
- if err != nil {
- fmt.Printf("Error: Failed to update using %s: %v\n", helper, err)
- }
-
- fmt.Println("dms successfully updated")
- return nil
-}
-
-func updateOtherDistros() error {
- homeDir, err := os.UserHomeDir()
- if err != nil {
- return fmt.Errorf("failed to get user home directory: %w", err)
- }
-
- dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
-
- if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
- return fmt.Errorf("DMS configuration directory not found at %s", dmsPath)
- }
-
- fmt.Printf("Found DMS configuration at %s\n", dmsPath)
-
- versionInfo, err := version.GetDMSVersionInfo()
- if err == nil && !versionInfo.HasUpdate {
- fmt.Println()
- fmt.Printf("Current version: %s\n", versionInfo.Current)
- fmt.Printf("Latest version: %s\n", versionInfo.Latest)
- fmt.Println()
- fmt.Println("✓ You are already running the latest version.")
- return errdefs.ErrNoUpdateNeeded
- }
-
- fmt.Println("\nThis will update:")
- fmt.Println(" 1. The dms binary from GitHub releases")
- fmt.Println(" 2. DankMaterialShell configuration using git")
- if !confirmUpdate() {
- return errdefs.ErrUpdateCancelled
- }
-
- fmt.Println("\n=== Updating dms binary ===")
- if err := updateDMSBinary(); err != nil {
- fmt.Printf("Warning: Failed to update dms binary: %v\n", err)
- fmt.Println("Continuing with shell configuration update...")
- } else {
- fmt.Println("dms binary successfully updated")
- }
-
- fmt.Println("\n=== Updating DMS shell configuration ===")
-
- if err := os.Chdir(dmsPath); err != nil {
- return fmt.Errorf("failed to change to DMS directory: %w", err)
- }
-
- statusCmd := exec.Command("git", "status", "--porcelain")
- statusOutput, _ := statusCmd.Output()
- hasLocalChanges := len(strings.TrimSpace(string(statusOutput))) > 0
-
- currentRefCmd := exec.Command("git", "symbolic-ref", "-q", "HEAD")
- currentRefOutput, _ := currentRefCmd.Output()
- onBranch := len(currentRefOutput) > 0
-
- var currentTag string
- var currentBranch string
-
- if !onBranch {
- tagCmd := exec.Command("git", "describe", "--exact-match", "--tags", "HEAD")
- if tagOutput, err := tagCmd.Output(); err == nil {
- currentTag = strings.TrimSpace(string(tagOutput))
- }
- } else {
- branchCmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
- if branchOutput, err := branchCmd.Output(); err == nil {
- currentBranch = strings.TrimSpace(string(branchOutput))
- }
- }
-
- fmt.Println("Fetching latest changes...")
- fetchCmd := exec.Command("git", "fetch", "origin", "--tags", "--force")
- fetchCmd.Stdout = os.Stdout
- fetchCmd.Stderr = os.Stderr
- if err := fetchCmd.Run(); err != nil {
- return fmt.Errorf("failed to fetch changes: %w", err)
- }
-
- if currentTag != "" {
- latestTagCmd := exec.Command("git", "tag", "-l", "v*", "--sort=-version:refname")
- latestTagOutput, err := latestTagCmd.Output()
- if err != nil {
- return fmt.Errorf("failed to get latest tag: %w", err)
- }
-
- tags := strings.Split(strings.TrimSpace(string(latestTagOutput)), "\n")
- if len(tags) == 0 || tags[0] == "" {
- return fmt.Errorf("no version tags found")
- }
- latestTag := tags[0]
-
- if latestTag == currentTag {
- fmt.Printf("Already on latest tag: %s\n", currentTag)
- return nil
- }
-
- fmt.Printf("Current tag: %s\n", currentTag)
- fmt.Printf("Latest tag: %s\n", latestTag)
-
- if hasLocalChanges {
- fmt.Println("\nWarning: You have local changes in your DMS configuration.")
- if offerReclone(dmsPath) {
- return nil
- }
- return errdefs.ErrUpdateCancelled
- }
-
- fmt.Printf("Updating to %s...\n", latestTag)
- checkoutCmd := exec.Command("git", "checkout", latestTag)
- checkoutCmd.Stdout = os.Stdout
- checkoutCmd.Stderr = os.Stderr
- if err := checkoutCmd.Run(); err != nil {
- fmt.Printf("Error: Failed to checkout %s: %v\n", latestTag, err)
- if offerReclone(dmsPath) {
- return nil
- }
- return fmt.Errorf("update cancelled")
- }
-
- fmt.Printf("\nUpdate complete! Updated from %s to %s\n", currentTag, latestTag)
- return nil
- }
-
- if currentBranch == "" {
- currentBranch = "master"
- }
-
- fmt.Printf("Current branch: %s\n", currentBranch)
-
- if hasLocalChanges {
- fmt.Println("\nWarning: You have local changes in your DMS configuration.")
- if offerReclone(dmsPath) {
- return nil
- }
- return errdefs.ErrUpdateCancelled
- }
-
- pullCmd := exec.Command("git", "pull", "origin", currentBranch)
- pullCmd.Stdout = os.Stdout
- pullCmd.Stderr = os.Stderr
- if err := pullCmd.Run(); err != nil {
- fmt.Printf("Error: Failed to pull latest changes: %v\n", err)
- if offerReclone(dmsPath) {
- return nil
- }
- return fmt.Errorf("update cancelled")
- }
-
- fmt.Println("\nUpdate complete!")
- return nil
-}
-
-func offerReclone(dmsPath string) bool {
- fmt.Println("\nWould you like to backup and re-clone the repository? (y/N): ")
- reader := bufio.NewReader(os.Stdin)
- response, err := reader.ReadString('\n')
- if err != nil || !strings.HasPrefix(strings.ToLower(strings.TrimSpace(response)), "y") {
- return false
- }
-
- timestamp := time.Now().Unix()
- backupPath := fmt.Sprintf("%s.backup-%d", dmsPath, timestamp)
-
- fmt.Printf("Backing up current directory to %s...\n", backupPath)
- if err := os.Rename(dmsPath, backupPath); err != nil {
- fmt.Printf("Error: Failed to backup directory: %v\n", err)
- return false
- }
-
- fmt.Println("Cloning fresh copy...")
- cloneCmd := exec.Command("git", "clone", "https://github.com/AvengeMedia/DankMaterialShell.git", dmsPath)
- cloneCmd.Stdout = os.Stdout
- cloneCmd.Stderr = os.Stderr
- if err := cloneCmd.Run(); err != nil {
- fmt.Printf("Error: Failed to clone repository: %v\n", err)
- fmt.Printf("Restoring backup...\n")
- os.Rename(backupPath, dmsPath)
- return false
- }
-
- fmt.Printf("Successfully re-cloned repository (backup at %s)\n", backupPath)
- return true
-}
-
-func confirmUpdate() bool {
- fmt.Print("Do you want to proceed with the update? (y/N): ")
- reader := bufio.NewReader(os.Stdin)
- response, err := reader.ReadString('\n')
- if err != nil {
- fmt.Printf("Error reading input: %v\n", err)
- return false
- }
- response = strings.TrimSpace(strings.ToLower(response))
- return response == "y" || response == "yes"
-}
-
-func updateDMSBinary() error {
- arch := ""
- switch strings.ToLower(os.Getenv("HOSTTYPE")) {
- case "x86_64", "amd64":
- arch = "amd64"
- case "aarch64", "arm64":
- arch = "arm64"
- default:
- cmd := exec.Command("uname", "-m")
- output, err := cmd.Output()
- if err != nil {
- return fmt.Errorf("failed to detect architecture: %w", err)
- }
- archStr := strings.TrimSpace(string(output))
- switch archStr {
- case "x86_64":
- arch = "amd64"
- case "aarch64":
- arch = "arm64"
- default:
- return fmt.Errorf("unsupported architecture: %s", archStr)
- }
- }
-
- fmt.Println("Fetching latest release version...")
- cmd := exec.Command("curl", "-s", "https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest")
- output, err := cmd.Output()
- if err != nil {
- return fmt.Errorf("failed to fetch latest release: %w", err)
- }
-
- version := ""
- for line := range strings.SplitSeq(string(output), "\n") {
- if strings.Contains(line, "\"tag_name\"") {
- parts := strings.Split(line, "\"")
- if len(parts) >= 4 {
- version = parts[3]
- break
- }
- }
- }
-
- if version == "" {
- return fmt.Errorf("could not determine latest version")
- }
-
- fmt.Printf("Latest version: %s\n", version)
-
- tempDir, err := os.MkdirTemp("", "dms-update-*")
- if err != nil {
- return fmt.Errorf("failed to create temp directory: %w", err)
- }
- defer os.RemoveAll(tempDir)
-
- binaryURL := fmt.Sprintf("https://github.com/AvengeMedia/DankMaterialShell/releases/download/%s/dms-cli-%s.gz", version, arch)
- checksumURL := fmt.Sprintf("https://github.com/AvengeMedia/DankMaterialShell/releases/download/%s/dms-cli-%s.gz.sha256", version, arch)
-
- binaryPath := filepath.Join(tempDir, "dms.gz")
- checksumPath := filepath.Join(tempDir, "dms.gz.sha256")
-
- fmt.Println("Downloading dms binary...")
- downloadCmd := exec.Command("curl", "-L", binaryURL, "-o", binaryPath)
- if err := downloadCmd.Run(); err != nil {
- return fmt.Errorf("failed to download binary: %w", err)
- }
-
- fmt.Println("Downloading checksum...")
- downloadCmd = exec.Command("curl", "-L", checksumURL, "-o", checksumPath)
- if err := downloadCmd.Run(); err != nil {
- return fmt.Errorf("failed to download checksum: %w", err)
- }
-
- fmt.Println("Verifying checksum...")
- checksumData, err := os.ReadFile(checksumPath)
- if err != nil {
- return fmt.Errorf("failed to read checksum file: %w", err)
- }
- expectedChecksum := strings.Fields(string(checksumData))[0]
-
- actualCmd := exec.Command("sha256sum", binaryPath)
- actualOutput, err := actualCmd.Output()
- if err != nil {
- return fmt.Errorf("failed to calculate checksum: %w", err)
- }
- actualChecksum := strings.Fields(string(actualOutput))[0]
-
- if expectedChecksum != actualChecksum {
- return fmt.Errorf("checksum verification failed\nExpected: %s\nGot: %s", expectedChecksum, actualChecksum)
- }
-
- fmt.Println("Decompressing binary...")
- decompressCmd := exec.Command("gunzip", binaryPath)
- if err := decompressCmd.Run(); err != nil {
- return fmt.Errorf("failed to decompress binary: %w", err)
- }
-
- decompressedPath := filepath.Join(tempDir, "dms")
-
- if err := os.Chmod(decompressedPath, 0o755); err != nil {
- return fmt.Errorf("failed to make binary executable: %w", err)
- }
-
- currentPath, err := exec.LookPath("dms")
- if err != nil {
- return fmt.Errorf("could not find current dms binary: %w", err)
- }
-
- fmt.Printf("Installing to %s...\n", currentPath)
-
- replaceCmd := exec.Command("sudo", "install", "-m", "0755", decompressedPath, currentPath)
- replaceCmd.Stdin = os.Stdin
- replaceCmd.Stdout = os.Stdout
- replaceCmd.Stderr = os.Stderr
- if err := replaceCmd.Run(); err != nil {
- return fmt.Errorf("failed to replace binary: %w", err)
- }
-
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_greeter.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_greeter.go
deleted file mode 100644
index 1efa545..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_greeter.go
+++ /dev/null
@@ -1,1850 +0,0 @@
-package main
-
-import (
- "bufio"
- "fmt"
- "os"
- "os/exec"
- "os/user"
- "path/filepath"
- "strconv"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- sharedpam "github.com/AvengeMedia/DankMaterialShell/core/internal/pam"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
- "github.com/spf13/cobra"
- "golang.org/x/text/cases"
- "golang.org/x/text/language"
-)
-
-var greeterCmd = &cobra.Command{
- Use: "greeter",
- Short: "Manage DMS greeter",
- Long: "Manage DMS greeter (greetd)",
-}
-
-var (
- greeterConfigSyncFn = greeter.SyncDMSConfigs
- sharedAuthSyncFn = sharedpam.SyncAuthConfig
-)
-
-var greeterInstallCmd = &cobra.Command{
- Use: "install",
- Short: "Install and configure DMS greeter",
- Long: "Install greetd and configure it to use DMS as the greeter interface",
- PreRunE: requireMutableSystemCommand,
- Run: func(cmd *cobra.Command, args []string) {
- yes, _ := cmd.Flags().GetBool("yes")
- term, _ := cmd.Flags().GetBool("terminal")
- if term {
- installCmd := "dms greeter install"
- if yes {
- installCmd += " --yes"
- }
- installCmd += "; echo; echo \"Install finished. Closing in 3 seconds...\"; sleep 3"
- if err := runCommandInTerminal(installCmd); err != nil {
- log.Fatalf("Error launching install in terminal: %v", err)
- }
- return
- }
- if err := installGreeter(yes); err != nil {
- log.Fatalf("Error installing greeter: %v", err)
- }
- },
-}
-
-var greeterSyncCmd = &cobra.Command{
- Use: "sync",
- Short: "Sync DMS theme and settings with greeter",
- Long: "Synchronize your current user's DMS theme, settings, and wallpaper configuration with the login greeter screen",
- Run: func(cmd *cobra.Command, args []string) {
- yes, _ := cmd.Flags().GetBool("yes")
- auth, _ := cmd.Flags().GetBool("auth")
- local, _ := cmd.Flags().GetBool("local")
- term, _ := cmd.Flags().GetBool("terminal")
- if term {
- if err := syncInTerminal(yes, auth, local); err != nil {
- log.Fatalf("Error launching sync in terminal: %v", err)
- }
- return
- }
- if err := syncGreeter(yes, auth, local); err != nil {
- log.Fatalf("Error syncing greeter: %v", err)
- }
- },
-}
-
-func init() {
- greeterSyncCmd.Flags().BoolP("yes", "y", false, "Non-interactive mode: skip prompts, use defaults (for UI)")
- greeterSyncCmd.Flags().BoolP("terminal", "t", false, "Run sync in a new terminal (for entering sudo password); terminal auto-closes when done")
- greeterSyncCmd.Flags().BoolP("auth", "a", false, "Configure PAM for fingerprint and U2F (adds both if modules exist); overrides UI toggles")
- greeterSyncCmd.Flags().BoolP("local", "l", false, "Developer mode: force greetd config to use a local DMS checkout path")
-}
-
-var greeterEnableCmd = &cobra.Command{
- Use: "enable",
- Short: "Enable DMS greeter in greetd config",
- Long: "Configure greetd to use DMS as the greeter",
- PreRunE: requireMutableSystemCommand,
- Run: func(cmd *cobra.Command, args []string) {
- yes, _ := cmd.Flags().GetBool("yes")
- term, _ := cmd.Flags().GetBool("terminal")
- if term {
- enableCmd := "dms greeter enable"
- if yes {
- enableCmd += " --yes"
- }
- enableCmd += "; echo; echo \"Enable finished. Closing in 3 seconds...\"; sleep 3"
- if err := runCommandInTerminal(enableCmd); err != nil {
- log.Fatalf("Error launching enable in terminal: %v", err)
- }
- return
- }
- if err := enableGreeter(yes); err != nil {
- log.Fatalf("Error enabling greeter: %v", err)
- }
- },
-}
-
-var greeterStatusCmd = &cobra.Command{
- Use: "status",
- Short: "Check greeter sync status",
- Long: "Check the status of greeter installation and configuration sync",
- Run: func(cmd *cobra.Command, args []string) {
- if err := checkGreeterStatus(); err != nil {
- log.Fatalf("Error checking greeter status: %v", err)
- }
- },
-}
-
-var greeterUninstallCmd = &cobra.Command{
- Use: "uninstall",
- Short: "Remove DMS greeter configuration and restore previous display manager",
- Long: "Disable greetd, remove DMS managed configs, and restore the system to its pre-DMS-greeter state",
- PreRunE: requireMutableSystemCommand,
- Run: func(cmd *cobra.Command, args []string) {
- yes, _ := cmd.Flags().GetBool("yes")
- term, _ := cmd.Flags().GetBool("terminal")
- if term {
- uninstallCmd := "dms greeter uninstall"
- if yes {
- uninstallCmd += " --yes"
- }
- uninstallCmd += "; echo; echo \"Uninstall finished. Closing in 3 seconds...\"; sleep 3"
- if err := runCommandInTerminal(uninstallCmd); err != nil {
- log.Fatalf("Error launching uninstall in terminal: %v", err)
- }
- return
- }
- if err := uninstallGreeter(yes); err != nil {
- log.Fatalf("Error uninstalling greeter: %v", err)
- }
- },
-}
-
-func init() {
- greeterInstallCmd.Flags().BoolP("yes", "y", false, "Non-interactive: skip confirmation prompt")
- greeterInstallCmd.Flags().BoolP("terminal", "t", false, "Run in a new terminal (for entering sudo password)")
- greeterEnableCmd.Flags().BoolP("yes", "y", false, "Non-interactive: skip confirmation prompt")
- greeterEnableCmd.Flags().BoolP("terminal", "t", false, "Run in a new terminal (for entering sudo password)")
- greeterUninstallCmd.Flags().BoolP("yes", "y", false, "Non-interactive: skip confirmation prompt")
- greeterUninstallCmd.Flags().BoolP("terminal", "t", false, "Run in a new terminal (for entering sudo password)")
-}
-
-func syncGreeterConfigsAndAuth(dmsPath, compositor string, logFunc func(string), options sharedpam.SyncAuthOptions, beforeAuth func()) error {
- if err := greeterConfigSyncFn(dmsPath, compositor, logFunc, ""); err != nil {
- return err
- }
- if beforeAuth != nil {
- beforeAuth()
- }
- return sharedAuthSyncFn(logFunc, "", options)
-}
-
-func installGreeter(nonInteractive bool) error {
- fmt.Println("=== DMS Greeter Installation ===")
-
- logFunc := func(msg string) {
- fmt.Println(msg)
- }
-
- if !nonInteractive {
- fmt.Print("\nThis will install greetd (if needed), configure the DMS greeter, and enable it. Continue? [Y/n]: ")
- var response string
- fmt.Scanln(&response)
- if strings.ToLower(strings.TrimSpace(response)) == "n" || strings.ToLower(strings.TrimSpace(response)) == "no" {
- fmt.Println("Aborted.")
- return nil
- }
- fmt.Println()
- }
-
- if err := greeter.EnsureGreetdInstalled(logFunc, ""); err != nil {
- return err
- }
-
- greeter.TryInstallGreeterPackage(logFunc, "")
- if isPackageOnlyGreeterDistro() && !greeter.IsGreeterPackaged() {
- return fmt.Errorf("dms-greeter must be installed from distro packages on this distribution. %s", packageInstallHint())
- }
- if greeter.IsGreeterPackaged() && greeter.HasLegacyLocalGreeterWrapper() {
- return fmt.Errorf("legacy manual wrapper detected at /usr/local/bin/dms-greeter; remove it before using packaged dms-greeter: sudo rm -f /usr/local/bin/dms-greeter")
- }
-
- if isGreeterEnabled() {
- fmt.Print("\nGreeter is already installed and configured. Re-run to re-sync settings and permissions? [Y/n]: ")
- var response string
- fmt.Scanln(&response)
- response = strings.TrimSpace(strings.ToLower(response))
- if response == "n" || response == "no" {
- fmt.Println("Run 'dms greeter sync' to re-sync theme and settings at any time.")
- return nil
- }
- fmt.Println()
- }
-
- fmt.Println("\nDetecting DMS installation...")
- dmsPath, err := greeter.DetectDMSPath()
- if err != nil {
- return err
- }
- fmt.Printf("✓ Found DMS at: %s\n", dmsPath)
-
- fmt.Println("\nDetecting installed compositors...")
- compositors := greeter.DetectCompositors()
- if len(compositors) == 0 {
- return fmt.Errorf("no supported compositors found (niri or Hyprland required)")
- }
-
- var selectedCompositor string
- if len(compositors) == 1 {
- selectedCompositor = compositors[0]
- fmt.Printf("✓ Found compositor: %s\n", selectedCompositor)
- } else {
- var err error
- selectedCompositor, err = greeter.PromptCompositorChoice(compositors)
- if err != nil {
- return err
- }
- fmt.Printf("✓ Selected compositor: %s\n", selectedCompositor)
- }
-
- fmt.Println("\nSetting up dms-greeter group and permissions...")
- if err := greeter.SetupDMSGroup(logFunc, ""); err != nil {
- return err
- }
-
- fmt.Println("\nCopying greeter files...")
- if err := greeter.CopyGreeterFiles(dmsPath, selectedCompositor, logFunc, ""); err != nil {
- return err
- }
-
- if greeter.IsAppArmorEnabled() {
- fmt.Println("\nConfiguring AppArmor profile...")
- if err := greeter.InstallAppArmorProfile(logFunc, ""); err != nil {
- logFunc(fmt.Sprintf("⚠ AppArmor profile setup failed: %v", err))
- }
- }
-
- fmt.Println("\nConfiguring greetd...")
- greeterPathForConfig := ""
- if !greeter.IsGreeterPackaged() {
- greeterPathForConfig = dmsPath
- }
- if err := greeter.ConfigureGreetd(greeterPathForConfig, selectedCompositor, logFunc, ""); err != nil {
- return err
- }
-
- fmt.Println("\nSynchronizing DMS configurations...")
- if err := syncGreeterConfigsAndAuth(dmsPath, selectedCompositor, logFunc, sharedpam.SyncAuthOptions{}, func() {
- fmt.Println("\nConfiguring authentication...")
- }); err != nil {
- return err
- }
-
- if err := ensureGraphicalTarget(); err != nil {
- return err
- }
-
- if err := handleConflictingDisplayManagers(); err != nil {
- return err
- }
-
- if err := ensureGreetdEnabled(); err != nil {
- return err
- }
-
- fmt.Println("\n=== Installation Complete ===")
- fmt.Println("\nTo start the greeter now, run:")
- fmt.Println(" sudo systemctl start greetd")
- fmt.Println("\nOr reboot to see the greeter at next boot.")
-
- return nil
-}
-
-func uninstallGreeter(nonInteractive bool) error {
- fmt.Println("=== DMS Greeter Uninstall ===")
-
- logFunc := func(msg string) { fmt.Println(msg) }
-
- if !isGreeterEnabled() {
- fmt.Println("ℹ DMS greeter is not currently configured in /etc/greetd/config.toml.")
- fmt.Println(" Nothing to undo for greetd configuration.")
- }
-
- if !nonInteractive {
- fmt.Print("\nThis will:\n • Stop and disable greetd\n • Remove the DMS-managed greeter auth block\n • Remove the DMS AppArmor profile\n • Restore the most recent pre-DMS greetd config (if available)\n\nContinue? [y/N]: ")
- var response string
- fmt.Scanln(&response)
- if strings.ToLower(strings.TrimSpace(response)) != "y" {
- fmt.Println("Aborted.")
- return nil
- }
- }
-
- fmt.Println("\nDisabling greetd...")
- disableCmd := exec.Command("sudo", "systemctl", "disable", "greetd")
- disableCmd.Stdout = os.Stdout
- disableCmd.Stderr = os.Stderr
- if err := disableCmd.Run(); err != nil {
- fmt.Printf(" ⚠ Could not disable greetd: %v\n", err)
- } else {
- fmt.Println(" ✓ greetd disabled")
- }
-
- fmt.Println("\nRemoving DMS authentication configuration...")
- if err := sharedpam.RemoveManagedGreeterPamBlock(logFunc, ""); err != nil {
- fmt.Printf(" ⚠ PAM cleanup failed: %v\n", err)
- }
-
- fmt.Println("\nRemoving DMS AppArmor profile...")
- if err := greeter.UninstallAppArmorProfile(logFunc, ""); err != nil {
- fmt.Printf(" ⚠ AppArmor cleanup failed: %v\n", err)
- }
-
- fmt.Println("\nRestoring greetd configuration...")
- if err := restorePreDMSGreetdConfig(""); err != nil {
- fmt.Printf(" ⚠ Could not restore previous greetd config: %v\n", err)
- fmt.Println(" You may need to manually edit /etc/greetd/config.toml.")
- }
-
- fmt.Println("\nChecking for other display managers to re-enable...")
- suggestDisplayManagerRestore(nonInteractive)
-
- fmt.Println("\n=== Uninstall Complete ===")
- fmt.Println("\nReboot to complete the uninstallation and switch to your previous display manager.")
- fmt.Println("To re-enable DMS greeter at any time, run: dms greeter enable")
-
- return nil
-}
-
-func restorePreDMSGreetdConfig(sudoPassword string) error {
- const configPath = "/etc/greetd/config.toml"
- const backupGlob = "/etc/greetd/config.toml.backup-*"
-
- matches, _ := filepath.Glob(backupGlob)
-
- for i := 0; i < len(matches)-1; i++ {
- for j := i + 1; j < len(matches); j++ {
- if matches[j] > matches[i] {
- matches[i], matches[j] = matches[j], matches[i]
- }
- }
- }
-
- for _, candidate := range matches {
- data, err := os.ReadFile(candidate)
- if err != nil {
- continue
- }
- if strings.Contains(string(data), "dms-greeter") {
- continue
- }
- tmp, err := os.CreateTemp("", "greetd-restore-*")
- if err != nil {
- return fmt.Errorf("could not create temp file: %w", err)
- }
- tmpPath := tmp.Name()
- defer os.Remove(tmpPath)
- if _, err := tmp.Write(data); err != nil {
- tmp.Close()
- return err
- }
- tmp.Close()
-
- if err := runSudoCommand(sudoPassword, "cp", tmpPath, configPath); err != nil {
- return fmt.Errorf("failed to restore %s: %w", candidate, err)
- }
- if err := runSudoCommand(sudoPassword, "chmod", "644", configPath); err != nil {
- return err
- }
- fmt.Printf(" ✓ Restored greetd config from %s\n", candidate)
- return nil
- }
-
- minimal := `[terminal]
-vt = 1
-
-# DMS greeter has been uninstalled.
-# Configure a greeter command here or re-enable a display manager.
-[default_session]
-user = "greeter"
-command = "agreety --cmd /bin/bash"
-`
- tmp, err := os.CreateTemp("", "greetd-minimal-*")
- if err != nil {
- return fmt.Errorf("could not create temp file: %w", err)
- }
- tmpPath := tmp.Name()
- defer os.Remove(tmpPath)
- if _, err := tmp.WriteString(minimal); err != nil {
- tmp.Close()
- return err
- }
- tmp.Close()
-
- if err := runSudoCommand(sudoPassword, "cp", tmpPath, configPath); err != nil {
- return fmt.Errorf("failed to write fallback greetd config: %w", err)
- }
- _ = runSudoCommand(sudoPassword, "chmod", "644", configPath)
- fmt.Println(" ✓ Wrote minimal fallback greetd config (configure a greeter command manually if needed)")
- return nil
-}
-
-func runSudoCommand(_ string, command string, args ...string) error {
- cmd := exec.Command("sudo", append([]string{command}, args...)...)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- return cmd.Run()
-}
-
-// suggestDisplayManagerRestore scans for installed DMs and re-enables one
-func suggestDisplayManagerRestore(nonInteractive bool) {
- knownDMs := []string{"gdm", "gdm3", "lightdm", "sddm", "lxdm", "xdm", "cosmic-greeter"}
- var found []string
- for _, dm := range knownDMs {
- if utils.CommandExists(dm) || isSystemdUnitInstalled(dm) {
- found = append(found, dm)
- }
- }
- if len(found) == 0 {
- fmt.Println(" ℹ No other display managers detected.")
- fmt.Println(" You can install one (e.g. gdm, lightdm, sddm) and then run:")
- fmt.Println(" sudo systemctl enable --now <dm-name>")
- return
- }
-
- enableDM := func(dm string) {
- fmt.Printf(" Enabling %s...\n", dm)
- cmd := exec.Command("sudo", "systemctl", "enable", "--force", dm)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- if err := cmd.Run(); err != nil {
- fmt.Printf(" ⚠ Failed to enable %s: %v\n", dm, err)
- } else {
- fmt.Printf(" ✓ %s enabled (will take effect on next boot).\n", dm)
- }
- }
-
- if len(found) == 1 || nonInteractive {
- chosen := found[0]
- if len(found) > 1 {
- fmt.Printf(" ℹ Multiple display managers found (%s); enabling %s automatically.\n",
- strings.Join(found, ", "), chosen)
- } else {
- fmt.Printf(" ℹ Found display manager: %s\n", chosen)
- }
- enableDM(chosen)
- return
- }
-
- fmt.Println(" ℹ Found the following display managers:")
- for i, dm := range found {
- fmt.Printf(" %d) %s\n", i+1, dm)
- }
- fmt.Print(" Choose a number to re-enable (or press Enter to skip): ")
-
- scanner := bufio.NewScanner(os.Stdin)
- if !scanner.Scan() {
- return
- }
- input := strings.TrimSpace(scanner.Text())
- if input == "" {
- fmt.Println(" Skipped. You can re-enable a display manager later with:")
- fmt.Println(" sudo systemctl enable --now <dm-name>")
- return
- }
-
- n, err := strconv.Atoi(input)
- if err != nil || n < 1 || n > len(found) {
- fmt.Printf(" Invalid selection %q — skipping.\n", input)
- return
- }
-
- enableDM(found[n-1])
-}
-
-func isSystemdUnitInstalled(unit string) bool {
- cmd := exec.Command("systemctl", "list-unit-files", unit+".service", "--no-legend", "--no-pager")
- out, err := cmd.Output()
- return err == nil && strings.Contains(string(out), unit)
-}
-
-func runCommandInTerminal(shellCmd string) error {
- terminals := []struct {
- name string
- args []string
- }{
- {"gnome-terminal", []string{"--", "bash", "-c", shellCmd}},
- {"konsole", []string{"-e", "bash", "-c", shellCmd}},
- {"xfce4-terminal", []string{"-e", "bash -c \"" + strings.ReplaceAll(shellCmd, `"`, `\"`) + "\""}},
- {"ghostty", []string{"-e", "bash", "-c", shellCmd}},
- {"wezterm", []string{"start", "--", "bash", "-c", shellCmd}},
- {"alacritty", []string{"-e", "bash", "-c", shellCmd}},
- {"kitty", []string{"bash", "-c", shellCmd}},
- {"xterm", []string{"-e", "bash -c \"" + strings.ReplaceAll(shellCmd, `"`, `\"`) + "\""}},
- }
- for _, t := range terminals {
- if _, err := exec.LookPath(t.name); err != nil {
- continue
- }
- cmd := exec.Command(t.name, t.args...)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- if err := cmd.Run(); err != nil {
- return err
- }
- return nil
- }
- return fmt.Errorf("no terminal emulator found (tried: gnome-terminal, konsole, xfce4-terminal, ghostty, wezterm, alacritty, kitty, xterm)")
-}
-
-func syncInTerminal(nonInteractive bool, forceAuth bool, local bool) error {
- syncFlags := make([]string, 0, 3)
- if nonInteractive {
- syncFlags = append(syncFlags, "--yes")
- }
- if forceAuth {
- syncFlags = append(syncFlags, "--auth")
- }
- if local {
- syncFlags = append(syncFlags, "--local")
- }
- shellSyncCmd := "dms greeter sync"
- if len(syncFlags) > 0 {
- shellSyncCmd += " " + strings.Join(syncFlags, " ")
- }
- shellCmd := shellSyncCmd + `; echo; echo "Sync finished. Closing in 3 seconds..."; sleep 3`
- return runCommandInTerminal(shellCmd)
-}
-
-func resolveLocalWrapperShell() (string, error) {
- for _, shellName := range []string{"bash", "sh"} {
- shellPath, err := exec.LookPath(shellName)
- if err == nil {
- return shellPath, nil
- }
- }
- return "", fmt.Errorf("could not find bash or sh in PATH for local greeter wrapper")
-}
-
-func syncGreeter(nonInteractive bool, forceAuth bool, local bool) error {
- if !nonInteractive {
- fmt.Println("=== DMS Greeter Sync ===")
- fmt.Println()
- }
-
- logFunc := func(msg string) {
- fmt.Println(msg)
- }
-
- if !nonInteractive {
- fmt.Println("Detecting DMS installation...")
- }
- var dmsPath string
- var err error
- if local {
- dmsPath, err = resolveLocalDMSPath()
- if err != nil {
- return err
- }
- if !nonInteractive {
- fmt.Printf("✓ Using local DMS path: %s\n", dmsPath)
- }
- } else {
- dmsPath, err = greeter.DetectDMSPath()
- if err != nil {
- return err
- }
- if !nonInteractive {
- fmt.Printf("✓ Found DMS at: %s\n", dmsPath)
- }
- }
-
- if !isGreeterEnabled() {
- if nonInteractive {
- return fmt.Errorf("greeter is not enabled; run 'dms greeter install' or 'dms greeter enable' first")
- }
- fmt.Println("\n⚠ DMS greeter is not enabled in greetd config.")
- fmt.Print("Would you like to enable it now? (Y/n): ")
-
- var response string
- fmt.Scanln(&response)
- response = strings.ToLower(strings.TrimSpace(response))
-
- if response != "n" && response != "no" {
- if err := enableGreeter(false); err != nil {
- return err
- }
- } else {
- return fmt.Errorf("greeter must be enabled before syncing")
- }
- }
-
- if greeter.IsGreeterPackaged() && greeter.HasLegacyLocalGreeterWrapper() {
- return fmt.Errorf("legacy manual wrapper detected at /usr/local/bin/dms-greeter; remove it before using packaged dms-greeter: sudo rm -f /usr/local/bin/dms-greeter")
- }
-
- cacheDir := greeter.GreeterCacheDir
- if _, err := os.Stat(cacheDir); os.IsNotExist(err) {
- logFunc("Cache directory not found — attempting to create it...")
- }
-
- greeterGroup := greeter.DetectGreeterGroup()
- greeterGroupExists := utils.HasGroup(greeterGroup)
- if greeterGroupExists {
- currentUser, err := user.Current()
- if err != nil {
- return fmt.Errorf("failed to get current user: %w", err)
- }
-
- groupsCmd := exec.Command("groups", currentUser.Username)
- groupsOutput, err := groupsCmd.Output()
- if err != nil {
- return fmt.Errorf("failed to check groups: %w", err)
- }
-
- inGreeterGroup := strings.Contains(string(groupsOutput), greeterGroup)
- if !inGreeterGroup {
- if nonInteractive {
- logFunc(fmt.Sprintf("⚠ Not yet in %s group — will be added during sync (logout/login required to take effect).", greeterGroup))
- } else {
- fmt.Printf("\n⚠ Warning: You are not in the %s group.\n", greeterGroup)
- fmt.Printf("Would you like to add your user to the %s group? (Y/n): ", greeterGroup)
-
- var response string
- fmt.Scanln(&response)
- response = strings.ToLower(strings.TrimSpace(response))
-
- if response != "n" && response != "no" {
- fmt.Printf("\nAdding user to %s group...\n", greeterGroup)
- addUserCmd := exec.Command("sudo", "usermod", "-aG", greeterGroup, currentUser.Username)
- addUserCmd.Stdout = os.Stdout
- addUserCmd.Stderr = os.Stderr
- if err := addUserCmd.Run(); err != nil {
- return fmt.Errorf("failed to add user to %s group: %w", greeterGroup, err)
- }
- fmt.Printf("✓ User added to %s group\n", greeterGroup)
- fmt.Println("⚠ You will need to log out and back in for the group change to take effect")
- } else {
- return fmt.Errorf("aborted: user must be in the greeter group before syncing")
- }
- }
- }
- }
-
- compositor := detectConfiguredCompositor()
- if compositor == "" {
- compositors := greeter.DetectCompositors()
- switch len(compositors) {
- case 0:
- return fmt.Errorf("no supported compositors found")
- case 1:
- compositor = compositors[0]
- if !nonInteractive {
- fmt.Printf("✓ Using compositor: %s\n", compositor)
- }
- default:
- if nonInteractive {
- compositor = compositors[0]
- break
- }
- var err error
- compositor, err = promptCompositorChoice(compositors)
- if err != nil {
- return err
- }
- fmt.Printf("✓ Selected compositor: %s\n", compositor)
- }
- } else if !nonInteractive {
- fmt.Printf("✓ Detected compositor from config: %s\n", compositor)
- }
-
- if local {
- localWrapperScript := filepath.Join(dmsPath, "Modules", "Greetd", "assets", "dms-greeter")
- restoreWrapperOverride := func() {}
- if info, statErr := os.Stat(localWrapperScript); statErr == nil && !info.IsDir() {
- wrapperShell, shellErr := resolveLocalWrapperShell()
- if shellErr != nil {
- return shellErr
- }
- previousWrapperOverride, hadWrapperOverride := os.LookupEnv("DMS_GREETER_WRAPPER_CMD")
- wrapperCmdOverride := wrapperShell + " " + localWrapperScript
- _ = os.Setenv("DMS_GREETER_WRAPPER_CMD", wrapperCmdOverride)
- restoreWrapperOverride = func() {
- if hadWrapperOverride {
- _ = os.Setenv("DMS_GREETER_WRAPPER_CMD", previousWrapperOverride)
- } else {
- _ = os.Unsetenv("DMS_GREETER_WRAPPER_CMD")
- }
- }
- if !nonInteractive {
- fmt.Printf("✓ Using local greeter wrapper script: %s\n", localWrapperScript)
- }
- } else if !nonInteractive {
- fmt.Printf("ℹ Local wrapper script not found at %s; using system wrapper.\n", localWrapperScript)
- }
-
- fmt.Println("\nUpdating greetd command to use local DMS path...")
- err := greeter.ConfigureGreetd(dmsPath, compositor, logFunc, "")
- restoreWrapperOverride()
- if err != nil {
- return fmt.Errorf("failed to apply local greeter path: %w", err)
- }
- if !nonInteractive {
- fmt.Println("ℹ Local mode applies both DMS path override (-p) and local wrapper behavior when available.")
- }
- } else {
- greeterPathForConfig := ""
- if !greeter.IsGreeterPackaged() {
- greeterPathForConfig = dmsPath
- }
- fmt.Println("\nUpdating greetd command...")
- if err := greeter.ConfigureGreetd(greeterPathForConfig, compositor, logFunc, ""); err != nil {
- return fmt.Errorf("failed to update greetd command: %w", err)
- }
- }
-
- fmt.Println("\nSetting up permissions and ACLs...")
- greeter.RemediateStaleACLs(logFunc, "")
- greeter.RemediateStaleAppArmor(logFunc, "")
- if err := greeter.SetupDMSGroup(logFunc, ""); err != nil {
- return err
- }
- if err := greeter.EnsureGreeterCacheDir(logFunc, ""); err != nil {
- return fmt.Errorf("failed to ensure greeter cache directory at %s: %w\nRun: sudo mkdir -p %s && sudo chown root:%s %s && sudo chmod 2770 %s", cacheDir, err, cacheDir, greeterGroup, cacheDir, cacheDir)
- }
-
- fmt.Println("\nSynchronizing DMS configurations...")
- if err := syncGreeterConfigsAndAuth(dmsPath, compositor, logFunc, sharedpam.SyncAuthOptions{
- ForceGreeterAuth: forceAuth,
- }, func() {
- fmt.Println("\nConfiguring authentication...")
- }); err != nil {
- return err
- }
-
- if greeter.IsAppArmorEnabled() {
- fmt.Println("\nConfiguring AppArmor profile...")
- if err := greeter.InstallAppArmorProfile(logFunc, ""); err != nil {
- logFunc(fmt.Sprintf("⚠ AppArmor profile setup failed: %v", err))
- }
- }
-
- fmt.Println("\n=== Sync Complete ===")
- fmt.Println("\nYour theme, settings, and wallpaper configuration have been synced with the greeter.")
- fmt.Println("Shared authentication settings were also checked and reconciled where needed.")
- if forceAuth {
- fmt.Println("Authentication has been configured for fingerprint and U2F (where modules exist).")
- }
- fmt.Println("The changes will be visible on the next login screen.")
-
- return nil
-}
-
-func hasDmsShellQml(dir string) bool {
- info, err := os.Stat(filepath.Join(dir, "shell.qml"))
- return err == nil && !info.IsDir()
-}
-
-func resolveDMSLocalCandidate(path string) (string, bool) {
- if path == "" {
- return "", false
- }
- if hasDmsShellQml(path) {
- abs, err := filepath.Abs(path)
- if err != nil {
- return path, true
- }
- return abs, true
- }
-
- quickshellPath := filepath.Join(path, "quickshell")
- if hasDmsShellQml(quickshellPath) {
- abs, err := filepath.Abs(quickshellPath)
- if err != nil {
- return quickshellPath, true
- }
- return abs, true
- }
-
- return "", false
-}
-
-func resolveLocalDMSPath() (string, error) {
- if override := strings.TrimSpace(os.Getenv("DMS_LOCAL_PATH")); override != "" {
- if resolved, ok := resolveDMSLocalCandidate(override); ok {
- return resolved, nil
- }
- return "", fmt.Errorf("DMS_LOCAL_PATH is set but does not point to a valid DMS quickshell path: %s", override)
- }
-
- wd, err := os.Getwd()
- if err != nil {
- return "", fmt.Errorf("failed to get current directory: %w", err)
- }
-
- dir := wd
- for {
- if resolved, ok := resolveDMSLocalCandidate(dir); ok {
- return resolved, nil
- }
-
- parent := filepath.Dir(dir)
- if parent == dir {
- break
- }
- dir = parent
- }
-
- homeDir, err := os.UserHomeDir()
- if err == nil && homeDir != "" {
- for _, candidate := range []string{
- filepath.Join(homeDir, "dms"),
- filepath.Join(homeDir, "DankMaterialShell"),
- filepath.Join(homeDir, "dankmaterialshell"),
- filepath.Join(homeDir, "projects", "dms"),
- filepath.Join(homeDir, "src", "dms"),
- } {
- if resolved, ok := resolveDMSLocalCandidate(candidate); ok {
- return resolved, nil
- }
- }
-
- if entries, readErr := os.ReadDir(homeDir); readErr == nil {
- for _, entry := range entries {
- if !entry.IsDir() {
- continue
- }
- name := strings.ToLower(entry.Name())
- if !strings.Contains(name, "dms") && !strings.Contains(name, "dank") {
- continue
- }
- if resolved, ok := resolveDMSLocalCandidate(filepath.Join(homeDir, entry.Name())); ok {
- return resolved, nil
- }
- }
- }
- }
-
- return "", fmt.Errorf("could not locate a local DMS checkout from %s; run from repo root or set DMS_LOCAL_PATH=/absolute/path/to/repo", wd)
-}
-
-func disableDisplayManager(dmName string) (bool, error) {
- state, err := getSystemdServiceState(dmName)
- if err != nil {
- return false, fmt.Errorf("failed to check %s state: %w", dmName, err)
- }
-
- if !state.Exists {
- return false, nil
- }
-
- fmt.Printf("\nChecking %s...\n", dmName)
- fmt.Printf(" Current state: enabled=%s\n", state.EnabledState)
-
- actionTaken := false
-
- if state.NeedsDisable {
- var disableCmd *exec.Cmd
- var actionVerb string
-
- if state.EnabledState == "static" {
- fmt.Printf(" Masking %s (static service cannot be disabled)...\n", dmName)
- disableCmd = exec.Command("sudo", "systemctl", "mask", dmName)
- actionVerb = "masked"
- } else {
- fmt.Printf(" Disabling %s...\n", dmName)
- disableCmd = exec.Command("sudo", "systemctl", "disable", dmName)
- actionVerb = "disabled"
- }
-
- disableCmd.Stdout = os.Stdout
- disableCmd.Stderr = os.Stderr
- if err := disableCmd.Run(); err != nil {
- return actionTaken, fmt.Errorf("failed to disable/mask %s: %w", dmName, err)
- }
-
- enabledState, shouldDisable, verifyErr := checkSystemdServiceEnabled(dmName)
- if verifyErr != nil {
- fmt.Printf(" ⚠ Warning: Could not verify %s was %s: %v\n", dmName, actionVerb, verifyErr)
- } else if shouldDisable {
- return actionTaken, fmt.Errorf("%s is still in state '%s' after %s operation", dmName, enabledState, actionVerb)
- } else {
- fmt.Printf(" ✓ %s %s (now: %s)\n", cases.Title(language.English).String(actionVerb), dmName, enabledState)
- }
-
- actionTaken = true
- } else {
- if state.EnabledState == "masked" || state.EnabledState == "masked-runtime" {
- fmt.Printf(" ✓ %s is already masked\n", dmName)
- } else {
- fmt.Printf(" ✓ %s is already disabled\n", dmName)
- }
- }
-
- return actionTaken, nil
-}
-
-func ensureGreetdEnabled() error {
- fmt.Println("\nChecking greetd service status...")
-
- state, err := getSystemdServiceState("greetd")
- if err != nil {
- return fmt.Errorf("failed to check greetd state: %w", err)
- }
-
- if !state.Exists {
- return fmt.Errorf("greetd service not found. Please install greetd first")
- }
-
- fmt.Printf(" Current state: %s\n", state.EnabledState)
-
- if state.EnabledState == "masked" || state.EnabledState == "masked-runtime" {
- fmt.Println(" Unmasking greetd...")
- unmaskCmd := exec.Command("sudo", "systemctl", "unmask", "greetd")
- unmaskCmd.Stdout = os.Stdout
- unmaskCmd.Stderr = os.Stderr
- if err := unmaskCmd.Run(); err != nil {
- return fmt.Errorf("failed to unmask greetd: %w", err)
- }
- fmt.Println(" ✓ Unmasked greetd")
- }
-
- if state.EnabledState == "enabled" || state.EnabledState == "enabled-runtime" {
- fmt.Println(" Reasserting greetd as active display manager...")
- } else {
- fmt.Println(" Enabling greetd service...")
- }
-
- enableCmd := exec.Command("sudo", "systemctl", "enable", "--force", "greetd")
- enableCmd.Stdout = os.Stdout
- enableCmd.Stderr = os.Stderr
- if err := enableCmd.Run(); err != nil {
- return fmt.Errorf("failed to enable greetd: %w", err)
- }
-
- enabledState, _, verifyErr := checkSystemdServiceEnabled("greetd")
- if verifyErr != nil {
- fmt.Printf(" ⚠ Warning: Could not verify greetd enabled state: %v\n", verifyErr)
- } else {
- switch enabledState {
- case "enabled", "enabled-runtime", "static", "indirect", "alias":
- fmt.Printf(" ✓ greetd enabled (state: %s)\n", enabledState)
- default:
- return fmt.Errorf("greetd is still in state '%s' after enable operation", enabledState)
- }
- }
-
- return nil
-}
-
-func ensureGraphicalTarget() error {
- getDefaultCmd := exec.Command("systemctl", "get-default")
- currentTarget, err := getDefaultCmd.Output()
- if err != nil {
- fmt.Println("⚠ Warning: Could not detect current default systemd target")
- return nil
- }
-
- currentTargetStr := strings.TrimSpace(string(currentTarget))
- if currentTargetStr != "graphical.target" {
- fmt.Printf("\nSetting graphical.target as default (current: %s)...\n", currentTargetStr)
- setDefaultCmd := exec.Command("sudo", "systemctl", "set-default", "graphical.target")
- setDefaultCmd.Stdout = os.Stdout
- setDefaultCmd.Stderr = os.Stderr
- if err := setDefaultCmd.Run(); err != nil {
- fmt.Println("⚠ Warning: Failed to set graphical.target as default")
- fmt.Println(" Greeter may not start on boot. Run manually:")
- fmt.Println(" sudo systemctl set-default graphical.target")
- return nil
- }
- fmt.Println("✓ Set graphical.target as default")
- } else {
- fmt.Println("✓ Default target already set to graphical.target")
- }
-
- return nil
-}
-
-func handleConflictingDisplayManagers() error {
- fmt.Println("\n=== Checking for Conflicting Display Managers ===")
-
- conflictingDMs := []string{"gdm", "gdm3", "lightdm", "sddm", "lxdm", "xdm", "cosmic-greeter"}
-
- disabledAny := false
- var errors []string
-
- for _, dm := range conflictingDMs {
- actionTaken, err := disableDisplayManager(dm)
- if err != nil {
- errMsg := fmt.Sprintf("Failed to handle %s: %v", dm, err)
- errors = append(errors, errMsg)
- fmt.Printf(" ⚠⚠⚠ ERROR: %s\n", errMsg)
- continue
- }
- if actionTaken {
- disabledAny = true
- }
- }
-
- if len(errors) > 0 {
- fmt.Println("\n╔════════════════════════════════════════════════════════════╗")
- fmt.Println("║ ⚠⚠⚠ ERRORS OCCURRED ⚠⚠⚠ ║")
- fmt.Println("╚════════════════════════════════════════════════════════════╝")
- fmt.Println("\nSome display managers could not be disabled:")
- for _, err := range errors {
- fmt.Printf(" ✗ %s\n", err)
- }
- fmt.Println("\nThis may prevent greetd from starting properly.")
- fmt.Println("You may need to manually disable them before greetd will work.")
- fmt.Println("\nManual commands to try:")
- for _, dm := range conflictingDMs {
- fmt.Printf(" sudo systemctl disable %s\n", dm)
- fmt.Printf(" sudo systemctl mask %s\n", dm)
- }
- fmt.Print("\nContinue with greeter enablement anyway? (Y/n): ")
-
- var response string
- fmt.Scanln(&response)
- response = strings.ToLower(strings.TrimSpace(response))
-
- if response == "n" || response == "no" {
- return fmt.Errorf("aborted due to display manager conflicts")
- }
- fmt.Println("\nContinuing despite errors...")
- }
-
- if !disabledAny && len(errors) == 0 {
- fmt.Println("\n✓ No conflicting display managers found")
- } else if disabledAny && len(errors) == 0 {
- fmt.Println("\n✓ Successfully handled all conflicting display managers")
- }
-
- return nil
-}
-
-func enableGreeter(nonInteractive bool) error {
- fmt.Println("=== DMS Greeter Enable ===")
- fmt.Println()
-
- configPath := "/etc/greetd/config.toml"
- if _, err := os.Stat(configPath); os.IsNotExist(err) {
- return fmt.Errorf("greetd config not found at %s\nPlease install greetd first", configPath)
- } else if err != nil {
- return fmt.Errorf("failed to access greetd config at %s: %w", configPath, err)
- }
-
- if greeter.IsGreeterPackaged() && greeter.HasLegacyLocalGreeterWrapper() {
- return fmt.Errorf("legacy manual wrapper detected at /usr/local/bin/dms-greeter; remove it before using packaged dms-greeter: sudo rm -f /usr/local/bin/dms-greeter")
- }
-
- configAlreadyCorrect := isGreeterEnabled()
- configuredCompositor := detectConfiguredCompositor()
-
- logFunc := func(msg string) {
- fmt.Println(msg)
- }
- greeterGroup := greeter.DetectGreeterGroup()
-
- if configAlreadyCorrect {
- fmt.Println("✓ Greeter is already configured with dms-greeter")
- if configuredCompositor != "" {
- fmt.Printf("✓ Configured compositor: %s\n", configuredCompositor)
- }
-
- fmt.Println("\nSetting up dms-greeter group and permissions...")
- if err := greeter.SetupDMSGroup(logFunc, ""); err != nil {
- return err
- }
- if err := greeter.EnsureGreeterCacheDir(logFunc, ""); err != nil {
- fmt.Printf("⚠ Could not ensure cache directory: %v\n Run: sudo mkdir -p %s && sudo chown root:%s %s && sudo chmod 2770 %s\n", err, greeter.GreeterCacheDir, greeterGroup, greeter.GreeterCacheDir, greeter.GreeterCacheDir)
- }
-
- if err := ensureGraphicalTarget(); err != nil {
- return err
- }
-
- if err := handleConflictingDisplayManagers(); err != nil {
- return err
- }
-
- if err := ensureGreetdEnabled(); err != nil {
- return err
- }
-
- fmt.Println("\n=== Enable Complete ===")
- fmt.Println("\nGreeter configuration verified and system state corrected.")
- fmt.Println("To start the greeter now, run:")
- fmt.Println(" sudo systemctl start greetd")
- fmt.Println("\nOr reboot to see the greeter at boot time.")
-
- return nil
- }
-
- if !nonInteractive {
- fmt.Print("\nThis will configure greetd to use the DMS greeter and may disable other display managers. Continue? [Y/n]: ")
- var response string
- fmt.Scanln(&response)
- if strings.ToLower(strings.TrimSpace(response)) == "n" || strings.ToLower(strings.TrimSpace(response)) == "no" {
- fmt.Println("Aborted.")
- return nil
- }
- fmt.Println()
- }
-
- fmt.Println("Detecting installed compositors...")
- compositors := greeter.DetectCompositors()
-
- if utils.CommandExists("sway") {
- compositors = append(compositors, "sway")
- }
-
- if len(compositors) == 0 {
- return fmt.Errorf("no supported compositors found (niri, Hyprland, or sway required)")
- }
-
- var selectedCompositor string
- if len(compositors) == 1 {
- selectedCompositor = compositors[0]
- fmt.Printf("✓ Found compositor: %s\n", selectedCompositor)
- } else {
- var err error
- selectedCompositor, err = promptCompositorChoice(compositors)
- if err != nil {
- return err
- }
- fmt.Printf("✓ Selected compositor: %s\n", selectedCompositor)
- }
-
- greeterPathForConfig := ""
- if !greeter.IsGreeterPackaged() {
- dmsPath, err := greeter.DetectDMSPath()
- if err != nil {
- return fmt.Errorf("failed to detect DMS path for manual greeter configuration: %w", err)
- }
- greeterPathForConfig = dmsPath
- }
- if err := greeter.ConfigureGreetd(greeterPathForConfig, selectedCompositor, logFunc, ""); err != nil {
- return fmt.Errorf("failed to configure greetd: %w", err)
- }
-
- fmt.Println("\nSetting up dms-greeter group and permissions...")
- if err := greeter.SetupDMSGroup(logFunc, ""); err != nil {
- return err
- }
- if err := greeter.EnsureGreeterCacheDir(logFunc, ""); err != nil {
- fmt.Printf("⚠ Could not ensure cache directory: %v\n Run: sudo mkdir -p %s && sudo chown root:%s %s && sudo chmod 2770 %s\n", err, greeter.GreeterCacheDir, greeterGroup, greeter.GreeterCacheDir, greeter.GreeterCacheDir)
- }
-
- if greeter.IsAppArmorEnabled() {
- if err := greeter.InstallAppArmorProfile(logFunc, ""); err != nil {
- logFunc(fmt.Sprintf("⚠ AppArmor profile setup failed: %v", err))
- }
- }
-
- if err := ensureGraphicalTarget(); err != nil {
- return err
- }
-
- if err := handleConflictingDisplayManagers(); err != nil {
- return err
- }
-
- if err := ensureGreetdEnabled(); err != nil {
- return err
- }
-
- fmt.Println("\n=== Enable Complete ===")
- fmt.Println("\nTo start the greeter now, run:")
- fmt.Println(" sudo systemctl start greetd")
- fmt.Println("\nOr reboot to see the greeter at boot time.")
-
- return nil
-}
-
-func isGreeterEnabled() bool {
- command := readDefaultSessionCommand("/etc/greetd/config.toml")
- return command != "" && strings.Contains(command, "dms-greeter")
-}
-
-func detectConfiguredCompositor() string {
- command := strings.ToLower(readDefaultSessionCommand("/etc/greetd/config.toml"))
- switch {
- case strings.Contains(command, "--command niri"):
- return "niri"
- case strings.Contains(command, "--command hyprland"):
- return "hyprland"
- case strings.Contains(command, "--command sway"):
- return "sway"
- }
- return ""
-}
-
-func stripTomlComment(line string) string {
- trimmed := strings.TrimSpace(line)
- if idx := strings.Index(trimmed, "#"); idx >= 0 {
- return strings.TrimSpace(trimmed[:idx])
- }
- return trimmed
-}
-
-func parseTomlSection(line string) (string, bool) {
- trimmed := stripTomlComment(line)
- if len(trimmed) < 3 || !strings.HasPrefix(trimmed, "[") || !strings.HasSuffix(trimmed, "]") {
- return "", false
- }
- return strings.TrimSpace(trimmed[1 : len(trimmed)-1]), true
-}
-
-func readDefaultSessionCommand(configPath string) string {
- data, err := os.ReadFile(configPath)
- if err != nil {
- return ""
- }
-
- inDefaultSession := false
- for line := range strings.SplitSeq(string(data), "\n") {
- if section, ok := parseTomlSection(line); ok {
- inDefaultSession = section == "default_session"
- continue
- }
-
- if !inDefaultSession {
- continue
- }
-
- trimmed := stripTomlComment(line)
- if !strings.HasPrefix(trimmed, "command =") && !strings.HasPrefix(trimmed, "command=") {
- continue
- }
-
- parts := strings.SplitN(trimmed, "=", 2)
- if len(parts) != 2 {
- continue
- }
-
- command := strings.Trim(strings.TrimSpace(parts[1]), `"`)
- if command != "" {
- return command
- }
- }
-
- return ""
-}
-
-func extractGreeterCacheDirFromCommand(command string) string {
- if command == "" {
- return greeter.GreeterCacheDir
- }
- tokens := strings.Fields(command)
- for i := 0; i < len(tokens); i++ {
- token := strings.Trim(tokens[i], "\"")
- if token == "--cache-dir" && i+1 < len(tokens) {
- return strings.Trim(tokens[i+1], "\"")
- }
- if strings.HasPrefix(token, "--cache-dir=") {
- value := strings.TrimPrefix(token, "--cache-dir=")
- value = strings.Trim(value, "\"")
- if value != "" {
- return value
- }
- }
- }
- return greeter.GreeterCacheDir
-}
-
-func extractGreeterWrapperFromCommand(command string) string {
- if command == "" {
- return ""
- }
- tokens := strings.Fields(command)
- if len(tokens) == 0 {
- return ""
- }
- wrapper := strings.Trim(tokens[0], "\"")
- if wrapper == "" {
- return ""
- }
- if len(tokens) > 1 {
- next := strings.Trim(tokens[1], "\"")
- if next != "" && (filepath.Base(wrapper) == "bash" || filepath.Base(wrapper) == "sh") && strings.Contains(filepath.Base(next), "dms-greeter") {
- return fmt.Sprintf("%s (script: %s)", wrapper, next)
- }
- }
- return wrapper
-}
-
-func extractGreeterPathOverrideFromCommand(command string) string {
- if command == "" {
- return ""
- }
- tokens := strings.Fields(command)
- for i := 0; i < len(tokens); i++ {
- token := strings.Trim(tokens[i], "\"")
- if (token == "-p" || token == "--path") && i+1 < len(tokens) {
- return strings.Trim(tokens[i+1], "\"")
- }
- if strings.HasPrefix(token, "--path=") {
- value := strings.TrimPrefix(token, "--path=")
- value = strings.Trim(value, "\"")
- if value != "" {
- return value
- }
- }
- }
- return ""
-}
-
-func parseManagedGreeterPamAuth(pamText string) (managed bool, fingerprint bool, u2f bool, legacy bool) {
- return sharedpam.ParseManagedGreeterPamAuth(pamText)
-}
-
-func packageInstallHint() string {
- osInfo, err := distros.GetOSInfo()
- if err != nil {
- return "Install package: dms-greeter"
- }
- config, exists := distros.Registry[osInfo.Distribution.ID]
- if !exists {
- return "Install package: dms-greeter"
- }
-
- switch config.Family {
- case distros.FamilyDebian:
- return "Install with 'sudo apt install dms-greeter' (requires DankLinux OBS repo — see https://danklinux.com/docs/dankgreeter/installation#debian)"
- case distros.FamilySUSE:
- return "Install with 'sudo zypper install dms-greeter' (requires DankLinux OBS repo — see https://danklinux.com/docs/dankgreeter/installation#opensuse)"
- case distros.FamilyUbuntu:
- return "Install with 'sudo apt install dms-greeter' (requires ppa:avengemedia/danklinux: sudo add-apt-repository ppa:avengemedia/danklinux)"
- case distros.FamilyFedora:
- return "Install with 'sudo dnf install dms-greeter' (requires COPR: sudo dnf copr enable avengemedia/danklinux)"
- case distros.FamilyArch:
- return "Install from AUR with 'paru -S greetd-dms-greeter-git' or 'yay -S greetd-dms-greeter-git'"
- default:
- return "Run 'dms greeter install' to install greeter"
- }
-}
-
-func systemPamManagerRemediationHint() string {
- osInfo, err := distros.GetOSInfo()
- if err != nil {
- return "Disable it in your PAM manager (authselect/pam-auth-update) or in the included PAM stack to force password-only greeter login."
- }
- config, exists := distros.Registry[osInfo.Distribution.ID]
- if !exists {
- return "Disable it in your PAM manager (authselect/pam-auth-update) or in the included PAM stack to force password-only greeter login."
- }
-
- switch config.Family {
- case distros.FamilyFedora:
- return "Disable it in authselect to force password-only greeter login."
- case distros.FamilyDebian, distros.FamilyUbuntu:
- return "Disable it in pam-auth-update to force password-only greeter login."
- default:
- return "Disable it in your distro PAM manager (authselect/pam-auth-update) or in the included PAM stack to force password-only greeter login."
- }
-}
-
-func isPackageOnlyGreeterDistro() bool {
- osInfo, err := distros.GetOSInfo()
- if err != nil {
- return false
- }
- config, exists := distros.Registry[osInfo.Distribution.ID]
- if !exists {
- return false
- }
- return config.Family == distros.FamilyDebian ||
- config.Family == distros.FamilySUSE ||
- config.Family == distros.FamilyUbuntu ||
- config.Family == distros.FamilyFedora ||
- config.Family == distros.FamilyArch
-}
-
-func promptCompositorChoice(compositors []string) (string, error) {
- fmt.Println("\nMultiple compositors detected:")
- for i, comp := range compositors {
- fmt.Printf("%d) %s\n", i+1, comp)
- }
-
- var response string
- fmt.Print("Choose compositor for greeter: ")
- fmt.Scanln(&response)
- response = strings.TrimSpace(response)
-
- choice := 0
- fmt.Sscanf(response, "%d", &choice)
-
- if choice < 1 || choice > len(compositors) {
- return "", fmt.Errorf("invalid choice")
- }
-
- return compositors[choice-1], nil
-}
-
-func checkGreeterStatus() error {
- fmt.Println("=== DMS Greeter Status ===")
- fmt.Println()
-
- homeDir, err := os.UserHomeDir()
- if err != nil {
- return fmt.Errorf("failed to get user home directory: %w", err)
- }
-
- currentUser, err := user.Current()
- if err != nil {
- return fmt.Errorf("failed to get current user: %w", err)
- }
-
- configPath := "/etc/greetd/config.toml"
- configuredCommand := ""
- allGood := true
- fmt.Println("Greeter Configuration:")
- if _, err := os.ReadFile(configPath); err == nil {
- configuredCommand = readDefaultSessionCommand(configPath)
- if configuredCommand != "" && strings.Contains(configuredCommand, "dms-greeter") {
- fmt.Println(" ✓ Greeter is enabled")
- if wrapper := extractGreeterWrapperFromCommand(configuredCommand); wrapper != "" {
- fmt.Printf(" Wrapper: %s\n", wrapper)
- }
- if pathOverride := extractGreeterPathOverrideFromCommand(configuredCommand); pathOverride != "" {
- fmt.Printf(" DMS path override: %s\n", pathOverride)
- }
-
- compositor := detectConfiguredCompositor()
- switch compositor {
- case "niri":
- fmt.Println(" Compositor: niri")
- case "hyprland":
- fmt.Println(" Compositor: Hyprland")
- case "sway":
- fmt.Println(" Compositor: sway")
- default:
- fmt.Println(" Compositor: unknown")
- }
- } else {
- fmt.Println(" ✗ Greeter is NOT enabled")
- fmt.Println(" Run 'dms greeter enable' to enable it, or use the Activate button in Settings → Greeter, then Sync.")
- allGood = false
- }
- } else {
- fmt.Println(" ✗ Greeter config not found")
- fmt.Printf(" %s\n", packageInstallHint())
- allGood = false
- }
-
- fmt.Println("\nGroup Membership:")
- groupsCmd := exec.Command("groups", currentUser.Username)
- groupsOutput, err := groupsCmd.Output()
- if err != nil {
- return fmt.Errorf("failed to check groups: %w", err)
- }
-
- greeterGroup := greeter.DetectGreeterGroup()
- inGreeterGroup := strings.Contains(string(groupsOutput), greeterGroup)
- if inGreeterGroup {
- fmt.Printf(" ✓ User is in %s group\n", greeterGroup)
- } else {
- fmt.Printf(" ✗ User is NOT in %s group\n", greeterGroup)
- fmt.Println(" Run 'dms greeter sync' to set up group membership and permissions")
- }
-
- cacheDir := extractGreeterCacheDirFromCommand(configuredCommand)
- fmt.Println("\nGreeter Cache Directory:")
- fmt.Printf(" Effective cache dir: %s\n", cacheDir)
- if cacheDir != greeter.GreeterCacheDir {
- fmt.Printf(" ⚠ Non-default cache dir detected (default: %s)\n", greeter.GreeterCacheDir)
- }
- if stat, err := os.Stat(cacheDir); err == nil && stat.IsDir() {
- fmt.Printf(" ✓ %s exists\n", cacheDir)
- requiredSubdirs := []string{".local/state", ".local/share", ".cache"}
- missingSubdirs := false
- for _, sub := range requiredSubdirs {
- subPath := filepath.Join(cacheDir, sub)
- if _, err := os.Stat(subPath); os.IsNotExist(err) {
- fmt.Printf(" ⚠ Missing required subdir: %s\n", subPath)
- missingSubdirs = true
- }
- }
- if missingSubdirs {
- fmt.Println(" Run 'dms greeter sync' to initialize the cache directory structure.")
- allGood = false
- }
- } else {
- fmt.Printf(" ✗ %s not found\n", cacheDir)
- fmt.Printf(" %s\n", packageInstallHint())
- return nil
- }
-
- fmt.Println("\nConfiguration Symlinks:")
- colorSyncInfo, colorSyncErr := greeter.ResolveGreeterColorSyncInfo(homeDir)
- if colorSyncErr != nil {
- fmt.Printf(" ✗ Failed to resolve expected greeter color source: %v\n", colorSyncErr)
- allGood = false
- colorSyncInfo = greeter.GreeterColorSyncInfo{
- SourcePath: filepath.Join(homeDir, ".cache", "DankMaterialShell", "dms-colors.json"),
- }
- }
-
- colorThemeDesc := "Color theme"
- if colorSyncInfo.UsesDynamicWallpaperOverride {
- colorThemeDesc = "Color theme (greeter wallpaper override)"
- }
-
- symlinks := []struct {
- source string
- target string
- desc string
- }{
- {
- source: filepath.Join(homeDir, ".config", "DankMaterialShell", "settings.json"),
- target: filepath.Join(cacheDir, "settings.json"),
- desc: "Settings",
- },
- {
- source: filepath.Join(homeDir, ".local", "state", "DankMaterialShell", "session.json"),
- target: filepath.Join(cacheDir, "session.json"),
- desc: "Session state",
- },
- {
- source: colorSyncInfo.SourcePath,
- target: filepath.Join(cacheDir, "colors.json"),
- desc: colorThemeDesc,
- },
- }
-
- for _, link := range symlinks {
- targetInfo, err := os.Lstat(link.target)
- if err != nil {
- fmt.Printf(" ✗ %s: symlink not found at %s\n", link.desc, link.target)
- allGood = false
- continue
- }
-
- if targetInfo.Mode()&os.ModeSymlink == 0 {
- fmt.Printf(" ✗ %s: %s is not a symlink\n", link.desc, link.target)
- allGood = false
- continue
- }
-
- linkDest, err := os.Readlink(link.target)
- if err != nil {
- fmt.Printf(" ✗ %s: failed to read symlink\n", link.desc)
- allGood = false
- continue
- }
-
- if linkDest != link.source {
- fmt.Printf(" ✗ %s: symlink points to wrong location\n", link.desc)
- fmt.Printf(" Expected: %s\n", link.source)
- fmt.Printf(" Got: %s\n", linkDest)
- allGood = false
- continue
- }
-
- if _, err := os.Stat(link.source); os.IsNotExist(err) {
- fmt.Printf(" ⚠ %s: symlink OK, but source file doesn't exist yet\n", link.desc)
- fmt.Printf(" Will be created when you run DMS\n")
- continue
- }
-
- fmt.Printf(" ✓ %s: synced correctly\n", link.desc)
- }
-
- if colorSyncInfo.UsesDynamicWallpaperOverride {
- fmt.Printf(" ℹ Dynamic theme uses greeter override colors from %s\n", colorSyncInfo.SourcePath)
- }
-
- fmt.Println("\nGreeter Wallpaper Override:")
- overridePath := filepath.Join(cacheDir, "greeter_wallpaper_override.jpg")
- if stat, err := os.Stat(overridePath); err == nil && !stat.IsDir() {
- fmt.Printf(" ✓ Override file present: %s\n", overridePath)
- } else if os.IsNotExist(err) {
- fmt.Println(" ℹ Override file not present (desktop/session wallpaper fallback in effect)")
- } else if err != nil {
- fmt.Printf(" ✗ Could not inspect override file: %v\n", err)
- allGood = false
- } else {
- fmt.Printf(" ✗ Override path is not a regular file: %s\n", overridePath)
- allGood = false
- }
-
- fmt.Println("\nGreeter PAM Authentication (DMS-managed block):")
- if greeter.IsNixOS() {
- fmt.Println(" ℹ NixOS detected: PAM is managed by NixOS modules.")
- fmt.Println(" Configure fingerprint/U2F via your greetd NixOS module (security.pam.services.greetd).")
- fmt.Println()
- if allGood && inGreeterGroup {
- fmt.Println("✓ All checks passed! Greeter is properly configured.")
- } else if !allGood {
- fmt.Println("⚠ Some issues detected. Run 'dms greeter sync' to repair configuration.")
- } else if !inGreeterGroup {
- fmt.Printf("⚠ User is not in %s group. Run 'dms greeter sync' after adding group membership.\n", greeterGroup)
- }
- return nil
- }
- greetdPamPath := "/etc/pam.d/greetd"
- pamData, err := os.ReadFile(greetdPamPath)
- if err != nil {
- fmt.Printf(" ✗ Failed to read %s: %v\n", greetdPamPath, err)
- allGood = false
- } else {
- managed, managedFprint, managedU2f, legacyManaged := parseManagedGreeterPamAuth(string(pamData))
- if managed {
- fmt.Println(" ✓ Managed auth block present")
- if managedFprint {
- fmt.Println(" - fingerprint: enabled")
- } else {
- fmt.Println(" - fingerprint: disabled")
- }
- if managedU2f {
- fmt.Println(" - security key (U2F): enabled")
- } else {
- fmt.Println(" - security key (U2F): disabled")
- }
- } else {
- fmt.Println(" ℹ No managed auth block present (DMS-managed fingerprint/U2F lines are disabled)")
- }
- if legacyManaged {
- fmt.Println(" ⚠ Legacy unmanaged DMS PAM lines detected. Run 'dms auth sync' to normalize.")
- allGood = false
- }
- enableFprintToggle, enableU2fToggle := false, false
- if enableFprint, enableU2f, settingsErr := sharedpam.ReadGreeterAuthToggles(homeDir); settingsErr == nil {
- enableFprintToggle = enableFprint
- enableU2fToggle = enableU2f
- } else {
- fmt.Printf(" ℹ Could not read greeter auth toggles from settings: %v\n", settingsErr)
- }
-
- includedFprintFile := sharedpam.DetectIncludedPamModule(string(pamData), "pam_fprintd.so")
- includedU2fFile := sharedpam.DetectIncludedPamModule(string(pamData), "pam_u2f.so")
- fprintAvailableForCurrentUser := sharedpam.FingerprintAuthAvailableForCurrentUser()
-
- if managedFprint && includedFprintFile != "" {
- fmt.Printf(" ⚠ pam_fprintd found in both DMS managed block and %s.\n", includedFprintFile)
- fmt.Println(" Double fingerprint auth detected — run 'dms auth sync' to resolve.")
- allGood = false
- }
- if managedU2f && includedU2fFile != "" {
- fmt.Printf(" ⚠ pam_u2f found in both DMS managed block and %s.\n", includedU2fFile)
- fmt.Println(" Double security-key auth detected — run 'dms auth sync' to resolve.")
- allGood = false
- }
-
- if includedFprintFile != "" && !managedFprint {
- if enableFprintToggle {
- fmt.Printf(" ℹ Fingerprint auth is enabled via included %s.\n", includedFprintFile)
- if fprintAvailableForCurrentUser {
- fmt.Println(" DMS toggle is enabled, and effective auth is coming from the included PAM stack.")
- } else {
- fmt.Println(" No enrolled fingerprints detected for the current user; password auth remains the effective path.")
- }
- } else {
- if fprintAvailableForCurrentUser {
- fmt.Printf(" ℹ Fingerprint auth is active via included %s while DMS fingerprint toggle is off.\n", includedFprintFile)
- fmt.Println(" Password login will work but may be delayed while the fingerprint module runs first.")
- fmt.Printf(" To eliminate the delay, %s\n", systemPamManagerRemediationHint())
- } else {
- fmt.Printf(" ℹ pam_fprintd is present via included %s, but no enrolled fingerprints were detected for user %s.\n", includedFprintFile, currentUser.Username)
- fmt.Println(" Password auth remains the effective login path.")
- }
- }
- }
- if includedU2fFile != "" && !managedU2f {
- if enableU2fToggle {
- fmt.Printf(" ℹ Security-key auth is enabled via included %s.\n", includedU2fFile)
- fmt.Println(" DMS toggle is enabled, but effective auth is coming from the included PAM stack.")
- } else {
- fmt.Printf(" ⚠ Security-key auth is active via included %s while DMS security-key toggle is off.\n", includedU2fFile)
- fmt.Printf(" %s\n", systemPamManagerRemediationHint())
- }
- }
- }
-
- fmt.Println("\nSecurity (AppArmor):")
- if !greeter.IsAppArmorEnabled() {
- fmt.Println(" ℹ AppArmor not enabled")
- } else {
- fmt.Println(" ℹ AppArmor is enabled")
-
- const appArmorProfilePath = "/etc/apparmor.d/usr.bin.dms-greeter"
- if _, err := os.Stat(appArmorProfilePath); os.IsNotExist(err) {
- fmt.Println(" ⚠ DMS AppArmor profile not installed")
- fmt.Println(" Run 'dms greeter sync' to install it and prevent potential TTY fallback")
- allGood = false
- } else {
- mode := appArmorProfileMode("dms-greeter")
- if mode != "" {
- fmt.Printf(" ✓ DMS AppArmor profile installed (%s mode)\n", mode)
- } else {
- fmt.Println(" ✓ DMS AppArmor profile installed")
- }
- }
-
- denialCount, denialSamples, denialErr := recentAppArmorGreeterDenials(3)
- if denialErr != nil {
- fmt.Printf(" ℹ Could not inspect AppArmor denials automatically: %v\n", denialErr)
- fmt.Println(" If greetd falls back to TTY, run: sudo journalctl -b -k | grep 'apparmor.*DENIED'")
- } else if denialCount > 0 {
- fmt.Printf(" ⚠ Found %d recent AppArmor denial(s) related to greeter runtime.\n", denialCount)
- fmt.Println(" This can cause greetd fallback to TTY (for example: 'Failed to create stream fd: Permission denied').")
- fmt.Println(" Review denials with: sudo journalctl -b -k | grep 'apparmor.*DENIED'")
- fmt.Println(" Then refine the profile with: sudo aa-logprof")
- for i, sample := range denialSamples {
- fmt.Printf(" %d) %s\n", i+1, sample)
- }
- allGood = false
- } else {
- fmt.Println(" ✓ No recent AppArmor denials detected for common greeter components")
- }
- }
-
- fmt.Println()
- if allGood && inGreeterGroup {
- fmt.Println("✓ All checks passed! Greeter is properly configured.")
- } else if !allGood {
- fmt.Println("⚠ Some issues detected. Run 'dms greeter sync' to repair configuration.")
- } else if !inGreeterGroup {
- fmt.Printf("⚠ User is not in %s group. Run 'dms greeter sync' after adding group membership.\n", greeterGroup)
- }
-
- return nil
-}
-
-func recentAppArmorGreeterDenials(sampleLimit int) (int, []string, error) {
- if sampleLimit <= 0 {
- sampleLimit = 3
- }
- if !utils.CommandExists("journalctl") {
- return 0, nil, fmt.Errorf("journalctl not found")
- }
-
- queries := [][]string{
- {"-b", "-k", "--no-pager", "-n", "2000", "-o", "cat"},
- {"-b", "--no-pager", "-n", "2000", "-o", "cat"},
- }
-
- seen := make(map[string]bool)
- samples := make([]string, 0, sampleLimit)
- total := 0
- var lastErr error
- successfulQuery := false
-
- for _, query := range queries {
- cmd := exec.Command("journalctl", query...)
- output, err := cmd.CombinedOutput()
- if err != nil {
- lastErr = err
- continue
- }
- successfulQuery = true
- total += collectGreeterAppArmorDenials(string(output), seen, &samples, sampleLimit)
- }
-
- if !successfulQuery && lastErr != nil {
- return 0, nil, lastErr
- }
-
- return total, samples, nil
-}
-
-func collectGreeterAppArmorDenials(text string, seen map[string]bool, samples *[]string, sampleLimit int) int {
- count := 0
- for _, rawLine := range strings.Split(text, "\n") {
- line := strings.TrimSpace(rawLine)
- if line == "" || !isGreeterRelatedAppArmorDenial(line) {
- continue
- }
- if seen[line] {
- continue
- }
- seen[line] = true
- count++
- if len(*samples) < sampleLimit {
- *samples = append(*samples, line)
- }
- }
- return count
-}
-
-func isGreeterRelatedAppArmorDenial(line string) bool {
- lower := strings.ToLower(line)
- if !strings.Contains(lower, "apparmor") || !strings.Contains(lower, "denied") {
- return false
- }
-
- greeterTokens := []string{
- "dms-greeter",
- "/usr/bin/dms-greeter",
- "greetd",
- "quickshell",
- "/usr/bin/qs",
- "/usr/bin/quickshell",
- "niri",
- "hyprland",
- "sway",
- "mango",
- "miracle",
- "labwc",
- "pipewire",
- "wireplumber",
- "stream fd",
- }
-
- for _, token := range greeterTokens {
- if strings.Contains(lower, token) {
- return true
- }
- }
- return false
-}
-
-// appArmorProfileMode returns "complain", "enforce", or "" for a named AppArmor profile.
-func appArmorProfileMode(profileName string) string {
- data, err := os.ReadFile("/sys/kernel/security/apparmor/profiles")
- if err != nil {
- return ""
- }
- for _, line := range strings.Split(string(data), "\n") {
- line = strings.TrimSpace(line)
- if !strings.Contains(line, profileName) {
- continue
- }
- lower := strings.ToLower(line)
- if strings.Contains(lower, "(complain)") {
- return "complain"
- }
- if strings.Contains(lower, "(enforce)") {
- return "enforce"
- }
- if strings.Contains(lower, "(kill)") {
- return "kill"
- }
- }
- return ""
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_greeter_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_greeter_test.go
deleted file mode 100644
index 775149f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_greeter_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package main
-
-import (
- "errors"
- "reflect"
- "testing"
-
- sharedpam "github.com/AvengeMedia/DankMaterialShell/core/internal/pam"
-)
-
-func TestSyncGreeterConfigsAndAuthDelegatesSharedAuth(t *testing.T) {
- origGreeterConfigSyncFn := greeterConfigSyncFn
- origSharedAuthSyncFn := sharedAuthSyncFn
- t.Cleanup(func() {
- greeterConfigSyncFn = origGreeterConfigSyncFn
- sharedAuthSyncFn = origSharedAuthSyncFn
- })
-
- var calls []string
- greeterConfigSyncFn = func(dmsPath, compositor string, logFunc func(string), sudoPassword string) error {
- if dmsPath != "/tmp/dms" {
- t.Fatalf("unexpected dmsPath %q", dmsPath)
- }
- if compositor != "niri" {
- t.Fatalf("unexpected compositor %q", compositor)
- }
- if sudoPassword != "" {
- t.Fatalf("expected empty sudoPassword, got %q", sudoPassword)
- }
- calls = append(calls, "configs")
- return nil
- }
-
- var gotOptions sharedpam.SyncAuthOptions
- sharedAuthSyncFn = func(logFunc func(string), sudoPassword string, options sharedpam.SyncAuthOptions) error {
- if sudoPassword != "" {
- t.Fatalf("expected empty sudoPassword, got %q", sudoPassword)
- }
- gotOptions = options
- calls = append(calls, "auth")
- return nil
- }
-
- err := syncGreeterConfigsAndAuth("/tmp/dms", "niri", func(string) {}, sharedpam.SyncAuthOptions{
- ForceGreeterAuth: true,
- }, func() {
- calls = append(calls, "before-auth")
- })
- if err != nil {
- t.Fatalf("syncGreeterConfigsAndAuth returned error: %v", err)
- }
-
- wantCalls := []string{"configs", "before-auth", "auth"}
- if !reflect.DeepEqual(calls, wantCalls) {
- t.Fatalf("call order = %v, want %v", calls, wantCalls)
- }
- if !gotOptions.ForceGreeterAuth {
- t.Fatalf("expected ForceGreeterAuth to be true, got %+v", gotOptions)
- }
-}
-
-func TestSyncGreeterConfigsAndAuthStopsOnConfigError(t *testing.T) {
- origGreeterConfigSyncFn := greeterConfigSyncFn
- origSharedAuthSyncFn := sharedAuthSyncFn
- t.Cleanup(func() {
- greeterConfigSyncFn = origGreeterConfigSyncFn
- sharedAuthSyncFn = origSharedAuthSyncFn
- })
-
- greeterConfigSyncFn = func(string, string, func(string), string) error {
- return errors.New("config sync failed")
- }
-
- authCalled := false
- sharedAuthSyncFn = func(func(string), string, sharedpam.SyncAuthOptions) error {
- authCalled = true
- return nil
- }
-
- err := syncGreeterConfigsAndAuth("/tmp/dms", "niri", func(string) {}, sharedpam.SyncAuthOptions{}, nil)
- if err == nil || err.Error() != "config sync failed" {
- t.Fatalf("expected config sync error, got %v", err)
- }
- if authCalled {
- t.Fatal("expected auth sync not to run after config sync failure")
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_keybinds.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_keybinds.go
deleted file mode 100644
index cdb15ac..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_keybinds.go
+++ /dev/null
@@ -1,265 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "fmt"
- "os"
- "path/filepath"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds/providers"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/spf13/cobra"
-)
-
-var keybindsCmd = &cobra.Command{
- Use: "keybinds",
- Aliases: []string{"cheatsheet", "chsht"},
- Short: "Manage keybinds and cheatsheets",
- Long: "Display and manage keybinds and cheatsheets for various applications",
-}
-
-var keybindsListCmd = &cobra.Command{
- Use: "list",
- Short: "List available providers",
- Long: "List all available keybind/cheatsheet providers",
- Run: runKeybindsList,
-}
-
-var keybindsShowCmd = &cobra.Command{
- Use: "show <provider>",
- Short: "Show keybinds for a provider",
- Long: "Display keybinds/cheatsheet for the specified provider",
- Args: cobra.ExactArgs(1),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) != 0 {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
- registry := keybinds.GetDefaultRegistry()
- return registry.List(), cobra.ShellCompDirectiveNoFileComp
- },
- Run: runKeybindsShow,
-}
-
-var keybindsSetCmd = &cobra.Command{
- Use: "set <provider> <key> <action>",
- Short: "Set a keybind override",
- Long: "Create or update a keybind override for the specified provider",
- Args: cobra.ExactArgs(3),
- Run: runKeybindsSet,
-}
-
-var keybindsRemoveCmd = &cobra.Command{
- Use: "remove <provider> <key>",
- Short: "Remove a keybind override",
- Long: "Remove a keybind override from the specified provider",
- Args: cobra.ExactArgs(2),
- Run: runKeybindsRemove,
-}
-
-func init() {
- keybindsListCmd.Flags().BoolP("json", "j", false, "Output as JSON")
- keybindsShowCmd.Flags().String("path", "", "Override config path for the provider")
- keybindsSetCmd.Flags().String("desc", "", "Description for hotkey overlay")
- keybindsSetCmd.Flags().Bool("allow-when-locked", false, "Allow when screen is locked")
- keybindsSetCmd.Flags().Int("cooldown-ms", 0, "Cooldown in milliseconds")
- keybindsSetCmd.Flags().Bool("no-repeat", false, "Disable key repeat")
- keybindsSetCmd.Flags().Bool("no-inhibiting", false, "Keep bind active when shortcuts are inhibited (allow-inhibiting=false)")
- keybindsSetCmd.Flags().String("replace-key", "", "Original key to replace (removes old key)")
- keybindsSetCmd.Flags().String("flags", "", "Hyprland bind flags (e.g., 'e' for repeat, 'l' for locked, 'r' for release)")
-
- keybindsCmd.AddCommand(keybindsListCmd)
- keybindsCmd.AddCommand(keybindsShowCmd)
- keybindsCmd.AddCommand(keybindsSetCmd)
- keybindsCmd.AddCommand(keybindsRemoveCmd)
-
- keybinds.SetJSONProviderFactory(func(filePath string) (keybinds.Provider, error) {
- return providers.NewJSONFileProvider(filePath)
- })
-
- initializeProviders()
-}
-
-func initializeProviders() {
- registry := keybinds.GetDefaultRegistry()
-
- hyprlandProvider := providers.NewHyprlandProvider("")
- if err := registry.Register(hyprlandProvider); err != nil {
- log.Warnf("Failed to register Hyprland provider: %v", err)
- }
-
- mangowcProvider := providers.NewMangoWCProvider("")
- if err := registry.Register(mangowcProvider); err != nil {
- log.Warnf("Failed to register MangoWC provider: %v", err)
- }
-
- configDir, _ := os.UserConfigDir()
-
- if configDir != "" {
- scrollProvider := providers.NewSwayProvider(filepath.Join(configDir, "scroll"))
- if err := registry.Register(scrollProvider); err != nil {
- log.Warnf("Failed to register Scroll provider: %v", err)
- }
- }
-
- miracleProvider := providers.NewMiracleProvider("")
- if err := registry.Register(miracleProvider); err != nil {
- log.Warnf("Failed to register Miracle WM provider: %v", err)
- }
-
- if configDir != "" {
- swayProvider := providers.NewSwayProvider(filepath.Join(configDir, "sway"))
- if err := registry.Register(swayProvider); err != nil {
- log.Warnf("Failed to register Sway provider: %v", err)
- }
- }
-
- niriProvider := providers.NewNiriProvider("")
- if err := registry.Register(niriProvider); err != nil {
- log.Warnf("Failed to register Niri provider: %v", err)
- }
-
- config := keybinds.DefaultDiscoveryConfig()
- if err := keybinds.AutoDiscoverProviders(registry, config); err != nil {
- log.Warnf("Failed to auto-discover providers: %v", err)
- }
-}
-
-func runKeybindsList(cmd *cobra.Command, _ []string) {
- providerList := keybinds.GetDefaultRegistry().List()
- asJSON, _ := cmd.Flags().GetBool("json")
-
- if asJSON {
- output, _ := json.Marshal(providerList)
- fmt.Fprintln(os.Stdout, string(output))
- return
- }
-
- if len(providerList) == 0 {
- fmt.Fprintln(os.Stdout, "No providers available")
- return
- }
-
- fmt.Fprintln(os.Stdout, "Available providers:")
- for _, name := range providerList {
- fmt.Fprintf(os.Stdout, " - %s\n", name)
- }
-}
-
-func makeProviderWithPath(name, path string) keybinds.Provider {
- switch name {
- case "hyprland":
- return providers.NewHyprlandProvider(path)
- case "mangowc":
- return providers.NewMangoWCProvider(path)
- case "sway":
- return providers.NewSwayProvider(path)
- case "scroll":
- return providers.NewSwayProvider(path)
- case "miracle":
- return providers.NewMiracleProvider(path)
- case "niri":
- return providers.NewNiriProvider(path)
- default:
- return nil
- }
-}
-
-func printCheatSheet(provider keybinds.Provider) {
- sheet, err := provider.GetCheatSheet()
- if err != nil {
- log.Fatalf("Error getting cheatsheet: %v", err)
- }
- output, err := json.MarshalIndent(sheet, "", " ")
- if err != nil {
- log.Fatalf("Error generating JSON: %v", err)
- }
- fmt.Fprintln(os.Stdout, string(output))
-}
-
-func runKeybindsShow(cmd *cobra.Command, args []string) {
- providerName := args[0]
- customPath, _ := cmd.Flags().GetString("path")
-
- if customPath != "" {
- provider := makeProviderWithPath(providerName, customPath)
- if provider == nil {
- log.Fatalf("Provider %s does not support custom path", providerName)
- }
- printCheatSheet(provider)
- return
- }
-
- provider, err := keybinds.GetDefaultRegistry().Get(providerName)
- if err != nil {
- log.Fatalf("Error: %v", err)
- }
- printCheatSheet(provider)
-}
-
-func getWritableProvider(name string) keybinds.WritableProvider {
- provider, err := keybinds.GetDefaultRegistry().Get(name)
- if err != nil {
- log.Fatalf("Error: %v", err)
- }
- writable, ok := provider.(keybinds.WritableProvider)
- if !ok {
- log.Fatalf("Provider %s does not support writing keybinds", name)
- }
- return writable
-}
-
-func runKeybindsSet(cmd *cobra.Command, args []string) {
- providerName, key, action := args[0], args[1], args[2]
- writable := getWritableProvider(providerName)
-
- if replaceKey, _ := cmd.Flags().GetString("replace-key"); replaceKey != "" && replaceKey != key {
- _ = writable.RemoveBind(replaceKey)
- }
-
- options := make(map[string]any)
- if v, _ := cmd.Flags().GetBool("allow-when-locked"); v {
- options["allow-when-locked"] = true
- }
- if v, _ := cmd.Flags().GetInt("cooldown-ms"); v > 0 {
- options["cooldown-ms"] = v
- }
- if v, _ := cmd.Flags().GetBool("no-repeat"); v {
- options["repeat"] = false
- }
- if v, _ := cmd.Flags().GetBool("no-inhibiting"); v {
- options["allow-inhibiting"] = false
- }
- if v, _ := cmd.Flags().GetString("flags"); v != "" {
- options["flags"] = v
- }
-
- desc, _ := cmd.Flags().GetString("desc")
- if err := writable.SetBind(key, action, desc, options); err != nil {
- log.Fatalf("Error setting keybind: %v", err)
- }
-
- output, _ := json.MarshalIndent(map[string]any{
- "success": true,
- "key": key,
- "action": action,
- "path": writable.GetOverridePath(),
- }, "", " ")
- fmt.Fprintln(os.Stdout, string(output))
-}
-
-func runKeybindsRemove(_ *cobra.Command, args []string) {
- providerName, key := args[0], args[1]
- writable := getWritableProvider(providerName)
-
- if err := writable.RemoveBind(key); err != nil {
- log.Fatalf("Error removing keybind: %v", err)
- }
-
- output, _ := json.MarshalIndent(map[string]any{
- "success": true,
- "key": key,
- "removed": true,
- }, "", " ")
- fmt.Fprintln(os.Stdout, string(output))
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_matugen.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_matugen.go
deleted file mode 100644
index 566f360..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_matugen.go
+++ /dev/null
@@ -1,202 +0,0 @@
-package main
-
-import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "os"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/matugen"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/spf13/cobra"
-)
-
-var matugenCmd = &cobra.Command{
- Use: "matugen",
- Short: "Generate Material Design themes",
- Long: "Generate Material Design themes using matugen with dank16 color integration",
-}
-
-var matugenGenerateCmd = &cobra.Command{
- Use: "generate",
- Short: "Generate theme synchronously",
- Run: runMatugenGenerate,
-}
-
-var matugenQueueCmd = &cobra.Command{
- Use: "queue",
- Short: "Queue theme generation (uses socket if available)",
- Run: runMatugenQueue,
-}
-
-var matugenCheckCmd = &cobra.Command{
- Use: "check",
- Short: "Check which template apps are detected",
- Run: runMatugenCheck,
-}
-
-func init() {
- matugenCmd.AddCommand(matugenGenerateCmd)
- matugenCmd.AddCommand(matugenQueueCmd)
- matugenCmd.AddCommand(matugenCheckCmd)
-
- for _, cmd := range []*cobra.Command{matugenGenerateCmd, matugenQueueCmd} {
- cmd.Flags().String("state-dir", "", "State directory for cache files")
- cmd.Flags().String("shell-dir", "", "DMS shell installation directory")
- cmd.Flags().String("config-dir", "", "User config directory")
- cmd.Flags().String("kind", "image", "Source type: image or hex")
- cmd.Flags().String("value", "", "Wallpaper path or hex color")
- cmd.Flags().String("mode", "dark", "Color mode: dark or light")
- cmd.Flags().String("icon-theme", "System Default", "Icon theme name")
- cmd.Flags().String("matugen-type", "scheme-tonal-spot", "Matugen scheme type")
- cmd.Flags().Bool("run-user-templates", true, "Run user matugen templates")
- cmd.Flags().String("stock-colors", "", "Stock theme colors JSON")
- cmd.Flags().Bool("sync-mode-with-portal", false, "Sync color scheme with GNOME portal")
- cmd.Flags().Bool("terminals-always-dark", false, "Force terminal themes to dark variant")
- cmd.Flags().String("skip-templates", "", "Comma-separated list of templates to skip")
- cmd.Flags().Float64("contrast", 0, "Contrast value from -1 to 1 (0 = standard)")
- }
-
- matugenQueueCmd.Flags().Bool("wait", true, "Wait for completion")
- matugenQueueCmd.Flags().Duration("timeout", 90*time.Second, "Timeout for waiting")
-}
-
-func buildMatugenOptions(cmd *cobra.Command) matugen.Options {
- stateDir, _ := cmd.Flags().GetString("state-dir")
- shellDir, _ := cmd.Flags().GetString("shell-dir")
- configDir, _ := cmd.Flags().GetString("config-dir")
- kind, _ := cmd.Flags().GetString("kind")
- value, _ := cmd.Flags().GetString("value")
- mode, _ := cmd.Flags().GetString("mode")
- iconTheme, _ := cmd.Flags().GetString("icon-theme")
- matugenType, _ := cmd.Flags().GetString("matugen-type")
- runUserTemplates, _ := cmd.Flags().GetBool("run-user-templates")
- stockColors, _ := cmd.Flags().GetString("stock-colors")
- syncModeWithPortal, _ := cmd.Flags().GetBool("sync-mode-with-portal")
- terminalsAlwaysDark, _ := cmd.Flags().GetBool("terminals-always-dark")
- skipTemplates, _ := cmd.Flags().GetString("skip-templates")
- contrast, _ := cmd.Flags().GetFloat64("contrast")
-
- return matugen.Options{
- StateDir: stateDir,
- ShellDir: shellDir,
- ConfigDir: configDir,
- Kind: kind,
- Value: value,
- Mode: matugen.ColorMode(mode),
- IconTheme: iconTheme,
- MatugenType: matugenType,
- Contrast: contrast,
- RunUserTemplates: runUserTemplates,
- StockColors: stockColors,
- SyncModeWithPortal: syncModeWithPortal,
- TerminalsAlwaysDark: terminalsAlwaysDark,
- SkipTemplates: skipTemplates,
- }
-}
-
-func runMatugenGenerate(cmd *cobra.Command, args []string) {
- opts := buildMatugenOptions(cmd)
- err := matugen.Run(opts)
- switch {
- case errors.Is(err, matugen.ErrNoChanges):
- os.Exit(2)
- case err != nil:
- log.Fatalf("Theme generation failed: %v", err)
- }
-}
-
-func runMatugenQueue(cmd *cobra.Command, args []string) {
- opts := buildMatugenOptions(cmd)
- wait, _ := cmd.Flags().GetBool("wait")
- timeout, _ := cmd.Flags().GetDuration("timeout")
-
- request := models.Request{
- ID: 1,
- Method: "matugen.queue",
- Params: map[string]any{
- "stateDir": opts.StateDir,
- "shellDir": opts.ShellDir,
- "configDir": opts.ConfigDir,
- "kind": opts.Kind,
- "value": opts.Value,
- "mode": opts.Mode,
- "iconTheme": opts.IconTheme,
- "matugenType": opts.MatugenType,
- "runUserTemplates": opts.RunUserTemplates,
- "stockColors": opts.StockColors,
- "syncModeWithPortal": opts.SyncModeWithPortal,
- "terminalsAlwaysDark": opts.TerminalsAlwaysDark,
- "skipTemplates": opts.SkipTemplates,
- "contrast": opts.Contrast,
- "wait": wait,
- },
- }
-
- if !wait {
- if err := sendServerRequestFireAndForget(request); err != nil {
- log.Info("Server unavailable, running synchronously")
- err := matugen.Run(opts)
- switch {
- case errors.Is(err, matugen.ErrNoChanges):
- os.Exit(2)
- case err != nil:
- log.Fatalf("Theme generation failed: %v", err)
- }
- return
- }
- fmt.Println("Theme generation queued")
- return
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
- defer cancel()
-
- resultCh := make(chan error, 1)
- go func() {
- resp, ok := tryServerRequest(request)
- if !ok {
- log.Info("Server unavailable, running synchronously")
- err := matugen.Run(opts)
- switch {
- case errors.Is(err, matugen.ErrNoChanges):
- resultCh <- matugen.ErrNoChanges
- case err != nil:
- resultCh <- err
- default:
- resultCh <- nil
- }
- return
- }
- if resp.Error != "" {
- resultCh <- fmt.Errorf("server error: %s", resp.Error)
- return
- }
- resultCh <- nil
- }()
-
- select {
- case err := <-resultCh:
- switch {
- case errors.Is(err, matugen.ErrNoChanges):
- os.Exit(2)
- case err != nil:
- log.Fatalf("Theme generation failed: %v", err)
- }
- fmt.Println("Theme generation completed")
- case <-ctx.Done():
- log.Fatalf("Timeout waiting for theme generation")
- }
-}
-
-func runMatugenCheck(cmd *cobra.Command, args []string) {
- checks := matugen.CheckTemplates(nil)
- data, err := json.Marshal(checks)
- if err != nil {
- log.Fatalf("Failed to marshal check results: %v", err)
- }
- fmt.Println(string(data))
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_notify.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_notify.go
deleted file mode 100644
index e04b60f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_notify.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/notify"
- "github.com/spf13/cobra"
-)
-
-var (
- notifyAppName string
- notifyIcon string
- notifyFile string
- notifyTimeout int
-)
-
-var notifyCmd = &cobra.Command{
- Use: "notify <summary> [body]",
- Short: "Send a desktop notification",
- Long: `Send a desktop notification with optional actions.
-
-If --file is provided, the notification will have "Open" and "Open Folder" actions.
-
-Examples:
- dms notify "Hello" "World"
- dms notify "File received" "photo.jpg" --file ~/Downloads/photo.jpg --icon smartphone
- dms notify "Download complete" --file ~/Downloads/file.zip --app "My App"`,
- Args: cobra.MinimumNArgs(1),
- Run: runNotify,
-}
-
-var genericNotifyActionCmd = &cobra.Command{
- Use: "notify-action-generic",
- Hidden: true,
- Run: func(cmd *cobra.Command, args []string) {
- notify.RunActionListener(args)
- },
-}
-
-func init() {
- notifyCmd.Flags().StringVar(&notifyAppName, "app", "DMS", "Application name")
- notifyCmd.Flags().StringVar(&notifyIcon, "icon", "", "Icon name or path")
- notifyCmd.Flags().StringVar(&notifyFile, "file", "", "File path (enables Open/Open Folder actions)")
- notifyCmd.Flags().IntVar(&notifyTimeout, "timeout", 5000, "Timeout in milliseconds")
-}
-
-func runNotify(cmd *cobra.Command, args []string) {
- summary := args[0]
- body := ""
- if len(args) > 1 {
- body = args[1]
- }
-
- n := notify.Notification{
- AppName: notifyAppName,
- Icon: notifyIcon,
- Summary: summary,
- Body: body,
- FilePath: notifyFile,
- Timeout: int32(notifyTimeout),
- }
-
- if err := notify.Send(n); err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- os.Exit(1)
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_open.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_open.go
deleted file mode 100644
index 8cc16cf..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_open.go
+++ /dev/null
@@ -1,205 +0,0 @@
-package main
-
-import (
- "fmt"
- "mime"
- "net/url"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
- "github.com/spf13/cobra"
-)
-
-var (
- openMimeType string
- openCategories []string
- openRequestType string
-)
-
-var openCmd = &cobra.Command{
- Use: "open [target]",
- Short: "Open a file, URL, or resource with an application picker",
- Long: `Open a target (URL, file, or other resource) using the DMS application picker.
-By default, this opens URLs with the browser picker. You can customize the behavior
-with flags to handle different MIME types or application categories.
-
-Examples:
- dms open https://example.com # Open URL with browser picker
- dms open file.pdf --mime application/pdf # Open PDF with compatible apps
- dms open document.odt --category Office # Open with office applications
- dms open --mime image/png image.png # Open image with image viewers`,
- Args: cobra.ExactArgs(1),
- Run: func(cmd *cobra.Command, args []string) {
- runOpen(args[0])
- },
-}
-
-func init() {
- rootCmd.AddCommand(openCmd)
- openCmd.Flags().StringVar(&openMimeType, "mime", "", "MIME type for filtering applications")
- openCmd.Flags().StringSliceVar(&openCategories, "category", []string{}, "Application categories to filter (e.g., WebBrowser, Office, Graphics)")
- openCmd.Flags().StringVar(&openRequestType, "type", "url", "Request type (url, file, or custom)")
- _ = openCmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- return []string{"url", "file", "custom"}, cobra.ShellCompDirectiveNoFileComp
- })
-}
-
-// mimeTypeToCategories maps MIME types to desktop file categories
-func mimeTypeToCategories(mimeType string) []string {
- // Split MIME type to get the main type
- parts := strings.Split(mimeType, "/")
- if len(parts) < 1 {
- return nil
- }
-
- mainType := parts[0]
-
- switch mainType {
- case "image":
- return []string{"Graphics", "Viewer"}
- case "video":
- return []string{"Video", "AudioVideo"}
- case "audio":
- return []string{"Audio", "AudioVideo"}
- case "text":
- if strings.Contains(mimeType, "html") {
- return []string{"WebBrowser"}
- }
- return []string{"TextEditor", "Office"}
- case "application":
- if strings.Contains(mimeType, "pdf") {
- return []string{"Office", "Viewer"}
- }
- if strings.Contains(mimeType, "document") || strings.Contains(mimeType, "spreadsheet") ||
- strings.Contains(mimeType, "presentation") || strings.Contains(mimeType, "msword") ||
- strings.Contains(mimeType, "ms-excel") || strings.Contains(mimeType, "ms-powerpoint") ||
- strings.Contains(mimeType, "opendocument") {
- return []string{"Office"}
- }
- if strings.Contains(mimeType, "zip") || strings.Contains(mimeType, "tar") ||
- strings.Contains(mimeType, "gzip") || strings.Contains(mimeType, "compress") {
- return []string{"Archiving", "Utility"}
- }
- return []string{"Office", "Viewer"}
- }
-
- return nil
-}
-
-func runOpen(target string) {
- // Parse file:// URIs to extract the actual file path
- actualTarget := target
- detectedMimeType := openMimeType
- detectedCategories := openCategories
- detectedRequestType := openRequestType
-
- log.Infof("Processing target: %s", target)
-
- if parsedURL, err := url.Parse(target); err == nil && parsedURL.Scheme == "file" {
- // Extract file path from file:// URI and convert to absolute path
- actualTarget = parsedURL.Path
- if absPath, err := filepath.Abs(actualTarget); err == nil {
- actualTarget = absPath
- }
-
- if detectedRequestType == "url" || detectedRequestType == "" {
- detectedRequestType = "file"
- }
-
- log.Infof("Detected file:// URI, extracted absolute path: %s", actualTarget)
-
- // Auto-detect MIME type if not provided
- if detectedMimeType == "" {
- ext := filepath.Ext(actualTarget)
- if ext != "" {
- detectedMimeType = mime.TypeByExtension(ext)
- log.Infof("Detected MIME type from extension %s: %s", ext, detectedMimeType)
- }
- }
-
- // Auto-detect categories based on MIME type if not provided
- if len(detectedCategories) == 0 && detectedMimeType != "" {
- detectedCategories = mimeTypeToCategories(detectedMimeType)
- log.Infof("Detected categories from MIME type: %v", detectedCategories)
- }
- } else if strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") {
- // Handle HTTP(S) URLs
- if detectedRequestType == "" {
- detectedRequestType = "url"
- }
- log.Infof("Detected HTTP(S) URL")
- } else if strings.HasPrefix(target, "dms://") {
- // Handle DMS internal URLs (theme/plugin install, etc.)
- if detectedRequestType == "" {
- detectedRequestType = "url"
- }
- log.Infof("Detected DMS internal URL")
- } else if _, err := os.Stat(target); err == nil {
- // Handle local file paths directly (not file:// URIs)
- // Convert to absolute path
- if absPath, err := filepath.Abs(target); err == nil {
- actualTarget = absPath
- }
-
- if detectedRequestType == "url" || detectedRequestType == "" {
- detectedRequestType = "file"
- }
-
- log.Infof("Detected local file path, converted to absolute: %s", actualTarget)
-
- // Auto-detect MIME type if not provided
- if detectedMimeType == "" {
- ext := filepath.Ext(actualTarget)
- if ext != "" {
- detectedMimeType = mime.TypeByExtension(ext)
- log.Infof("Detected MIME type from extension %s: %s", ext, detectedMimeType)
- }
- }
-
- // Auto-detect categories based on MIME type if not provided
- if len(detectedCategories) == 0 && detectedMimeType != "" {
- detectedCategories = mimeTypeToCategories(detectedMimeType)
- log.Infof("Detected categories from MIME type: %v", detectedCategories)
- }
- }
-
- params := map[string]any{
- "target": actualTarget,
- }
-
- if detectedMimeType != "" {
- params["mimeType"] = detectedMimeType
- }
-
- if len(detectedCategories) > 0 {
- params["categories"] = detectedCategories
- }
-
- if detectedRequestType != "" {
- params["requestType"] = detectedRequestType
- }
-
- method := "apppicker.open"
- if detectedMimeType == "" && len(detectedCategories) == 0 && (strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") || strings.HasPrefix(target, "dms://")) {
- method = "browser.open"
- params["url"] = target
- }
-
- req := models.Request{
- ID: 1,
- Method: method,
- Params: params,
- }
-
- log.Infof("Sending request - Method: %s, Params: %+v", method, params)
-
- if err := sendServerRequestFireAndForget(req); err != nil {
- fmt.Println("DMS is not running. Please start DMS first.")
- os.Exit(1)
- }
-
- log.Infof("Request sent successfully")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_randr.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_randr.go
deleted file mode 100644
index fa0c990..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_randr.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "fmt"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/spf13/cobra"
-)
-
-var randrCmd = &cobra.Command{
- Use: "randr",
- Short: "Query output display information",
- Long: "Query Wayland compositor for output names, scales, resolutions and refresh rates via zwlr-output-management",
- Run: runRandr,
-}
-
-func init() {
- randrCmd.Flags().Bool("json", false, "Output in JSON format")
-}
-
-type randrJSON struct {
- Outputs []randrOutput `json:"outputs"`
-}
-
-func runRandr(cmd *cobra.Command, args []string) {
- outputs, err := queryRandr()
- if err != nil {
- log.Fatalf("%v", err)
- }
-
- jsonFlag, _ := cmd.Flags().GetBool("json")
-
- if jsonFlag {
- data, err := json.Marshal(randrJSON{Outputs: outputs})
- if err != nil {
- log.Fatalf("failed to marshal JSON: %v", err)
- }
- fmt.Println(string(data))
- return
- }
-
- for i, out := range outputs {
- if i > 0 {
- fmt.Println()
- }
- status := "enabled"
- if !out.Enabled {
- status = "disabled"
- }
- fmt.Printf("%s (%s)\n", out.Name, status)
- fmt.Printf(" Scale: %.4g\n", out.Scale)
- fmt.Printf(" Resolution: %dx%d\n", out.Width, out.Height)
- if out.Refresh > 0 {
- fmt.Printf(" Refresh: %.2f Hz\n", float64(out.Refresh)/1000.0)
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_root.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_root.go
deleted file mode 100644
index b263ae6..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_root.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/config"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/spf13/cobra"
-)
-
-var customConfigPath string
-var configPath string
-
-var rootCmd = &cobra.Command{
- Use: "dms",
- Short: "dms CLI",
- Long: "dms is the DankMaterialShell management CLI and backend server.",
-}
-
-func init() {
- rootCmd.PersistentFlags().StringVarP(&customConfigPath, "config", "c", "", "Specify a custom path to the DMS config directory")
-}
-
-func findConfig(cmd *cobra.Command, args []string) error {
- if customConfigPath != "" {
- log.Debug("Custom config path provided via -c flag: %s", customConfigPath)
- shellPath := filepath.Join(customConfigPath, "shell.qml")
-
- info, statErr := os.Stat(shellPath)
-
- if statErr == nil && !info.IsDir() {
- configPath = customConfigPath
- log.Debug("Using config from: %s", configPath)
- return nil
- }
-
- if statErr != nil {
- return fmt.Errorf("custom config path error: %w", statErr)
- }
-
- return fmt.Errorf("path is a directory, not a file: %s", shellPath)
- }
-
- configStateFile := filepath.Join(getRuntimeDir(), "danklinux.path")
- if data, readErr := os.ReadFile(configStateFile); readErr == nil {
- if len(getAllDMSPIDs()) == 0 {
- os.Remove(configStateFile)
- } else {
- statePath := strings.TrimSpace(string(data))
- shellPath := filepath.Join(statePath, "shell.qml")
-
- if info, statErr := os.Stat(shellPath); statErr == nil && !info.IsDir() {
- log.Debug("Using config from active session state file: %s", statePath)
- configPath = statePath
- log.Debug("Using config from: %s", configPath)
- return nil
- }
- os.Remove(configStateFile)
- }
- }
-
- log.Debug("No custom path or active session, searching default XDG locations...")
- var err error
- configPath, err = config.LocateDMSConfig()
- if err != nil {
- return err
- }
-
- log.Debug("Using config from: %s", configPath)
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_screenshot.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_screenshot.go
deleted file mode 100644
index 2875019..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_screenshot.go
+++ /dev/null
@@ -1,422 +0,0 @@
-package main
-
-import (
- "bytes"
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/screenshot"
- "github.com/spf13/cobra"
-)
-
-var (
- ssOutputName string
- ssCursor string
- ssFormat string
- ssQuality int
- ssOutputDir string
- ssFilename string
- ssNoClipboard bool
- ssNoFile bool
- ssNoNotify bool
- ssNoConfirm bool
- ssReset bool
- ssStdout bool
-)
-
-var screenshotCmd = &cobra.Command{
- Use: "screenshot",
- Short: "Capture screenshots",
- Long: `Capture screenshots from Wayland displays.
-
-Modes:
- region - Select a region interactively (default)
- full - Capture the focused output
- all - Capture all outputs combined
- output - Capture a specific output by name
- window - Capture the focused window (Hyprland/DWL)
- last - Capture the last selected region
-
-Output format (--format):
- png - PNG format (default)
- jpg/jpeg - JPEG format
- ppm - PPM format
-
-Examples:
- dms screenshot # Region select, save file + clipboard
- dms screenshot full # Full screen of focused output
- dms screenshot all # All screens combined
- dms screenshot output -o DP-1 # Specific output
- dms screenshot window # Focused window (Hyprland)
- dms screenshot last # Last region (pre-selected)
- dms screenshot --reset # Reset last region pre-selection
- dms screenshot --no-clipboard # Save file only
- dms screenshot --no-file # Clipboard only
- dms screenshot --no-confirm # Region capture on mouse release
- dms screenshot --cursor=on # Include cursor
- dms screenshot -f jpg -q 85 # JPEG with quality 85`,
-}
-
-var ssRegionCmd = &cobra.Command{
- Use: "region",
- Short: "Select a region interactively",
- Run: runScreenshotRegion,
-}
-
-var ssFullCmd = &cobra.Command{
- Use: "full",
- Short: "Capture the focused output",
- Run: runScreenshotFull,
-}
-
-var ssAllCmd = &cobra.Command{
- Use: "all",
- Short: "Capture all outputs combined",
- Run: runScreenshotAll,
-}
-
-var ssOutputCmd = &cobra.Command{
- Use: "output",
- Short: "Capture a specific output",
- Run: runScreenshotOutput,
-}
-
-var ssLastCmd = &cobra.Command{
- Use: "last",
- Short: "Capture the last selected region",
- Long: `Capture the previously selected region without interactive selection.
-If no previous region exists, falls back to interactive selection.`,
- Run: runScreenshotLast,
-}
-
-var ssWindowCmd = &cobra.Command{
- Use: "window",
- Short: "Capture the focused window",
- Long: `Capture the currently focused window. Supported on Hyprland and DWL.`,
- Run: runScreenshotWindow,
-}
-
-var ssListCmd = &cobra.Command{
- Use: "list",
- Short: "List available outputs",
- Run: runScreenshotList,
-}
-
-var notifyActionCmd = &cobra.Command{
- Use: "notify-action",
- Hidden: true,
- Run: func(cmd *cobra.Command, args []string) {
- screenshot.RunNotifyActionListener(args)
- },
-}
-
-func init() {
- screenshotCmd.PersistentFlags().StringVarP(&ssOutputName, "output", "o", "", "Output name for 'output' mode")
- screenshotCmd.PersistentFlags().StringVar(&ssCursor, "cursor", "off", "Include cursor in screenshot (on/off)")
- screenshotCmd.PersistentFlags().StringVarP(&ssFormat, "format", "f", "png", "Output format (png, jpg, ppm)")
- screenshotCmd.PersistentFlags().IntVarP(&ssQuality, "quality", "q", 90, "JPEG quality (1-100)")
- screenshotCmd.PersistentFlags().StringVarP(&ssOutputDir, "dir", "d", "", "Output directory")
- screenshotCmd.PersistentFlags().StringVar(&ssFilename, "filename", "", "Output filename (auto-generated if empty)")
- screenshotCmd.PersistentFlags().BoolVar(&ssNoClipboard, "no-clipboard", false, "Don't copy to clipboard")
- screenshotCmd.PersistentFlags().BoolVar(&ssNoFile, "no-file", false, "Don't save to file")
- screenshotCmd.PersistentFlags().BoolVar(&ssNoNotify, "no-notify", false, "Don't show notification")
- screenshotCmd.PersistentFlags().BoolVar(&ssNoConfirm, "no-confirm", false, "Region mode: capture on mouse release without Enter/Space confirmation")
- screenshotCmd.PersistentFlags().BoolVar(&ssReset, "reset", false, "Reset saved last-region preselection before capturing")
- screenshotCmd.PersistentFlags().BoolVar(&ssStdout, "stdout", false, "Output image to stdout (for piping to swappy, etc.)")
-
- screenshotCmd.AddCommand(ssRegionCmd)
- screenshotCmd.AddCommand(ssFullCmd)
- screenshotCmd.AddCommand(ssAllCmd)
- screenshotCmd.AddCommand(ssOutputCmd)
- screenshotCmd.AddCommand(ssLastCmd)
- screenshotCmd.AddCommand(ssWindowCmd)
- screenshotCmd.AddCommand(ssListCmd)
-
- screenshotCmd.Run = runScreenshotRegion
-}
-
-func getScreenshotConfig(mode screenshot.Mode) screenshot.Config {
- config := screenshot.DefaultConfig()
- config.Mode = mode
- config.OutputName = ssOutputName
- if strings.EqualFold(ssCursor, "on") {
- config.Cursor = screenshot.CursorOn
- }
- config.Clipboard = !ssNoClipboard
- config.SaveFile = !ssNoFile
- config.Notify = !ssNoNotify
- config.NoConfirm = ssNoConfirm
- config.Reset = ssReset
- config.Stdout = ssStdout
-
- if ssOutputDir != "" {
- config.OutputDir = ssOutputDir
- }
- if ssFilename != "" {
- config.Filename = ssFilename
- }
-
- switch strings.ToLower(ssFormat) {
- case "jpg", "jpeg":
- config.Format = screenshot.FormatJPEG
- case "ppm":
- config.Format = screenshot.FormatPPM
- default:
- config.Format = screenshot.FormatPNG
- }
-
- if ssQuality < 1 {
- ssQuality = 1
- }
- if ssQuality > 100 {
- ssQuality = 100
- }
- config.Quality = ssQuality
-
- return config
-}
-
-func runScreenshot(config screenshot.Config) {
- sc := screenshot.New(config)
- result, err := sc.Run()
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- os.Exit(1)
- }
-
- if result == nil {
- os.Exit(0)
- }
-
- defer result.Buffer.Close()
-
- if result.YInverted {
- result.Buffer.FlipVertical()
- }
-
- if config.Stdout {
- if err := writeImageToStdout(result.Buffer, config.Format, config.Quality, result.Format); err != nil {
- fmt.Fprintf(os.Stderr, "Error writing to stdout: %v\n", err)
- os.Exit(1)
- }
- return
- }
-
- var filePath string
-
- if config.SaveFile {
- outputDir := config.OutputDir
- if outputDir == "" {
- outputDir = screenshot.GetOutputDir()
- }
-
- filename := config.Filename
- if filename == "" {
- filename = screenshot.GenerateFilename(config.Format)
- }
-
- filePath = filepath.Join(outputDir, filename)
- if err := screenshot.WriteToFileWithFormat(result.Buffer, filePath, config.Format, config.Quality, result.Format); err != nil {
- fmt.Fprintf(os.Stderr, "Error writing file: %v\n", err)
- os.Exit(1)
- }
- fmt.Println(filePath)
- }
-
- if config.Clipboard {
- if err := copyImageToClipboard(result.Buffer, config.Format, config.Quality, result.Format); err != nil {
- fmt.Fprintf(os.Stderr, "Error copying to clipboard: %v\n", err)
- os.Exit(1)
- }
- if !config.SaveFile {
- fmt.Println("Copied to clipboard")
- }
- }
-
- if config.Notify {
- thumbData, thumbW, thumbH := bufferToRGBThumbnail(result.Buffer, 256, result.Format)
- screenshot.SendNotification(screenshot.NotifyResult{
- FilePath: filePath,
- Clipboard: config.Clipboard,
- ImageData: thumbData,
- Width: thumbW,
- Height: thumbH,
- })
- }
-}
-
-func copyImageToClipboard(buf *screenshot.ShmBuffer, format screenshot.Format, quality int, pixelFormat uint32) error {
- var mimeType string
- var data bytes.Buffer
-
- img := screenshot.BufferToImageWithFormat(buf, pixelFormat)
-
- switch format {
- case screenshot.FormatJPEG:
- mimeType = "image/jpeg"
- if err := screenshot.EncodeJPEG(&data, img, quality); err != nil {
- return err
- }
- default:
- mimeType = "image/png"
- if err := screenshot.EncodePNG(&data, img); err != nil {
- return err
- }
- }
-
- return clipboard.Copy(data.Bytes(), mimeType)
-}
-
-func writeImageToStdout(buf *screenshot.ShmBuffer, format screenshot.Format, quality int, pixelFormat uint32) error {
- img := screenshot.BufferToImageWithFormat(buf, pixelFormat)
-
- switch format {
- case screenshot.FormatJPEG:
- return screenshot.EncodeJPEG(os.Stdout, img, quality)
- default:
- return screenshot.EncodePNG(os.Stdout, img)
- }
-}
-
-func bufferToRGBThumbnail(buf *screenshot.ShmBuffer, maxSize int, pixelFormat uint32) ([]byte, int, int) {
- srcW, srcH := buf.Width, buf.Height
- scale := 1.0
- if srcW > maxSize || srcH > maxSize {
- if srcW > srcH {
- scale = float64(maxSize) / float64(srcW)
- } else {
- scale = float64(maxSize) / float64(srcH)
- }
- }
-
- dstW := int(float64(srcW) * scale)
- dstH := int(float64(srcH) * scale)
- if dstW < 1 {
- dstW = 1
- }
- if dstH < 1 {
- dstH = 1
- }
-
- data := buf.Data()
- rgb := make([]byte, dstW*dstH*3)
-
- var swapRB bool
- switch pixelFormat {
- case uint32(screenshot.FormatABGR8888), uint32(screenshot.FormatXBGR8888):
- swapRB = false
- default:
- swapRB = true
- }
-
- for y := 0; y < dstH; y++ {
- srcY := int(float64(y) / scale)
- if srcY >= srcH {
- srcY = srcH - 1
- }
- for x := 0; x < dstW; x++ {
- srcX := int(float64(x) / scale)
- if srcX >= srcW {
- srcX = srcW - 1
- }
- si := srcY*buf.Stride + srcX*4
- di := (y*dstW + x) * 3
- if si+3 >= len(data) {
- continue
- }
- if swapRB {
- rgb[di+0] = data[si+2]
- rgb[di+1] = data[si+1]
- rgb[di+2] = data[si+0]
- } else {
- rgb[di+0] = data[si+0]
- rgb[di+1] = data[si+1]
- rgb[di+2] = data[si+2]
- }
- }
- }
- return rgb, dstW, dstH
-}
-
-func runScreenshotRegion(cmd *cobra.Command, args []string) {
- config := getScreenshotConfig(screenshot.ModeRegion)
- runScreenshot(config)
-}
-
-func runScreenshotFull(cmd *cobra.Command, args []string) {
- config := getScreenshotConfig(screenshot.ModeFullScreen)
- runScreenshot(config)
-}
-
-func runScreenshotAll(cmd *cobra.Command, args []string) {
- config := getScreenshotConfig(screenshot.ModeAllScreens)
- runScreenshot(config)
-}
-
-func runScreenshotOutput(cmd *cobra.Command, args []string) {
- if ssOutputName == "" && len(args) > 0 {
- ssOutputName = args[0]
- }
- if ssOutputName == "" {
- fmt.Fprintln(os.Stderr, "Error: output name required (use -o or provide as argument)")
- os.Exit(1)
- }
- config := getScreenshotConfig(screenshot.ModeOutput)
- runScreenshot(config)
-}
-
-func runScreenshotLast(cmd *cobra.Command, args []string) {
- config := getScreenshotConfig(screenshot.ModeLastRegion)
- runScreenshot(config)
-}
-
-func runScreenshotWindow(cmd *cobra.Command, args []string) {
- config := getScreenshotConfig(screenshot.ModeWindow)
- runScreenshot(config)
-}
-
-func runScreenshotList(cmd *cobra.Command, args []string) {
- outputs, err := screenshot.ListOutputs()
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- os.Exit(1)
- }
-
- for _, o := range outputs {
- scaleStr := fmt.Sprintf("%.2f", o.FractionalScale)
- if o.FractionalScale == float64(int(o.FractionalScale)) {
- scaleStr = fmt.Sprintf("%d", int(o.FractionalScale))
- }
-
- transformStr := transformName(o.Transform)
-
- fmt.Printf("%s: %dx%d+%d+%d scale=%s transform=%s\n",
- o.Name, o.Width, o.Height, o.X, o.Y, scaleStr, transformStr)
- }
-}
-
-func transformName(t int32) string {
- switch t {
- case 0:
- return "normal"
- case 1:
- return "90"
- case 2:
- return "180"
- case 3:
- return "270"
- case 4:
- return "flipped"
- case 5:
- return "flipped-90"
- case 6:
- return "flipped-180"
- case 7:
- return "flipped-270"
- default:
- return fmt.Sprintf("%d", t)
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_setup.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_setup.go
deleted file mode 100644
index 0f4a58d..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_setup.go
+++ /dev/null
@@ -1,436 +0,0 @@
-package main
-
-import (
- "context"
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "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"
-)
-
-var setupCmd = &cobra.Command{
- Use: "setup",
- Short: "Deploy DMS configurations",
- Long: "Deploy compositor and terminal configurations with interactive prompts",
- PersistentPreRunE: requireMutableSystemCommand,
- Run: func(cmd *cobra.Command, args []string) {
- if err := runSetup(); err != nil {
- log.Fatalf("Error during setup: %v", err)
- }
- },
-}
-
-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 ===")
-
- wm, wmSelected := promptCompositor()
- terminal, terminalSelected := promptTerminal()
- useSystemd := promptSystemd()
-
- if !wmSelected && !terminalSelected {
- fmt.Println("No configurations selected. Exiting.")
- return nil
- }
-
- if wmSelected || terminalSelected {
- willBackup := checkExistingConfigs(wm, wmSelected, terminal, terminalSelected)
- if willBackup {
- fmt.Println("\n⚠ Existing configurations will be backed up with timestamps.")
- }
-
- fmt.Print("\nProceed with deployment? (y/N): ")
- var response string
- fmt.Scanln(&response)
- response = strings.ToLower(strings.TrimSpace(response))
-
- if response != "y" && response != "yes" {
- fmt.Println("Setup cancelled.")
- return nil
- }
- }
-
- fmt.Println("\nDeploying configurations...")
- logChan := make(chan string, 100)
- deployer := config.NewConfigDeployer(logChan)
-
- go func() {
- for msg := range logChan {
- fmt.Println(" " + msg)
- }
- }()
-
- ctx := context.Background()
- var results []config.DeploymentResult
- var err error
-
- if wmSelected && terminalSelected {
- results, err = deployer.DeployConfigurationsWithSystemd(ctx, wm, terminal, useSystemd)
- } else if wmSelected {
- results, err = deployer.DeployConfigurationsWithSystemd(ctx, wm, deps.TerminalGhostty, useSystemd)
- if len(results) > 1 {
- results = results[:1]
- }
- } else if terminalSelected {
- results, err = deployer.DeployConfigurationsWithSystemd(ctx, deps.WindowManagerNiri, terminal, useSystemd)
- if len(results) > 0 && results[0].ConfigType == "Niri" {
- results = results[1:]
- }
- }
-
- close(logChan)
-
- if err != nil {
- return fmt.Errorf("deployment failed: %w", err)
- }
-
- fmt.Println("\n=== Deployment Complete ===")
- for _, result := range results {
- if result.Deployed {
- fmt.Printf("✓ %s: %s\n", result.ConfigType, result.Path)
- if result.BackupPath != "" {
- fmt.Printf(" Backup: %s\n", result.BackupPath)
- }
- }
- }
-
- return nil
-}
-
-func promptCompositor() (deps.WindowManager, bool) {
- fmt.Println("Select compositor:")
- fmt.Println("1) Niri")
- fmt.Println("2) Hyprland")
- fmt.Println("3) None")
-
- var response string
- fmt.Print("\nChoice (1-3): ")
- fmt.Scanln(&response)
- response = strings.TrimSpace(response)
-
- switch response {
- case "1":
- return deps.WindowManagerNiri, true
- case "2":
- return deps.WindowManagerHyprland, true
- default:
- return deps.WindowManagerNiri, false
- }
-}
-
-func promptTerminal() (deps.Terminal, bool) {
- fmt.Println("\nSelect terminal:")
- fmt.Println("1) Ghostty")
- fmt.Println("2) Kitty")
- fmt.Println("3) Alacritty")
- fmt.Println("4) None")
-
- var response string
- fmt.Print("\nChoice (1-4): ")
- fmt.Scanln(&response)
- response = strings.TrimSpace(response)
-
- switch response {
- case "1":
- return deps.TerminalGhostty, true
- case "2":
- return deps.TerminalKitty, true
- case "3":
- return deps.TerminalAlacritty, true
- default:
- return deps.TerminalGhostty, false
- }
-}
-
-func promptSystemd() bool {
- fmt.Println("\nUse systemd for session management?")
- fmt.Println("1) Yes (recommended for most distros)")
- fmt.Println("2) No (standalone, no systemd integration)")
-
- var response string
- fmt.Print("\nChoice (1-2): ")
- fmt.Scanln(&response)
- response = strings.TrimSpace(response)
-
- return response != "2"
-}
-
-func checkExistingConfigs(wm deps.WindowManager, wmSelected bool, terminal deps.Terminal, terminalSelected bool) bool {
- homeDir := os.Getenv("HOME")
- willBackup := false
-
- if wmSelected {
- var configPath string
- switch wm {
- case deps.WindowManagerNiri:
- configPath = filepath.Join(homeDir, ".config", "niri", "config.kdl")
- case deps.WindowManagerHyprland:
- configPath = filepath.Join(homeDir, ".config", "hypr", "hyprland.conf")
- }
-
- if _, err := os.Stat(configPath); err == nil {
- willBackup = true
- }
- }
-
- if terminalSelected {
- var configPath string
- switch terminal {
- case deps.TerminalGhostty:
- configPath = filepath.Join(homeDir, ".config", "ghostty", "config")
- case deps.TerminalKitty:
- configPath = filepath.Join(homeDir, ".config", "kitty", "kitty.conf")
- case deps.TerminalAlacritty:
- configPath = filepath.Join(homeDir, ".config", "alacritty", "alacritty.toml")
- }
-
- if _, err := os.Stat(configPath); err == nil {
- willBackup = true
- }
- }
-
- return willBackup
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_windowrules.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_windowrules.go
deleted file mode 100644
index 0883811..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/commands_windowrules.go
+++ /dev/null
@@ -1,338 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "fmt"
- "os"
- "strings"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/windowrules"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/windowrules/providers"
- "github.com/spf13/cobra"
-)
-
-var windowrulesCmd = &cobra.Command{
- Use: "windowrules",
- Short: "Manage window rules",
-}
-
-var windowrulesListCmd = &cobra.Command{
- Use: "list [compositor]",
- Short: "List all window rules",
- Long: "List all window rules from compositor config file. Returns JSON with rules and DMS status.",
- Args: cobra.MaximumNArgs(1),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) == 0 {
- return []string{"niri"}, cobra.ShellCompDirectiveNoFileComp
- }
- return nil, cobra.ShellCompDirectiveNoFileComp
- },
- Run: runWindowrulesList,
-}
-
-var windowrulesAddCmd = &cobra.Command{
- Use: "add <compositor> '<json>'",
- Short: "Add a window rule to DMS file",
- Long: "Add a new window rule to the DMS-managed rules file.",
- Args: cobra.ExactArgs(2),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) == 0 {
- // ! disabled hyprland return []string{"hyprland", "niri"}, cobra.ShellCompDirectiveNoFileComp
- return []string{"niri"}, cobra.ShellCompDirectiveNoFileComp
- }
- return nil, cobra.ShellCompDirectiveNoFileComp
- },
- Run: runWindowrulesAdd,
-}
-
-var windowrulesUpdateCmd = &cobra.Command{
- Use: "update <compositor> <id> '<json>'",
- Short: "Update a window rule in DMS file",
- Long: "Update an existing window rule in the DMS-managed rules file.",
- Args: cobra.ExactArgs(3),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) == 0 {
- return []string{"niri"}, cobra.ShellCompDirectiveNoFileComp
- }
- return nil, cobra.ShellCompDirectiveNoFileComp
- },
- Run: runWindowrulesUpdate,
-}
-
-var windowrulesRemoveCmd = &cobra.Command{
- Use: "remove <compositor> <id>",
- Short: "Remove a window rule from DMS file",
- Long: "Remove a window rule from the DMS-managed rules file.",
- Args: cobra.ExactArgs(2),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) == 0 {
- return []string{"niri"}, cobra.ShellCompDirectiveNoFileComp
- }
- return nil, cobra.ShellCompDirectiveNoFileComp
- },
- Run: runWindowrulesRemove,
-}
-
-var windowrulesReorderCmd = &cobra.Command{
- Use: "reorder <compositor> '<json-array-of-ids>'",
- Short: "Reorder window rules in DMS file",
- Long: "Reorder window rules by providing a JSON array of rule IDs in the desired order.",
- Args: cobra.ExactArgs(2),
- ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) == 0 {
- return []string{"niri"}, cobra.ShellCompDirectiveNoFileComp
- }
- return nil, cobra.ShellCompDirectiveNoFileComp
- },
- Run: runWindowrulesReorder,
-}
-
-func init() {
- configCmd.AddCommand(windowrulesCmd)
- windowrulesCmd.AddCommand(windowrulesListCmd)
- windowrulesCmd.AddCommand(windowrulesAddCmd)
- windowrulesCmd.AddCommand(windowrulesUpdateCmd)
- windowrulesCmd.AddCommand(windowrulesRemoveCmd)
- windowrulesCmd.AddCommand(windowrulesReorderCmd)
-}
-
-type WindowRulesListResult struct {
- Rules []windowrules.WindowRule `json:"rules"`
- DMSStatus *windowrules.DMSRulesStatus `json:"dmsStatus,omitempty"`
-}
-
-type WindowRuleWriteResult struct {
- Success bool `json:"success"`
- ID string `json:"id,omitempty"`
- Path string `json:"path,omitempty"`
- Error string `json:"error,omitempty"`
-}
-
-func getCompositor(args []string) string {
- if len(args) > 0 {
- return strings.ToLower(args[0])
- }
- if os.Getenv("NIRI_SOCKET") != "" {
- return "niri"
- }
- // if os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") != "" {
- // return "hyprland"
- // }
- return ""
-}
-
-func writeRuleError(errMsg string) {
- result := WindowRuleWriteResult{Success: false, Error: errMsg}
- output, _ := json.Marshal(result)
- fmt.Fprintln(os.Stdout, string(output))
- os.Exit(1)
-}
-
-func writeRuleSuccess(id, path string) {
- result := WindowRuleWriteResult{Success: true, ID: id, Path: path}
- output, _ := json.Marshal(result)
- fmt.Fprintln(os.Stdout, string(output))
-}
-
-func runWindowrulesList(cmd *cobra.Command, args []string) {
- compositor := getCompositor(args)
- if compositor == "" {
- log.Fatalf("Could not detect compositor. Please specify: hyprland or niri")
- }
-
- var result WindowRulesListResult
-
- switch compositor {
- case "niri":
- configDir, err := utils.ExpandPath("$HOME/.config/niri")
- if err != nil {
- log.Fatalf("Failed to expand niri config path: %v", err)
- }
-
- parseResult, err := providers.ParseNiriWindowRules(configDir)
- if err != nil {
- log.Fatalf("Failed to parse niri window rules: %v", err)
- }
-
- allRules := providers.ConvertNiriRulesToWindowRules(parseResult.Rules)
-
- provider := providers.NewNiriWritableProvider(configDir)
- dmsRulesPath := provider.GetOverridePath()
- dmsRules, _ := provider.LoadDMSRules()
-
- dmsRuleMap := make(map[int]windowrules.WindowRule)
- for i, dr := range dmsRules {
- dmsRuleMap[i] = dr
- }
-
- dmsIdx := 0
- for i, r := range allRules {
- if r.Source == dmsRulesPath {
- if dmr, ok := dmsRuleMap[dmsIdx]; ok {
- allRules[i].ID = dmr.ID
- allRules[i].Name = dmr.Name
- }
- dmsIdx++
- }
- }
-
- result.Rules = allRules
- result.DMSStatus = parseResult.DMSStatus
-
- case "hyprland":
- log.Fatalf("Hyprland support is currently disabled.") // ! disabled hyprland
- configDir, err := utils.ExpandPath("$HOME/.config/hypr")
- if err != nil {
- log.Fatalf("Failed to expand hyprland config path: %v", err)
- }
-
- parseResult, err := providers.ParseHyprlandWindowRules(configDir)
- if err != nil {
- log.Fatalf("Failed to parse hyprland window rules: %v", err)
- }
-
- allRules := providers.ConvertHyprlandRulesToWindowRules(parseResult.Rules)
-
- provider := providers.NewHyprlandWritableProvider(configDir)
- dmsRulesPath := provider.GetOverridePath()
- dmsRules, _ := provider.LoadDMSRules()
-
- dmsRuleMap := make(map[int]windowrules.WindowRule)
- for i, dr := range dmsRules {
- dmsRuleMap[i] = dr
- }
-
- dmsIdx := 0
- for i, r := range allRules {
- if r.Source == dmsRulesPath {
- if dmr, ok := dmsRuleMap[dmsIdx]; ok {
- allRules[i].ID = dmr.ID
- allRules[i].Name = dmr.Name
- }
- dmsIdx++
- }
- }
-
- result.Rules = allRules
- result.DMSStatus = parseResult.DMSStatus
-
- default:
- log.Fatalf("Unknown compositor: %s", compositor)
- }
-
- output, _ := json.Marshal(result)
- fmt.Fprintln(os.Stdout, string(output))
-}
-
-func runWindowrulesAdd(cmd *cobra.Command, args []string) {
- compositor := strings.ToLower(args[0])
- ruleJSON := args[1]
-
- var rule windowrules.WindowRule
- if err := json.Unmarshal([]byte(ruleJSON), &rule); err != nil {
- writeRuleError(fmt.Sprintf("Invalid JSON: %v", err))
- }
-
- if rule.ID == "" {
- rule.ID = generateRuleID()
- }
- rule.Enabled = true
-
- provider := getWindowRulesProvider(compositor)
- if provider == nil {
- writeRuleError(fmt.Sprintf("Unknown compositor: %s", compositor))
- }
-
- if err := provider.SetRule(rule); err != nil {
- writeRuleError(err.Error())
- }
-
- writeRuleSuccess(rule.ID, provider.GetOverridePath())
-}
-
-func runWindowrulesUpdate(cmd *cobra.Command, args []string) {
- compositor := strings.ToLower(args[0])
- ruleID := args[1]
- ruleJSON := args[2]
-
- var rule windowrules.WindowRule
- if err := json.Unmarshal([]byte(ruleJSON), &rule); err != nil {
- writeRuleError(fmt.Sprintf("Invalid JSON: %v", err))
- }
-
- rule.ID = ruleID
-
- provider := getWindowRulesProvider(compositor)
- if provider == nil {
- writeRuleError(fmt.Sprintf("Unknown compositor: %s", compositor))
- }
-
- if err := provider.SetRule(rule); err != nil {
- writeRuleError(err.Error())
- }
-
- writeRuleSuccess(rule.ID, provider.GetOverridePath())
-}
-
-func runWindowrulesRemove(cmd *cobra.Command, args []string) {
- compositor := strings.ToLower(args[0])
- ruleID := args[1]
-
- provider := getWindowRulesProvider(compositor)
- if provider == nil {
- writeRuleError(fmt.Sprintf("Unknown compositor: %s", compositor))
- }
-
- if err := provider.RemoveRule(ruleID); err != nil {
- writeRuleError(err.Error())
- }
-
- writeRuleSuccess(ruleID, provider.GetOverridePath())
-}
-
-func runWindowrulesReorder(cmd *cobra.Command, args []string) {
- compositor := strings.ToLower(args[0])
- idsJSON := args[1]
-
- var ids []string
- if err := json.Unmarshal([]byte(idsJSON), &ids); err != nil {
- writeRuleError(fmt.Sprintf("Invalid JSON array: %v", err))
- }
-
- provider := getWindowRulesProvider(compositor)
- if provider == nil {
- writeRuleError(fmt.Sprintf("Unknown compositor: %s", compositor))
- }
-
- if err := provider.ReorderRules(ids); err != nil {
- writeRuleError(err.Error())
- }
-
- writeRuleSuccess("", provider.GetOverridePath())
-}
-
-func getWindowRulesProvider(compositor string) windowrules.WritableProvider {
- switch compositor {
- case "niri":
- configDir, err := utils.ExpandPath("$HOME/.config/niri")
- if err != nil {
- return nil
- }
- return providers.NewNiriWritableProvider(configDir)
- case "hyprland":
- configDir, err := utils.ExpandPath("$HOME/.config/hypr")
- if err != nil {
- return nil
- }
- return providers.NewHyprlandWritableProvider(configDir)
- default:
- return nil
- }
-}
-
-func generateRuleID() string {
- return fmt.Sprintf("wr_%d", time.Now().UnixNano())
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/dpms_client.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/dpms_client.go
deleted file mode 100644
index 98f66d9..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/dpms_client.go
+++ /dev/null
@@ -1,339 +0,0 @@
-package main
-
-import (
- "fmt"
- "sync"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_power"
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
-)
-
-type cmd struct {
- fn func()
- done chan error
-}
-
-type dpmsClient struct {
- display *wlclient.Display
- ctx *wlclient.Context
- powerMgr *wlr_output_power.ZwlrOutputPowerManagerV1
- outputs map[string]*outputState
- mu sync.Mutex
- syncRound int
- done bool
- err error
- cmdq chan cmd
- stopChan chan struct{}
- wg sync.WaitGroup
-}
-
-type outputState struct {
- wlOutput *wlclient.Output
- powerCtrl *wlr_output_power.ZwlrOutputPowerV1
- name string
- mode uint32
- failed bool
- waitCh chan struct{}
- wantMode *uint32
-}
-
-func (c *dpmsClient) post(fn func()) {
- done := make(chan error, 1)
- select {
- case c.cmdq <- cmd{fn: fn, done: done}:
- <-done
- case <-c.stopChan:
- }
-}
-
-func (c *dpmsClient) waylandActor() {
- defer c.wg.Done()
- for {
- select {
- case <-c.stopChan:
- return
- case cmd := <-c.cmdq:
- cmd.fn()
- close(cmd.done)
- }
- }
-}
-
-func newDPMSClient() (*dpmsClient, error) {
- display, err := wlclient.Connect("")
- if err != nil {
- return nil, fmt.Errorf("failed to connect to Wayland: %w", err)
- }
-
- c := &dpmsClient{
- display: display,
- ctx: display.Context(),
- outputs: make(map[string]*outputState),
- cmdq: make(chan cmd, 128),
- stopChan: make(chan struct{}),
- }
-
- c.wg.Add(1)
- go c.waylandActor()
-
- registry, err := display.GetRegistry()
- if err != nil {
- display.Context().Close()
- return nil, fmt.Errorf("failed to get registry: %w", err)
- }
-
- registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
- switch e.Interface {
- case wlr_output_power.ZwlrOutputPowerManagerV1InterfaceName:
- powerMgr := wlr_output_power.NewZwlrOutputPowerManagerV1(c.ctx)
- version := min(e.Version, 1)
- if err := registry.Bind(e.Name, e.Interface, version, powerMgr); err == nil {
- c.powerMgr = powerMgr
- }
-
- case "wl_output":
- output := wlclient.NewOutput(c.ctx)
- version := min(e.Version, 4)
- if err := registry.Bind(e.Name, e.Interface, version, output); err == nil {
- outputID := fmt.Sprintf("output-%d", output.ID())
- state := &outputState{
- wlOutput: output,
- name: outputID,
- }
-
- c.mu.Lock()
- c.outputs[outputID] = state
- c.mu.Unlock()
-
- output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
- c.mu.Lock()
- delete(c.outputs, state.name)
- state.name = ev.Name
- c.outputs[ev.Name] = state
- c.mu.Unlock()
- })
- }
- }
- })
-
- syncCallback, err := display.Sync()
- if err != nil {
- c.Close()
- return nil, fmt.Errorf("failed to sync display: %w", err)
- }
- syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) {
- c.handleSync()
- })
-
- for !c.done {
- if err := c.ctx.Dispatch(); err != nil {
- c.Close()
- return nil, fmt.Errorf("dispatch error: %w", err)
- }
- }
-
- if c.err != nil {
- c.Close()
- return nil, c.err
- }
-
- return c, nil
-}
-
-func (c *dpmsClient) handleSync() {
- c.syncRound++
-
- switch c.syncRound {
- case 1:
- if c.powerMgr == nil {
- c.err = fmt.Errorf("wlr-output-power-management protocol not supported by compositor")
- c.done = true
- return
- }
-
- c.mu.Lock()
- for _, state := range c.outputs {
- powerCtrl, err := c.powerMgr.GetOutputPower(state.wlOutput)
- if err != nil {
- continue
- }
- state.powerCtrl = powerCtrl
-
- powerCtrl.SetModeHandler(func(e wlr_output_power.ZwlrOutputPowerV1ModeEvent) {
- c.mu.Lock()
- defer c.mu.Unlock()
- if state.powerCtrl == nil {
- return
- }
- state.mode = e.Mode
- if state.wantMode != nil && e.Mode == *state.wantMode && state.waitCh != nil {
- close(state.waitCh)
- state.wantMode = nil
- }
- })
-
- powerCtrl.SetFailedHandler(func(e wlr_output_power.ZwlrOutputPowerV1FailedEvent) {
- c.mu.Lock()
- defer c.mu.Unlock()
- if state.powerCtrl == nil {
- return
- }
- state.failed = true
- if state.waitCh != nil {
- close(state.waitCh)
- state.wantMode = nil
- }
- })
- }
- c.mu.Unlock()
-
- syncCallback, err := c.display.Sync()
- if err != nil {
- c.err = fmt.Errorf("failed to sync display: %w", err)
- c.done = true
- return
- }
- syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) {
- c.handleSync()
- })
-
- default:
- c.done = true
- }
-}
-
-func (c *dpmsClient) ListOutputs() []string {
- c.mu.Lock()
- defer c.mu.Unlock()
-
- names := make([]string, 0, len(c.outputs))
- for name := range c.outputs {
- names = append(names, name)
- }
- return names
-}
-
-func (c *dpmsClient) SetDPMS(outputName string, on bool) error {
- var mode uint32
- if on {
- mode = uint32(wlr_output_power.ZwlrOutputPowerV1ModeOn)
- } else {
- mode = uint32(wlr_output_power.ZwlrOutputPowerV1ModeOff)
- }
-
- var setErr error
- c.post(func() {
- c.mu.Lock()
- var waitStates []*outputState
-
- if outputName == "" || outputName == "all" {
- if len(c.outputs) == 0 {
- c.mu.Unlock()
- setErr = fmt.Errorf("no outputs found")
- return
- }
-
- for _, state := range c.outputs {
- if state.powerCtrl == nil {
- continue
- }
- state.wantMode = &mode
- state.waitCh = make(chan struct{})
- state.failed = false
- waitStates = append(waitStates, state)
- state.powerCtrl.SetMode(mode)
- }
- } else {
- state, ok := c.outputs[outputName]
- if !ok {
- c.mu.Unlock()
- setErr = fmt.Errorf("output not found: %s", outputName)
- return
- }
- if state.powerCtrl == nil {
- c.mu.Unlock()
- setErr = fmt.Errorf("output %s has nil powerCtrl", outputName)
- return
- }
- state.wantMode = &mode
- state.waitCh = make(chan struct{})
- state.failed = false
- waitStates = append(waitStates, state)
- state.powerCtrl.SetMode(mode)
- }
- c.mu.Unlock()
-
- deadline := time.Now().Add(10 * time.Second)
-
- for _, state := range waitStates {
- c.mu.Lock()
- ch := state.waitCh
- c.mu.Unlock()
-
- done := false
- for !done {
- if err := c.ctx.Dispatch(); err != nil {
- setErr = fmt.Errorf("dispatch error: %w", err)
- return
- }
-
- select {
- case <-ch:
- c.mu.Lock()
- if state.failed {
- setErr = fmt.Errorf("compositor reported failed for %s", state.name)
- c.mu.Unlock()
- return
- }
- c.mu.Unlock()
- done = true
- default:
- if time.Now().After(deadline) {
- setErr = fmt.Errorf("timeout waiting for mode change on %s", state.name)
- return
- }
- time.Sleep(10 * time.Millisecond)
- }
- }
- }
-
- c.mu.Lock()
- for _, state := range waitStates {
- if state.powerCtrl != nil {
- state.powerCtrl.Destroy()
- state.powerCtrl = nil
- }
- }
- c.mu.Unlock()
-
- c.display.Roundtrip()
- })
-
- return setErr
-}
-
-func (c *dpmsClient) Close() {
- close(c.stopChan)
- c.wg.Wait()
-
- c.mu.Lock()
- defer c.mu.Unlock()
-
- for _, state := range c.outputs {
- if state.powerCtrl != nil {
- state.powerCtrl.Destroy()
- }
- }
- c.outputs = nil
-
- if c.powerMgr != nil {
- c.powerMgr.Destroy()
- c.powerMgr = nil
- }
-
- if c.display != nil {
- c.ctx.Close()
- c.display = nil
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/immutable_policy.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/immutable_policy.go
deleted file mode 100644
index 5529c9f..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/immutable_policy.go
+++ /dev/null
@@ -1,271 +0,0 @@
-package main
-
-import (
- "bufio"
- _ "embed"
- "encoding/json"
- "fmt"
- "os"
- "strings"
- "sync"
-
- "github.com/spf13/cobra"
-)
-
-const (
- cliPolicyPackagedPath = "/usr/share/dms/cli-policy.json"
- cliPolicyAdminPath = "/etc/dms/cli-policy.json"
-)
-
-var (
- immutablePolicyOnce sync.Once
- immutablePolicy immutableCommandPolicy
- immutablePolicyErr error
-)
-
-//go:embed assets/cli-policy.default.json
-var defaultCLIPolicyJSON []byte
-
-type immutableCommandPolicy struct {
- ImmutableSystem bool
- ImmutableReason string
- BlockedCommands []string
- Message string
-}
-
-type cliPolicyFile struct {
- PolicyVersion int `json:"policy_version"`
- ImmutableSystem *bool `json:"immutable_system"`
- BlockedCommands *[]string `json:"blocked_commands"`
- Message *string `json:"message"`
-}
-
-func normalizeCommandSpec(raw string) string {
- normalized := strings.ToLower(strings.TrimSpace(raw))
- normalized = strings.TrimPrefix(normalized, "dms ")
- return strings.Join(strings.Fields(normalized), " ")
-}
-
-func normalizeBlockedCommands(raw []string) []string {
- normalized := make([]string, 0, len(raw))
- seen := make(map[string]bool)
-
- for _, cmd := range raw {
- spec := normalizeCommandSpec(cmd)
- if spec == "" || seen[spec] {
- continue
- }
- seen[spec] = true
- normalized = append(normalized, spec)
- }
-
- return normalized
-}
-
-func commandBlockedByPolicy(commandPath string, blocked []string) bool {
- normalizedPath := normalizeCommandSpec(commandPath)
- if normalizedPath == "" {
- return false
- }
-
- for _, entry := range blocked {
- spec := normalizeCommandSpec(entry)
- if spec == "" {
- continue
- }
- if normalizedPath == spec || strings.HasPrefix(normalizedPath, spec+" ") {
- return true
- }
- }
-
- return false
-}
-
-func loadPolicyFile(path string) (*cliPolicyFile, error) {
- data, err := os.ReadFile(path)
- if err != nil {
- if os.IsNotExist(err) {
- return nil, nil
- }
- return nil, fmt.Errorf("failed to read %s: %w", path, err)
- }
-
- var policy cliPolicyFile
- if err := json.Unmarshal(data, &policy); err != nil {
- return nil, fmt.Errorf("failed to parse %s: %w", path, err)
- }
-
- return &policy, nil
-}
-
-func mergePolicyFile(base *immutableCommandPolicy, path string) error {
- policyFile, err := loadPolicyFile(path)
- if err != nil {
- return err
- }
- if policyFile == nil {
- return nil
- }
-
- if policyFile.ImmutableSystem != nil {
- base.ImmutableSystem = *policyFile.ImmutableSystem
- }
- if policyFile.BlockedCommands != nil {
- base.BlockedCommands = normalizeBlockedCommands(*policyFile.BlockedCommands)
- }
- if policyFile.Message != nil {
- msg := strings.TrimSpace(*policyFile.Message)
- if msg != "" {
- base.Message = msg
- }
- }
-
- return nil
-}
-
-func readOSReleaseMap(path string) map[string]string {
- values := make(map[string]string)
-
- file, err := os.Open(path)
- if err != nil {
- return values
- }
- defer file.Close()
-
- scanner := bufio.NewScanner(file)
- for scanner.Scan() {
- line := strings.TrimSpace(scanner.Text())
- if line == "" || strings.HasPrefix(line, "#") {
- continue
- }
- parts := strings.SplitN(line, "=", 2)
- if len(parts) != 2 {
- continue
- }
- key := strings.ToUpper(strings.TrimSpace(parts[0]))
- value := strings.Trim(strings.TrimSpace(parts[1]), "\"")
- values[key] = strings.ToLower(value)
- }
-
- return values
-}
-
-func hasAnyToken(text string, tokens ...string) bool {
- if text == "" {
- return false
- }
- for _, token := range tokens {
- if strings.Contains(text, token) {
- return true
- }
- }
- return false
-}
-
-func detectImmutableSystem() (bool, string) {
- if _, err := os.Stat("/run/ostree-booted"); err == nil {
- return true, "/run/ostree-booted is present"
- }
-
- osRelease := readOSReleaseMap("/etc/os-release")
- if len(osRelease) == 0 {
- return false, ""
- }
-
- id := osRelease["ID"]
- idLike := osRelease["ID_LIKE"]
- variantID := osRelease["VARIANT_ID"]
- name := osRelease["NAME"]
- prettyName := osRelease["PRETTY_NAME"]
-
- immutableIDs := map[string]bool{
- "bluefin": true,
- "bazzite": true,
- "silverblue": true,
- "kinoite": true,
- "sericea": true,
- "onyx": true,
- "aurora": true,
- "fedora-iot": true,
- "fedora-coreos": true,
- }
- if immutableIDs[id] {
- return true, "os-release ID=" + id
- }
-
- markers := []string{"silverblue", "kinoite", "sericea", "onyx", "bazzite", "bluefin", "aurora", "ostree", "atomic"}
- if hasAnyToken(variantID, markers...) {
- return true, "os-release VARIANT_ID=" + variantID
- }
- if hasAnyToken(idLike, "ostree", "rpm-ostree") {
- return true, "os-release ID_LIKE=" + idLike
- }
- if hasAnyToken(name, markers...) || hasAnyToken(prettyName, markers...) {
- return true, "os-release identifies an atomic/ostree variant"
- }
-
- return false, ""
-}
-
-func getImmutablePolicy() (*immutableCommandPolicy, error) {
- immutablePolicyOnce.Do(func() {
- detectedImmutable, reason := detectImmutableSystem()
- immutablePolicy = immutableCommandPolicy{
- ImmutableSystem: detectedImmutable,
- ImmutableReason: reason,
- BlockedCommands: []string{"greeter install", "greeter enable", "setup"},
- Message: "This command is disabled on immutable/image-based systems. Use your distro-native workflow for system-level changes.",
- }
-
- var defaultPolicy cliPolicyFile
- if err := json.Unmarshal(defaultCLIPolicyJSON, &defaultPolicy); err != nil {
- immutablePolicyErr = fmt.Errorf("failed to parse embedded default CLI policy: %w", err)
- return
- }
- if defaultPolicy.BlockedCommands != nil {
- immutablePolicy.BlockedCommands = normalizeBlockedCommands(*defaultPolicy.BlockedCommands)
- }
- if defaultPolicy.Message != nil {
- msg := strings.TrimSpace(*defaultPolicy.Message)
- if msg != "" {
- immutablePolicy.Message = msg
- }
- }
-
- if err := mergePolicyFile(&immutablePolicy, cliPolicyPackagedPath); err != nil {
- immutablePolicyErr = err
- return
- }
- if err := mergePolicyFile(&immutablePolicy, cliPolicyAdminPath); err != nil {
- immutablePolicyErr = err
- return
- }
- })
-
- if immutablePolicyErr != nil {
- return nil, immutablePolicyErr
- }
- return &immutablePolicy, nil
-}
-
-func requireMutableSystemCommand(cmd *cobra.Command, _ []string) error {
- policy, err := getImmutablePolicy()
- if err != nil {
- return err
- }
- if !policy.ImmutableSystem {
- return nil
- }
-
- commandPath := normalizeCommandSpec(cmd.CommandPath())
- if !commandBlockedByPolicy(commandPath, policy.BlockedCommands) {
- return nil
- }
-
- reason := ""
- if policy.ImmutableReason != "" {
- reason = "Detected immutable system: " + policy.ImmutableReason + "\n"
- }
-
- return fmt.Errorf("%s%s\nCommand: dms %s\nPolicy files:\n %s\n %s", reason, policy.Message, commandPath, cliPolicyPackagedPath, cliPolicyAdminPath)
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/main.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/main.go
deleted file mode 100644
index c818148..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/main.go
+++ /dev/null
@@ -1,43 +0,0 @@
-//go:build !distro_binary
-
-package main
-
-import (
- "os"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
-)
-
-var Version = "dev"
-
-func init() {
- runCmd.Flags().BoolP("daemon", "d", false, "Run in daemon mode")
- runCmd.Flags().Bool("daemon-child", false, "Internal flag for daemon child process")
- runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
- runCmd.Flags().MarkHidden("daemon-child")
-
- greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd)
- authCmd.AddCommand(authSyncCmd)
- setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
- updateCmd.AddCommand(updateCheckCmd)
- pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
- rootCmd.AddCommand(getCommonCommands()...)
-
- rootCmd.AddCommand(authCmd)
- rootCmd.AddCommand(updateCmd)
-
- rootCmd.SetHelpTemplate(getHelpTemplate())
-}
-
-func main() {
- clipboard.MaybeServeAndExit()
-
- if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
- log.Fatal("This program should not be run as root. Exiting.")
- }
-
- if err := rootCmd.Execute(); err != nil {
- log.Fatal(err)
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/main_distro.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/main_distro.go
deleted file mode 100644
index 2b544ff..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/main_distro.go
+++ /dev/null
@@ -1,40 +0,0 @@
-//go:build distro_binary
-
-package main
-
-import (
- "os"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
-)
-
-var Version = "dev"
-
-func init() {
- runCmd.Flags().BoolP("daemon", "d", false, "Run in daemon mode")
- runCmd.Flags().Bool("daemon-child", false, "Internal flag for daemon child process")
- runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
- runCmd.Flags().MarkHidden("daemon-child")
-
- greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd)
- authCmd.AddCommand(authSyncCmd)
- setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
- pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
- rootCmd.AddCommand(getCommonCommands()...)
- rootCmd.AddCommand(authCmd)
-
- rootCmd.SetHelpTemplate(getHelpTemplate())
-}
-
-func main() {
- clipboard.MaybeServeAndExit()
-
- if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
- log.Fatal("This program should not be run as root. Exiting.")
- }
-
- if err := rootCmd.Execute(); err != nil {
- log.Fatal(err)
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/randr_client.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/randr_client.go
deleted file mode 100644
index 1a149ca..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/randr_client.go
+++ /dev/null
@@ -1,172 +0,0 @@
-package main
-
-import (
- "fmt"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
- wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
-)
-
-type randrOutput struct {
- Name string `json:"name"`
- Scale float64 `json:"scale"`
- Width int32 `json:"width"`
- Height int32 `json:"height"`
- Refresh int32 `json:"refresh"`
- Enabled bool `json:"enabled"`
-}
-
-type randrHead struct {
- name string
- enabled bool
- scale float64
- currentModeID uint32
- modeIDs []uint32
-}
-
-type randrMode struct {
- width int32
- height int32
- refresh int32
-}
-
-type randrClient struct {
- display *wlclient.Display
- ctx *wlclient.Context
- manager *wlr_output_management.ZwlrOutputManagerV1
- heads map[uint32]*randrHead
- modes map[uint32]*randrMode
- done bool
- err error
-}
-
-func queryRandr() ([]randrOutput, error) {
- display, err := wlclient.Connect("")
- if err != nil {
- return nil, fmt.Errorf("failed to connect to Wayland: %w", err)
- }
-
- c := &randrClient{
- display: display,
- ctx: display.Context(),
- heads: make(map[uint32]*randrHead),
- modes: make(map[uint32]*randrMode),
- }
- defer c.ctx.Close()
-
- registry, err := display.GetRegistry()
- if err != nil {
- return nil, fmt.Errorf("failed to get registry: %w", err)
- }
-
- registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
- if e.Interface == wlr_output_management.ZwlrOutputManagerV1InterfaceName {
- mgr := wlr_output_management.NewZwlrOutputManagerV1(c.ctx)
- version := min(e.Version, 4)
-
- mgr.SetHeadHandler(func(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) {
- c.handleHead(e)
- })
-
- mgr.SetDoneHandler(func(e wlr_output_management.ZwlrOutputManagerV1DoneEvent) {
- c.done = true
- })
-
- if err := registry.Bind(e.Name, e.Interface, version, mgr); err == nil {
- c.manager = mgr
- }
- }
- })
-
- // First roundtrip: discover globals and bind manager
- syncCallback, err := display.Sync()
- if err != nil {
- return nil, fmt.Errorf("failed to sync display: %w", err)
- }
- syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) {
- if c.manager == nil {
- c.err = fmt.Errorf("zwlr_output_manager_v1 protocol not supported by compositor")
- c.done = true
- }
- // Otherwise wait for manager's DoneHandler
- })
-
- for !c.done {
- if err := c.ctx.Dispatch(); err != nil {
- return nil, fmt.Errorf("dispatch error: %w", err)
- }
- }
-
- if c.err != nil {
- return nil, c.err
- }
-
- return c.buildOutputs(), nil
-}
-
-func (c *randrClient) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) {
- handle := e.Head
- headID := handle.ID()
-
- head := &randrHead{
- modeIDs: make([]uint32, 0),
- }
- c.heads[headID] = head
-
- handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) {
- head.name = e.Name
- })
-
- handle.SetEnabledHandler(func(e wlr_output_management.ZwlrOutputHeadV1EnabledEvent) {
- head.enabled = e.Enabled != 0
- })
-
- handle.SetScaleHandler(func(e wlr_output_management.ZwlrOutputHeadV1ScaleEvent) {
- head.scale = e.Scale
- })
-
- handle.SetCurrentModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1CurrentModeEvent) {
- head.currentModeID = e.Mode.ID()
- })
-
- handle.SetModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1ModeEvent) {
- modeHandle := e.Mode
- modeID := modeHandle.ID()
-
- head.modeIDs = append(head.modeIDs, modeID)
-
- mode := &randrMode{}
- c.modes[modeID] = mode
-
- modeHandle.SetSizeHandler(func(e wlr_output_management.ZwlrOutputModeV1SizeEvent) {
- mode.width = e.Width
- mode.height = e.Height
- })
-
- modeHandle.SetRefreshHandler(func(e wlr_output_management.ZwlrOutputModeV1RefreshEvent) {
- mode.refresh = e.Refresh
- })
- })
-}
-
-func (c *randrClient) buildOutputs() []randrOutput {
- outputs := make([]randrOutput, 0, len(c.heads))
-
- for _, head := range c.heads {
- out := randrOutput{
- Name: head.name,
- Scale: head.scale,
- Enabled: head.enabled,
- }
-
- if mode, ok := c.modes[head.currentModeID]; ok {
- out.Width = mode.width
- out.Height = mode.height
- out.Refresh = mode.refresh
- }
-
- outputs = append(outputs, out)
- }
-
- return outputs
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/server_client.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/server_client.go
deleted file mode 100644
index 871d21d..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/server_client.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package main
-
-import (
- "bufio"
- "encoding/json"
- "fmt"
- "net"
- "os"
- "path/filepath"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
-)
-
-func sendServerRequest(req models.Request) (*models.Response[any], error) {
- socketPath := getServerSocketPath()
-
- conn, err := net.Dial("unix", socketPath)
- if err != nil {
- return nil, fmt.Errorf("failed to connect to server (is it running?): %w", err)
- }
- defer conn.Close()
-
- scanner := bufio.NewScanner(conn)
- scanner.Scan() // discard initial capabilities message
-
- reqData, err := json.Marshal(req)
- if err != nil {
- return nil, fmt.Errorf("failed to marshal request: %w", err)
- }
-
- if _, err := conn.Write(reqData); err != nil {
- return nil, fmt.Errorf("failed to write request: %w", err)
- }
-
- if _, err := conn.Write([]byte("\n")); err != nil {
- return nil, fmt.Errorf("failed to write newline: %w", err)
- }
-
- if !scanner.Scan() {
- return nil, fmt.Errorf("failed to read response")
- }
-
- var resp models.Response[any]
- if err := json.Unmarshal(scanner.Bytes(), &resp); err != nil {
- return nil, fmt.Errorf("failed to unmarshal response: %w", err)
- }
-
- return &resp, nil
-}
-
-// sendServerRequestFireAndForget sends a request without waiting for a response.
-// Useful for commands that trigger UI or async operations.
-func sendServerRequestFireAndForget(req models.Request) error {
- socketPath := getServerSocketPath()
-
- conn, err := net.Dial("unix", socketPath)
- if err != nil {
- return fmt.Errorf("failed to connect to server (is it running?): %w", err)
- }
- defer conn.Close()
-
- scanner := bufio.NewScanner(conn)
- scanner.Scan() // discard initial capabilities message
-
- reqData, err := json.Marshal(req)
- if err != nil {
- return fmt.Errorf("failed to marshal request: %w", err)
- }
-
- if _, err := conn.Write(reqData); err != nil {
- return fmt.Errorf("failed to write request: %w", err)
- }
-
- if _, err := conn.Write([]byte("\n")); err != nil {
- return fmt.Errorf("failed to write newline: %w", err)
- }
-
- return nil
-}
-
-// tryServerRequest attempts to send a request but returns false if server unavailable.
-// Does not log errors - caller can decide what to do on failure.
-func tryServerRequest(req models.Request) (*models.Response[any], bool) {
- resp, err := sendServerRequest(req)
- if err != nil {
- return nil, false
- }
- return resp, true
-}
-
-func getServerSocketPath() string {
- runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
- if runtimeDir == "" {
- runtimeDir = os.TempDir()
- }
-
- entries, err := os.ReadDir(runtimeDir)
- if err != nil {
- return filepath.Join(runtimeDir, "danklinux.sock")
- }
-
- for _, entry := range entries {
- name := entry.Name()
- if name == "danklinux.sock" {
- return filepath.Join(runtimeDir, name)
- }
- if len(name) > 10 && name[:10] == "danklinux-" && filepath.Ext(name) == ".sock" {
- return filepath.Join(runtimeDir, name)
- }
- }
-
- return server.GetSocketPath()
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/shell.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/shell.go
deleted file mode 100644
index 7ce2003..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/shell.go
+++ /dev/null
@@ -1,739 +0,0 @@
-package main
-
-import (
- "context"
- "fmt"
- "os"
- "os/exec"
- "os/signal"
- "path/filepath"
- "slices"
- "strconv"
- "strings"
- "sync"
- "syscall"
- "time"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/server"
-)
-
-type ipcTargets map[string]map[string][]string
-
-// getProcessExitCode returns the exit code from a ProcessState.
-// For normal exits, returns the exit code directly.
-// For signal termination, returns 128 + signal number (Unix convention).
-func getProcessExitCode(state *os.ProcessState) int {
- if state == nil {
- return 1
- }
- if code := state.ExitCode(); code != -1 {
- return code
- }
- // Process was killed by signal - extract signal number
- if status, ok := state.Sys().(syscall.WaitStatus); ok {
- if status.Signaled() {
- return 128 + int(status.Signal())
- }
- }
- return 1
-}
-
-var isSessionManaged bool
-
-func execDetachedRestart(targetPID int) {
- selfPath, err := os.Executable()
- if err != nil {
- return
- }
-
- cmd := exec.Command(selfPath, "restart-detached", strconv.Itoa(targetPID))
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Setsid: true,
- }
- cmd.Start()
-}
-
-func runDetachedRestart(targetPIDStr string) {
- targetPID, err := strconv.Atoi(targetPIDStr)
- if err != nil {
- return
- }
-
- time.Sleep(200 * time.Millisecond)
-
- proc, err := os.FindProcess(targetPID)
- if err == nil {
- proc.Signal(syscall.SIGTERM)
- }
-
- time.Sleep(500 * time.Millisecond)
-
- killShell()
- runShellDaemon(false)
-}
-
-func getRuntimeDir() string {
- if runtime := os.Getenv("XDG_RUNTIME_DIR"); runtime != "" {
- return runtime
- }
- return os.TempDir()
-}
-
-func hasSystemdRun() bool {
- _, err := exec.LookPath("systemd-run")
- return err == nil
-}
-
-func getPIDFilePath() string {
- return filepath.Join(getRuntimeDir(), fmt.Sprintf("danklinux-%d.pid", os.Getpid()))
-}
-
-func writePIDFile(childPID int) error {
- pidFile := getPIDFilePath()
- return os.WriteFile(pidFile, []byte(strconv.Itoa(childPID)), 0o644)
-}
-
-func removePIDFile() {
- pidFile := getPIDFilePath()
- os.Remove(pidFile)
-}
-
-func getAllDMSPIDs() []int {
- dir := getRuntimeDir()
- entries, err := os.ReadDir(dir)
- if err != nil {
- return nil
- }
-
- var pids []int
-
- for _, entry := range entries {
- if !strings.HasPrefix(entry.Name(), "danklinux-") || !strings.HasSuffix(entry.Name(), ".pid") {
- continue
- }
-
- pidFile := filepath.Join(dir, entry.Name())
- data, err := os.ReadFile(pidFile)
- if err != nil {
- continue
- }
-
- childPID, err := strconv.Atoi(strings.TrimSpace(string(data)))
- if err != nil {
- os.Remove(pidFile)
- continue
- }
-
- proc, err := os.FindProcess(childPID)
- if err != nil {
- os.Remove(pidFile)
- continue
- }
-
- if err := proc.Signal(syscall.Signal(0)); err != nil {
- os.Remove(pidFile)
- continue
- }
-
- pids = append(pids, childPID)
-
- parentPIDStr := strings.TrimPrefix(entry.Name(), "danklinux-")
- parentPIDStr = strings.TrimSuffix(parentPIDStr, ".pid")
- if parentPID, err := strconv.Atoi(parentPIDStr); err == nil {
- if parentProc, err := os.FindProcess(parentPID); err == nil {
- if err := parentProc.Signal(syscall.Signal(0)); err == nil {
- pids = append(pids, parentPID)
- }
- }
- }
- }
-
- return pids
-}
-
-func runShellInteractive(session bool) {
- isSessionManaged = session
- go printASCII()
- fmt.Fprintf(os.Stderr, "dms %s\n", Version)
-
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- socketPath := server.GetSocketPath()
-
- configStateFile := filepath.Join(getRuntimeDir(), "danklinux.path")
- if err := os.WriteFile(configStateFile, []byte(configPath), 0o644); err != nil {
- log.Warnf("Failed to write config state file: %v", err)
- }
- defer os.Remove(configStateFile)
-
- errChan := make(chan error, 2)
-
- go func() {
- defer func() {
- if r := recover(); r != nil {
- errChan <- fmt.Errorf("server panic: %v", r)
- }
- }()
- server.CLIVersion = Version
- if err := server.Start(false); err != nil {
- errChan <- fmt.Errorf("server error: %w", err)
- }
- }()
-
- log.Infof("Spawning quickshell with -p %s", configPath)
-
- cmd := exec.CommandContext(ctx, "qs", "-p", configPath)
- cmd.Env = append(os.Environ(), "DMS_SOCKET="+socketPath)
- if os.Getenv("QT_LOGGING_RULES") == "" {
- if qtRules := log.GetQtLoggingRules(); qtRules != "" {
- cmd.Env = append(cmd.Env, "QT_LOGGING_RULES="+qtRules)
- }
- }
-
- // ! TODO - remove when QS 0.3 is up and we can use the pragma
- cmd.Env = append(cmd.Env, "QS_APP_ID=com.danklinux.dms")
-
- if isSessionManaged && hasSystemdRun() {
- cmd.Env = append(cmd.Env, "DMS_DEFAULT_LAUNCH_PREFIX=systemd-run --user --scope")
- }
-
- homeDir, err := os.UserHomeDir()
- if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" {
- if !strings.HasPrefix(configPath, homeDir) {
- cmd.Env = append(cmd.Env, "DMS_DISABLE_HOT_RELOAD=1")
- }
- }
-
- if os.Getenv("QT_QPA_PLATFORMTHEME") == "" {
- cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME=gtk3")
- }
- if os.Getenv("QT_QPA_PLATFORMTHEME_QT6") == "" {
- cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME_QT6=gtk3")
- }
- if os.Getenv("QT_QPA_PLATFORM") == "" {
- cmd.Env = append(cmd.Env, "QT_QPA_PLATFORM=wayland;xcb")
- }
-
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
-
- if err := cmd.Start(); err != nil {
- log.Fatalf("Error starting quickshell: %v", err)
- }
-
- // Write PID file for the quickshell child process
- if err := writePIDFile(cmd.Process.Pid); err != nil {
- log.Warnf("Failed to write PID file: %v", err)
- }
- defer removePIDFile()
-
- defer func() {
- if cmd.Process != nil {
- cmd.Process.Signal(syscall.SIGTERM)
- }
- }()
-
- sigChan := make(chan os.Signal, 1)
- signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
-
- go func() {
- if err := cmd.Wait(); err != nil {
- errChan <- fmt.Errorf("quickshell exited: %w", err)
- } else {
- errChan <- fmt.Errorf("quickshell exited")
- }
- }()
-
- for {
- select {
- case sig := <-sigChan:
- if sig == syscall.SIGUSR1 {
- if isSessionManaged {
- log.Infof("Received SIGUSR1, exiting for systemd restart...")
- cancel()
- cmd.Process.Signal(syscall.SIGTERM)
- os.Remove(socketPath)
- os.Exit(1)
- }
- log.Infof("Received SIGUSR1, spawning detached restart process...")
- execDetachedRestart(os.Getpid())
- return
- }
-
- // Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released)
- select {
- case <-errChan:
- cancel()
- os.Remove(socketPath)
- os.Exit(getProcessExitCode(cmd.ProcessState))
- case <-time.After(500 * time.Millisecond):
- }
-
- log.Infof("\nReceived signal %v, shutting down...", sig)
- cancel()
- cmd.Process.Signal(syscall.SIGTERM)
- os.Remove(socketPath)
- return
-
- case err := <-errChan:
- log.Error(err)
- cancel()
- if cmd.Process != nil {
- cmd.Process.Signal(syscall.SIGTERM)
- }
- os.Remove(socketPath)
- os.Exit(getProcessExitCode(cmd.ProcessState))
- }
- }
-}
-
-func restartShell() {
- pids := getAllDMSPIDs()
-
- if len(pids) == 0 {
- log.Info("No running DMS shell instances found. Starting daemon...")
- runShellDaemon(false)
- return
- }
-
- currentPid := os.Getpid()
- uniquePids := make(map[int]bool)
-
- for _, pid := range pids {
- if pid != currentPid {
- uniquePids[pid] = true
- }
- }
-
- for pid := range uniquePids {
- proc, err := os.FindProcess(pid)
- if err != nil {
- log.Errorf("Error finding process %d: %v", pid, err)
- continue
- }
-
- if err := proc.Signal(syscall.Signal(0)); err != nil {
- continue
- }
-
- if err := proc.Signal(syscall.SIGUSR1); err != nil {
- log.Errorf("Error sending SIGUSR1 to process %d: %v", pid, err)
- } else {
- log.Infof("Sent SIGUSR1 to DMS process with PID %d", pid)
- }
- }
-}
-
-func killShell() {
- pids := getAllDMSPIDs()
-
- if len(pids) == 0 {
- log.Info("No running DMS shell instances found.")
- return
- }
-
- currentPid := os.Getpid()
- uniquePids := make(map[int]bool)
-
- for _, pid := range pids {
- if pid != currentPid {
- uniquePids[pid] = true
- }
- }
-
- for pid := range uniquePids {
- proc, err := os.FindProcess(pid)
- if err != nil {
- log.Errorf("Error finding process %d: %v", pid, err)
- continue
- }
-
- if err := proc.Signal(syscall.Signal(0)); err != nil {
- continue
- }
-
- if err := proc.Kill(); err != nil {
- log.Errorf("Error killing process %d: %v", pid, err)
- } else {
- log.Infof("Killed DMS process with PID %d", pid)
- }
- }
-
- dir := getRuntimeDir()
- entries, err := os.ReadDir(dir)
- if err != nil {
- return
- }
-
- for _, entry := range entries {
- if strings.HasPrefix(entry.Name(), "danklinux-") && strings.HasSuffix(entry.Name(), ".pid") {
- pidFile := filepath.Join(dir, entry.Name())
- os.Remove(pidFile)
- }
- }
-}
-
-func runShellDaemon(session bool) {
- isSessionManaged = session
- isDaemonChild := slices.Contains(os.Args, "--daemon-child")
-
- if !isDaemonChild {
- fmt.Fprintf(os.Stderr, "dms %s\n", Version)
-
- cmd := exec.Command(os.Args[0], "run", "-d", "--daemon-child")
- cmd.Env = os.Environ()
-
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Setsid: true,
- }
-
- if err := cmd.Start(); err != nil {
- log.Fatalf("Error starting daemon: %v", err)
- }
-
- log.Infof("DMS shell daemon started (PID: %d)", cmd.Process.Pid)
- return
- }
-
- fmt.Fprintf(os.Stderr, "dms %s\n", Version)
-
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- socketPath := server.GetSocketPath()
-
- configStateFile := filepath.Join(getRuntimeDir(), "danklinux.path")
- if err := os.WriteFile(configStateFile, []byte(configPath), 0o644); err != nil {
- log.Warnf("Failed to write config state file: %v", err)
- }
- defer os.Remove(configStateFile)
-
- errChan := make(chan error, 2)
-
- go func() {
- defer func() {
- if r := recover(); r != nil {
- errChan <- fmt.Errorf("server panic: %v", r)
- }
- }()
- server.CLIVersion = Version
- if err := server.Start(false); err != nil {
- errChan <- fmt.Errorf("server error: %w", err)
- }
- }()
-
- log.Infof("Spawning quickshell with -p %s", configPath)
-
- cmd := exec.CommandContext(ctx, "qs", "-p", configPath)
- cmd.Env = append(os.Environ(), "DMS_SOCKET="+socketPath)
- if os.Getenv("QT_LOGGING_RULES") == "" {
- if qtRules := log.GetQtLoggingRules(); qtRules != "" {
- cmd.Env = append(cmd.Env, "QT_LOGGING_RULES="+qtRules)
- }
- }
-
- // ! TODO - remove when QS 0.3 is up and we can use the pragma
- cmd.Env = append(cmd.Env, "QS_APP_ID=com.danklinux.dms")
-
- if isSessionManaged && hasSystemdRun() {
- cmd.Env = append(cmd.Env, "DMS_DEFAULT_LAUNCH_PREFIX=systemd-run --user --scope")
- }
-
- homeDir, err := os.UserHomeDir()
- if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" {
- if !strings.HasPrefix(configPath, homeDir) {
- cmd.Env = append(cmd.Env, "DMS_DISABLE_HOT_RELOAD=1")
- }
- }
-
- if os.Getenv("QT_QPA_PLATFORMTHEME") == "" {
- cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME=gtk3")
- }
- if os.Getenv("QT_QPA_PLATFORMTHEME_QT6") == "" {
- cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME_QT6=gtk3")
- }
- if os.Getenv("QT_QPA_PLATFORM") == "" {
- cmd.Env = append(cmd.Env, "QT_QPA_PLATFORM=wayland;xcb")
- }
-
- devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
- if err != nil {
- log.Fatalf("Error opening /dev/null: %v", err)
- }
- defer devNull.Close()
-
- cmd.Stdin = devNull
- cmd.Stdout = devNull
- cmd.Stderr = devNull
-
- if err := cmd.Start(); err != nil {
- log.Fatalf("Error starting daemon: %v", err)
- }
-
- // Write PID file for the quickshell child process
- if err := writePIDFile(cmd.Process.Pid); err != nil {
- log.Warnf("Failed to write PID file: %v", err)
- }
- defer removePIDFile()
-
- defer func() {
- if cmd.Process != nil {
- cmd.Process.Signal(syscall.SIGTERM)
- }
- }()
-
- sigChan := make(chan os.Signal, 1)
- signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
-
- go func() {
- if err := cmd.Wait(); err != nil {
- errChan <- fmt.Errorf("quickshell exited: %w", err)
- } else {
- errChan <- fmt.Errorf("quickshell exited")
- }
- }()
-
- for {
- select {
- case sig := <-sigChan:
- if sig == syscall.SIGUSR1 {
- if isSessionManaged {
- log.Infof("Received SIGUSR1, exiting for systemd restart...")
- cancel()
- cmd.Process.Signal(syscall.SIGTERM)
- os.Remove(socketPath)
- os.Exit(1)
- }
- log.Infof("Received SIGUSR1, spawning detached restart process...")
- execDetachedRestart(os.Getpid())
- return
- }
-
- // Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released)
- select {
- case <-errChan:
- cancel()
- os.Remove(socketPath)
- os.Exit(getProcessExitCode(cmd.ProcessState))
- case <-time.After(500 * time.Millisecond):
- }
-
- cancel()
- cmd.Process.Signal(syscall.SIGTERM)
- os.Remove(socketPath)
- return
-
- case <-errChan:
- cancel()
- if cmd.Process != nil {
- cmd.Process.Signal(syscall.SIGTERM)
- }
- os.Remove(socketPath)
- os.Exit(getProcessExitCode(cmd.ProcessState))
- }
- }
-}
-
-var qsHasAnyDisplay = sync.OnceValue(func() bool {
- out, err := exec.Command("qs", "ipc", "--help").Output()
- if err != nil {
- return false
- }
- return strings.Contains(string(out), "--any-display")
-})
-
-func parseTargetsFromIPCShowOutput(output string) ipcTargets {
- targets := make(ipcTargets)
- var currentTarget string
- for line := range strings.SplitSeq(output, "\n") {
- if after, ok := strings.CutPrefix(line, "target "); ok {
- currentTarget = strings.TrimSpace(after)
- targets[currentTarget] = make(map[string][]string)
- }
- if strings.HasPrefix(line, " function") && currentTarget != "" {
- argsList := []string{}
- currentFunc := strings.TrimPrefix(line, " function ")
- funcDef := strings.SplitN(currentFunc, "(", 2)
- argList := strings.SplitN(funcDef[1], ")", 2)[0]
- args := strings.Split(argList, ",")
- if len(args) > 0 && strings.TrimSpace(args[0]) != "" {
- argsList = append(argsList, funcDef[0])
- for _, arg := range args {
- argName := strings.SplitN(strings.TrimSpace(arg), ":", 2)[0]
- argsList = append(argsList, argName)
- }
- targets[currentTarget][funcDef[0]] = argsList
- } else {
- targets[currentTarget][funcDef[0]] = make([]string, 0)
- }
- }
- }
- return targets
-}
-
-func getShellIPCCompletions(args []string, _ string) []string {
- cmdArgs := []string{"ipc"}
- if qsHasAnyDisplay() {
- cmdArgs = append(cmdArgs, "--any-display")
- }
- cmdArgs = append(cmdArgs, "-p", configPath, "show")
- cmd := exec.Command("qs", cmdArgs...)
- var targets ipcTargets
-
- if output, err := cmd.Output(); err == nil {
- targets = parseTargetsFromIPCShowOutput(string(output))
- } else {
- log.Debugf("Error getting IPC show output for completions: %v", err)
- return nil
- }
-
- if len(args) > 0 && args[0] == "call" {
- args = args[1:]
- }
-
- if len(args) == 0 {
- targetNames := make([]string, 0)
- targetNames = append(targetNames, "call")
- for k := range targets {
- targetNames = append(targetNames, k)
- }
- return targetNames
- }
- if len(args) == 1 {
- if targetFuncs, ok := targets[args[0]]; ok {
- funcNames := make([]string, 0)
- for k := range targetFuncs {
- funcNames = append(funcNames, k)
- }
- return funcNames
- }
- return nil
- }
- if len(args) <= len(targets[args[0]]) {
- funcArgs := targets[args[0]][args[1]]
- if len(funcArgs) >= len(args) {
- return []string{fmt.Sprintf("[%s]", funcArgs[len(args)-1])}
- }
- }
-
- return nil
-}
-
-func getFirstDMSPID() (int, bool) {
- dir := getRuntimeDir()
- entries, err := os.ReadDir(dir)
- if err != nil {
- return 0, false
- }
-
- for _, entry := range entries {
- if !strings.HasPrefix(entry.Name(), "danklinux-") || !strings.HasSuffix(entry.Name(), ".pid") {
- continue
- }
-
- data, err := os.ReadFile(filepath.Join(dir, entry.Name()))
- if err != nil {
- continue
- }
-
- pid, err := strconv.Atoi(strings.TrimSpace(string(data)))
- if err != nil {
- continue
- }
-
- proc, err := os.FindProcess(pid)
- if err != nil {
- continue
- }
-
- if proc.Signal(syscall.Signal(0)) != nil {
- continue
- }
-
- return pid, true
- }
-
- return 0, false
-}
-
-func runShellIPCCommand(args []string) {
- if len(args) == 0 {
- printIPCHelp()
- return
- }
-
- if args[0] != "call" {
- args = append([]string{"call"}, args...)
- }
-
- cmdArgs := []string{"ipc"}
-
- switch pid, ok := getFirstDMSPID(); {
- case ok:
- cmdArgs = append(cmdArgs, "--pid", strconv.Itoa(pid))
- default:
- if err := findConfig(nil, nil); err != nil {
- log.Fatalf("Error finding config: %v", err)
- }
- // ! TODO - remove check when QS 0.3 is released
- if qsHasAnyDisplay() {
- cmdArgs = append(cmdArgs, "--any-display")
- }
- cmdArgs = append(cmdArgs, "-p", configPath)
- }
-
- cmdArgs = append(cmdArgs, args...)
- cmd := exec.Command("qs", cmdArgs...)
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
-
- if err := cmd.Run(); err != nil {
- log.Fatalf("Error running IPC command: %v", err)
- }
-}
-
-func printIPCHelp() {
- fmt.Println("Usage: dms ipc <target> <function> [args...]")
- fmt.Println()
-
- cmdArgs := []string{"ipc"}
- if qsHasAnyDisplay() {
- cmdArgs = append(cmdArgs, "--any-display")
- }
- cmdArgs = append(cmdArgs, "-p", configPath, "show")
- cmd := exec.Command("qs", cmdArgs...)
-
- output, err := cmd.Output()
- if err != nil {
- fmt.Println("Could not retrieve available IPC targets (is DMS running?)")
- return
- }
-
- targets := parseTargetsFromIPCShowOutput(string(output))
- if len(targets) == 0 {
- fmt.Println("No IPC targets available")
- return
- }
-
- fmt.Println("Targets:")
-
- targetNames := make([]string, 0, len(targets))
- for name := range targets {
- targetNames = append(targetNames, name)
- }
- slices.Sort(targetNames)
-
- for _, targetName := range targetNames {
- funcs := targets[targetName]
- funcNames := make([]string, 0, len(funcs))
- for fn := range funcs {
- funcNames = append(funcNames, fn)
- }
- slices.Sort(funcNames)
- fmt.Printf(" %-16s %s\n", targetName, strings.Join(funcNames, ", "))
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/ui.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/ui.go
deleted file mode 100644
index c0b50e6..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/ui.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package main
-
-import (
- "fmt"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
- "github.com/charmbracelet/lipgloss"
-)
-
-func printASCII() {
- fmt.Print(getThemedASCII())
-}
-
-func getThemedASCII() string {
- theme := tui.TerminalTheme()
-
- logo := `
-██████╗ █████╗ ███╗ ██╗██╗ ██╗
-██╔══██╗██╔══██╗████╗ ██║██║ ██╔╝
-██║ ██║███████║██╔██╗ ██║█████╔╝
-██║ ██║██╔══██║██║╚██╗██║██╔═██╗
-██████╔╝██║ ██║██║ ╚████║██║ ██╗
-╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝`
-
- style := lipgloss.NewStyle().
- Foreground(lipgloss.Color(theme.Primary)).
- Bold(true)
-
- return style.Render(logo) + "\n"
-}
-
-func getHelpTemplate() string {
- return getThemedASCII() + `
-{{.Long}}
-
-Usage:
- {{.UseLine}}{{if .HasAvailableSubCommands}}
-
-Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
- {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
-
-Flags:
-{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
-
-Global Flags:
-{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
-
-Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
- {{rpad .Name .NamePadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
-
-Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
-`
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/utils.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/utils.go
deleted file mode 100644
index 9459e41..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dms/utils.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package main
-
-import (
- "fmt"
- "os/exec"
- "slices"
- "strings"
-)
-
-// isReadOnlyCommand returns true if the CLI args indicate a command that is
-// safe to run as root (e.g. shell completion, help).
-func isReadOnlyCommand(args []string) bool {
- for _, arg := range args[1:] {
- if strings.HasPrefix(arg, "-") {
- continue
- }
- switch arg {
- case "completion", "help", "__complete":
- return true
- }
- return false
- }
- return false
-}
-
-func isArchPackageInstalled(packageName string) bool {
- cmd := exec.Command("pacman", "-Q", packageName)
- err := cmd.Run()
- return err == nil
-}
-
-type systemdServiceState struct {
- Name string
- EnabledState string
- NeedsDisable bool
- Exists bool
-}
-
-// checkSystemdServiceEnabled returns (state, should_disable, error) for a systemd service
-func checkSystemdServiceEnabled(serviceName string) (string, bool, error) {
- cmd := exec.Command("systemctl", "is-enabled", serviceName)
- output, err := cmd.Output()
-
- stateStr := strings.TrimSpace(string(output))
-
- if err != nil {
- knownStates := []string{"disabled", "masked", "masked-runtime", "not-found", "enabled", "enabled-runtime", "static", "indirect", "alias"}
- isKnownState := slices.Contains(knownStates, stateStr)
-
- if !isKnownState {
- return stateStr, false, fmt.Errorf("systemctl is-enabled failed: %w (output: %s)", err, stateStr)
- }
- }
-
- shouldDisable := false
- switch stateStr {
- case "enabled", "enabled-runtime", "static", "indirect", "alias":
- shouldDisable = true
- case "disabled", "masked", "masked-runtime", "not-found":
- shouldDisable = false
- default:
- shouldDisable = true
- }
-
- return stateStr, shouldDisable, nil
-}
-
-func getSystemdServiceState(serviceName string) (*systemdServiceState, error) {
- state := &systemdServiceState{
- Name: serviceName,
- Exists: false,
- }
-
- enabledState, needsDisable, err := checkSystemdServiceEnabled(serviceName)
- if err != nil {
- return nil, fmt.Errorf("failed to check enabled state: %w", err)
- }
-
- state.EnabledState = enabledState
- state.NeedsDisable = needsDisable
-
- if enabledState == "not-found" {
- state.Exists = false
- return state, nil
- }
-
- state.Exists = true
- return state, nil
-}