diff --git a/core/cmd/dms/commands_doctor.go b/core/cmd/dms/commands_doctor.go index f9a55763e..597f0d243 100644 --- a/core/cmd/dms/commands_doctor.go +++ b/core/cmd/dms/commands_doctor.go @@ -1181,6 +1181,13 @@ func checkSystemdServices() []checkResult { results = append(results, checkResult{catServices, "dms.service", status, message, "", doctorDocsURL + "#services"}) } + if dmsState.exists && dmsState.enabled == "enabled" { + gsState := getServiceState("graphical-session.target", true) + if gsState.exists && gsState.active != "active" { + results = append(results, checkResult{catServices, "graphical-session.target", statusWarn, "Inactive", "Compositor session never activated it, so dms.service cannot autostart. On Hyprland, hyprland-session.target must exist and be started by the compositor.", doctorDocsURL + "#services"}) + } + } + greetdState := getServiceState("greetd", false) switch { case greetdState.exists: diff --git a/core/internal/config/deployer.go b/core/internal/config/deployer.go index 9c9f23a65..df1f52aa0 100644 --- a/core/internal/config/deployer.go +++ b/core/internal/config/deployer.go @@ -690,6 +690,14 @@ func (cd *ConfigDeployer) deployHyprlandConfig(terminal deps.Terminal, useSystem return result, result.Error } + if useSystemd { + if targetPath, err := EnsureHyprlandSessionTarget(); err != nil { + cd.log(fmt.Sprintf("Warning: failed to write hyprland-session.target: %v", err)) + } else { + cd.log(fmt.Sprintf("Ensured hyprland session target at %s", targetPath)) + } + } + movedLegacy, err := backupLegacyHyprlandConfFiles(configDir, dmsDir, backupDir) if err != nil { result.Error = fmt.Errorf("failed to back up legacy hyprlang configs: %w", err) diff --git a/core/internal/config/hyprland_session_target.go b/core/internal/config/hyprland_session_target.go new file mode 100644 index 000000000..c844d3be8 --- /dev/null +++ b/core/internal/config/hyprland_session_target.go @@ -0,0 +1,48 @@ +package config + +import ( + "os" + "path/filepath" +) + +const hyprlandSessionTargetUnit = `[Unit] +Description=Hyprland Session Target +BindsTo=graphical-session.target +Before=graphical-session.target +Wants=graphical-session-pre.target +After=graphical-session-pre.target +` + +const legacyHyprlandSessionTargetUnit = `[Unit] +Description=Hyprland Session Target +Requires=graphical-session.target +After=graphical-session.target +` + +// Only a missing or dankinstall-written unit is replaced; hand-written units are left alone. +func EnsureHyprlandSessionTarget() (string, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return "", err + } + + targetPath := filepath.Join(homeDir, ".config", "systemd", "user", "hyprland-session.target") + existing, err := os.ReadFile(targetPath) + if err == nil { + content := string(existing) + if content == hyprlandSessionTargetUnit { + return targetPath, nil + } + if content != legacyHyprlandSessionTargetUnit { + return targetPath, nil + } + } + + if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil { + return "", err + } + if err := os.WriteFile(targetPath, []byte(hyprlandSessionTargetUnit), 0o644); err != nil { + return "", err + } + return targetPath, nil +} diff --git a/core/internal/config/hyprland_session_target_test.go b/core/internal/config/hyprland_session_target_test.go new file mode 100644 index 000000000..53e00f291 --- /dev/null +++ b/core/internal/config/hyprland_session_target_test.go @@ -0,0 +1,73 @@ +package config + +import ( + "os" + "path/filepath" + "testing" +) + +func targetPathFor(t *testing.T, home string) string { + t.Helper() + return filepath.Join(home, ".config", "systemd", "user", "hyprland-session.target") +} + +func TestEnsureHyprlandSessionTargetWritesMissing(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + + path, err := EnsureHyprlandSessionTarget() + if err != nil { + t.Fatal(err) + } + if path != targetPathFor(t, home) { + t.Fatalf("unexpected path %s", path) + } + data, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if string(data) != hyprlandSessionTargetUnit { + t.Fatalf("unexpected content:\n%s", data) + } +} + +func TestEnsureHyprlandSessionTargetUpgradesLegacy(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + path := targetPathFor(t, home) + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte(legacyHyprlandSessionTargetUnit), 0o644); err != nil { + t.Fatal(err) + } + + if _, err := EnsureHyprlandSessionTarget(); err != nil { + t.Fatal(err) + } + data, _ := os.ReadFile(path) + if string(data) != hyprlandSessionTargetUnit { + t.Fatalf("legacy unit not upgraded:\n%s", data) + } +} + +func TestEnsureHyprlandSessionTargetKeepsCustom(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + path := targetPathFor(t, home) + custom := "[Unit]\nDescription=mine\n" + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte(custom), 0o644); err != nil { + t.Fatal(err) + } + + if _, err := EnsureHyprlandSessionTarget(); err != nil { + t.Fatal(err) + } + data, _ := os.ReadFile(path) + if string(data) != custom { + t.Fatalf("custom unit was overwritten:\n%s", data) + } +} diff --git a/core/internal/distros/base.go b/core/internal/distros/base.go index f8cb3e442..66b1732da 100644 --- a/core/internal/distros/base.go +++ b/core/internal/distros/base.go @@ -13,6 +13,7 @@ import ( "strings" "time" + "github.com/AvengeMedia/DankMaterialShell/core/internal/config" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/netfetch" "github.com/AvengeMedia/DankMaterialShell/core/internal/privesc" @@ -616,24 +617,8 @@ func (b *BaseDistribution) WriteWindowManagerConfig(wm deps.WindowManager) error } func (b *BaseDistribution) WriteHyprlandSessionTarget() error { - homeDir, err := os.UserHomeDir() + targetPath, err := config.EnsureHyprlandSessionTarget() if err != nil { - return fmt.Errorf("failed to get home directory: %w", err) - } - - targetDir := filepath.Join(homeDir, ".config", "systemd", "user") - if err := os.MkdirAll(targetDir, 0o755); err != nil { - return fmt.Errorf("failed to create systemd user directory: %w", err) - } - - targetPath := filepath.Join(targetDir, "hyprland-session.target") - content := `[Unit] -Description=Hyprland Session Target -Requires=graphical-session.target -After=graphical-session.target -` - - if err := os.WriteFile(targetPath, []byte(content), 0o644); err != nil { return fmt.Errorf("failed to write hyprland-session.target: %w", err) }