From ae361e37fc06fcf24c5e465217f6b15b23211362 Mon Sep 17 00:00:00 2001 From: purian23 Date: Sat, 17 Oct 2026 03:47:06 -0400 Subject: [PATCH] systemd: dms systemd command binds dms.service to the preferred compositor session unit - niri, mango & hyprland supported - `dms systemd enable/disable/status/start/stop/restart` supported --- core/cmd/dms/commands_common.go | 1 + core/cmd/dms/commands_doctor.go | 25 ++-- core/cmd/dms/commands_systemd.go | 134 ++++++++++++++++++ core/internal/config/deployer.go | 12 +- core/internal/config/deployer_test.go | 83 +++++++++++ core/internal/config/mango_deploy.go | 24 +--- core/internal/config/mango_deploy_test.go | 8 +- core/internal/config/session_units.go | 152 +++++++++++++++++++++ core/internal/config/session_units_test.go | 51 +++++++ core/internal/distros/base.go | 17 +-- core/internal/mangoconf/session.go | 5 - 11 files changed, 448 insertions(+), 64 deletions(-) create mode 100644 core/cmd/dms/commands_systemd.go create mode 100644 core/internal/config/session_units.go create mode 100644 core/internal/config/session_units_test.go diff --git a/core/cmd/dms/commands_common.go b/core/cmd/dms/commands_common.go index d564dfd33..89659a839 100644 --- a/core/cmd/dms/commands_common.go +++ b/core/cmd/dms/commands_common.go @@ -792,6 +792,7 @@ func getCommonCommands() []*cobra.Command { blurCmd, trashCmd, systemCmd, + systemdCmd, switchUserCmd, dmenuCmd, }...) diff --git a/core/cmd/dms/commands_doctor.go b/core/cmd/dms/commands_doctor.go index 2690c38d8..7b12aa424 100644 --- a/core/cmd/dms/commands_doctor.go +++ b/core/cmd/dms/commands_doctor.go @@ -886,11 +886,6 @@ func checkWindowManagers() []checkResult { return results } -func mangoWantsDMS() bool { - _, err := os.Stat(mangoconf.WantsLink()) - return err == nil -} - var mangoLegacyOverviewBinds = regexp.MustCompile(`(?im)^\s*mousebind\s*=\s*none\s*,\s*(btn_left\s*,\s*toggleoverview\s*,\s*1|btn_right\s*,\s*killclient\s*,\s*0)\s*$`) func checkMangoConfig(mangoDir string, dialect mangoconf.Dialect) []checkResult { @@ -1454,6 +1449,7 @@ func checkSystemdServices() []checkResult { var results []checkResult dmsState := getServiceState("dms", true) + wanted := config.SessionWanted() if !dmsState.exists { results = append(results, checkResult{catServices, "dms.service", statusInfo, "Not installed", "Optional user service", doctorDocsURL + "#services"}) } else { @@ -1465,8 +1461,8 @@ func checkSystemdServices() []checkResult { case dmsState.active == "failed": status = statusError case dmsState.active == "active": - case dmsState.enabled == "disabled" && os.Getenv("MANGO_INSTANCE_SIGNATURE") != "" && mangoWantsDMS(): - message = "Started with " + mangoconf.SessionTarget + case dmsState.enabled == "disabled" && len(wanted) > 0: + message = "Started with " + strings.Join(wanted, ", ") if dmsState.active != "" { message += ", " + dmsState.active } @@ -1477,7 +1473,16 @@ func checkSystemdServices() []checkResult { case dmsState.active == "inactive": status = statusError } - results = append(results, checkResult{catServices, "dms.service", status, message, "", doctorDocsURL + "#services"}) + details := "" + if len(wanted) > 0 { + details = "Wanted by " + strings.Join(wanted, ", ") + } + if wm, ok := config.RunningCompositor(); ok && status != statusOK { + if unit := config.SessionUnitName(wm); !slices.Contains(wanted, unit) { + details = fmt.Sprintf("Nothing starts dms.service with %s. Run 'dms systemd enable'.", unit) + } + } + results = append(results, checkResult{catServices, "dms.service", status, message, details, doctorDocsURL + "#services"}) } if dmsState.exists && dmsState.enabled == "enabled" { @@ -1607,7 +1612,7 @@ func printResultLine(r checkResult, styles tui.Styles) { fmt.Printf(" %s %s %s %s\n", style.Render(icon), name, styles.Subtle.Render(dots), r.message) - if doctorVerbose && r.details != "" { + if r.details != "" && (doctorVerbose || r.status != statusOK) { fmt.Printf(" %s\n", styles.Subtle.Render("└─ "+r.details)) } @@ -1666,7 +1671,7 @@ func formatResultsPlain(results []checkResult) string { fmt.Fprintf(&sb, "- [%s] %s: %s\n", r.status, r.name, r.message) - if doctorVerbose && r.details != "" { + if r.details != "" && (doctorVerbose || r.status != statusOK) { fmt.Fprintf(&sb, " - %s\n", r.details) } } diff --git a/core/cmd/dms/commands_systemd.go b/core/cmd/dms/commands_systemd.go new file mode 100644 index 000000000..c6cbd5ab6 --- /dev/null +++ b/core/cmd/dms/commands_systemd.go @@ -0,0 +1,134 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/config" + "github.com/AvengeMedia/DankMaterialShell/core/internal/deps" + "github.com/spf13/cobra" +) + +var systemdCompositorFlag string + +var systemdCmd = &cobra.Command{ + Use: "systemd", + Short: "Start DMS as a systemd user service with the compositor session", + Long: `Manage dms.service for the compositor session. + +enable makes the compositor's own session unit want dms.service, so DMS starts +with that compositor only: hyprland-session.target, niri.service or +mango-session.target. The compositor is the running one, or --compositor.`, +} + +var systemdEnableCmd = &cobra.Command{ + Use: "enable", + Short: "Start dms.service with the compositor session", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + wm, err := systemdCompositor() + if err != nil { + return err + } + unit, err := config.EnableSession(wm) + if err != nil { + return err + } + fmt.Printf("dms.service starts with %s\n", unit) + now, _ := cmd.Flags().GetBool("now") + if !now { + return nil + } + if err := runSystemctl("--user", "start", dmsSystemdUnit); err != nil { + return err + } + fmt.Println("dms.service started") + return nil + }, +} + +var systemdDisableCmd = &cobra.Command{ + Use: "disable", + Short: "Stop starting dms.service with any compositor session", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + removed, err := config.DisableSession() + if err != nil { + return err + } + if len(removed) == 0 { + fmt.Println("dms.service was not wanted by any compositor session") + } else { + fmt.Printf("dms.service no longer starts with %s\n", strings.Join(removed, ", ")) + } + now, _ := cmd.Flags().GetBool("now") + if !now { + return nil + } + return runSystemctl("--user", "stop", dmsSystemdUnit) + }, +} + +var systemdStatusCmd = &cobra.Command{ + Use: "status", + Short: "Show which compositor sessions start dms.service", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + state := getServiceState("dms", true) + if !state.exists { + fmt.Println("dms.service: not installed") + return + } + fmt.Printf("dms.service: %s", state.enabled) + if state.active != "" { + fmt.Printf(", %s", state.active) + } + fmt.Println() + wanted := config.SessionWanted() + if len(wanted) == 0 { + fmt.Println("Starts with: nothing (run 'dms systemd enable')") + return + } + fmt.Printf("Starts with: %s\n", strings.Join(wanted, ", ")) + }, +} + +func systemdLifecycleCmd(verb string) *cobra.Command { + return &cobra.Command{ + Use: verb, + Short: strings.ToUpper(verb[:1]) + verb[1:] + " dms.service", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + return runSystemctl("--user", verb, dmsSystemdUnit) + }, + } +} + +func systemdCompositor() (deps.WindowManager, error) { + if systemdCompositorFlag == "" { + if wm, ok := config.RunningCompositor(); ok { + return wm, nil + } + name, err := detectCompositorForSetup() + if err != nil { + return 0, err + } + systemdCompositorFlag = name + } + switch strings.ToLower(systemdCompositorFlag) { + case "hyprland": + return deps.WindowManagerHyprland, nil + case "niri": + return deps.WindowManagerNiri, nil + case "mango", "mangowc": + return deps.WindowManagerMango, nil + } + return 0, fmt.Errorf("unknown compositor %q (hyprland, niri or mango)", systemdCompositorFlag) +} + +func init() { + systemdEnableCmd.Flags().StringVar(&systemdCompositorFlag, "compositor", "", "hyprland, niri or mango (default: the running one)") + systemdEnableCmd.Flags().Bool("now", false, "Also start dms.service") + systemdDisableCmd.Flags().Bool("now", false, "Also stop dms.service") + systemdCmd.AddCommand(systemdEnableCmd, systemdDisableCmd, systemdStatusCmd, systemdLifecycleCmd("start"), systemdLifecycleCmd("stop"), systemdLifecycleCmd("restart")) +} diff --git a/core/internal/config/deployer.go b/core/internal/config/deployer.go index 8bccb5499..436afae67 100644 --- a/core/internal/config/deployer.go +++ b/core/internal/config/deployer.go @@ -262,7 +262,7 @@ func (cd *ConfigDeployer) deployNiriConfig(terminalCommand string, useSystemd bo newConfig := strings.ReplaceAll(NiriConfig, "{{TERMINAL_COMMAND}}", terminalCommand) - if !useSystemd { + if !cd.systemdAutostart(deps.WindowManagerNiri, useSystemd) { newConfig = cd.transformNiriConfigForNonSystemd(newConfig, terminalCommand) } @@ -652,7 +652,7 @@ func (cd *ConfigDeployer) deployHyprlandConfig(terminalCommand string, useSystem newConfig := strings.ReplaceAll(HyprlandLuaConfig, "{{TERMINAL_COMMAND}}", terminalCommand) - if !useSystemd { + if !cd.systemdAutostart(deps.WindowManagerHyprland, useSystemd) { newConfig = transformHyprlandLuaForNonSystemd(newConfig, terminalCommand) } @@ -671,14 +671,6 @@ func (cd *ConfigDeployer) deployHyprlandConfig(terminalCommand string, 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, mainConfBackup, 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/deployer_test.go b/core/internal/config/deployer_test.go index 1eddf7917..003c1b668 100644 --- a/core/internal/config/deployer_test.go +++ b/core/internal/config/deployer_test.go @@ -2,6 +2,7 @@ package config import ( "context" + "errors" "os" "path/filepath" "strings" @@ -12,6 +13,27 @@ import ( "github.com/stretchr/testify/require" ) +// Tests never reach the user's systemd; stubAddWants counts the add-wants calls a deploy makes. +func TestMain(m *testing.M) { + systemctlUser = func(...string) error { return nil } + os.Exit(m.Run()) +} + +func stubAddWants(t *testing.T, addWants error) *int { + t.Helper() + calls := 0 + prev := systemctlUser + systemctlUser = func(args ...string) error { + if args[0] != "add-wants" { + return nil + } + calls++ + return addWants + } + t.Cleanup(func() { systemctlUser = prev }) + return &calls +} + func TestCleanupStrayHyprlandConfFile(t *testing.T) { if os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") == "" { t.Setenv("HYPRLAND_INSTANCE_SIGNATURE", "test-signature") @@ -805,6 +827,67 @@ func TestDeployHyprlandConfigWritesLuaBeforeRemovingLegacyConf(t *testing.T) { assert.Equal(t, filepath.Join(filepath.Dir(result.BackupPath), "hyprland.conf"), result.BackupPath) } +func TestDeployHyprlandConfigSystemdAutostart(t *testing.T) { + cases := []struct { + name string + useSystemd bool + addWants error + wantExec bool + wantCalls int + wantLink bool + }{ + {"systemd", true, nil, false, 1, true}, + {"add-wants fails", true, errors.New("no user bus"), true, 1, true}, + {"no systemd", false, nil, true, 0, false}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + calls := stubAddWants(t, tc.addWants) + link := WantsLink(HyprlandSessionTarget) + require.NoError(t, os.MkdirAll(filepath.Dir(link), 0o755)) + require.NoError(t, os.Symlink("/dev/null", link)) + + result, err := NewConfigDeployer(nil).deployHyprlandConfig("kitty", tc.useSystemd) + require.NoError(t, err) + content, err := os.ReadFile(result.Path) + require.NoError(t, err) + assert.Equal(t, tc.wantExec, strings.Contains(string(content), `hl.exec_cmd("dms run")`)) + assert.Equal(t, !tc.wantExec, strings.Contains(string(content), "systemctl --user start hyprland-session.target")) + assert.Equal(t, tc.wantCalls, *calls) + _, statErr := os.Lstat(link) + assert.Equal(t, tc.wantLink, statErr == nil) + }) + } +} + +func TestDeployNiriConfigSystemdAutostart(t *testing.T) { + cases := []struct { + name string + useSystemd bool + addWants error + wantSpawn bool + wantCalls int + }{ + {"systemd", true, nil, false, 1}, + {"add-wants fails", true, errors.New("no user bus"), true, 1}, + {"no systemd", false, nil, true, 0}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Setenv("HOME", t.TempDir()) + calls := stubAddWants(t, tc.addWants) + result, err := NewConfigDeployer(nil).deployNiriConfig("kitty", tc.useSystemd) + require.NoError(t, err) + content, err := os.ReadFile(result.Path) + require.NoError(t, err) + assert.Equal(t, tc.wantSpawn, strings.Contains(string(content), `spawn-at-startup "dms" "run"`)) + assert.Equal(t, tc.wantCalls, *calls) + }) + } +} + func TestHyprlandLuaUnsupported(t *testing.T) { for ver, refuse := range map[string]bool{ "0.54.2": true, diff --git a/core/internal/config/mango_deploy.go b/core/internal/config/mango_deploy.go index c73ac210a..dcc6f6c74 100644 --- a/core/internal/config/mango_deploy.go +++ b/core/internal/config/mango_deploy.go @@ -3,7 +3,6 @@ package config import ( "fmt" "os" - "os/exec" "path/filepath" "regexp" "strings" @@ -51,9 +50,6 @@ var ( var ( mangoDialect = mangoconf.Detect mangoSessionTargetInstalled = mangoconf.SessionTargetInstalled - mangoAddWants = func() error { - return exec.Command("systemctl", "--user", "add-wants", mangoconf.SessionTarget, "dms").Run() - } ) // MangoExistingBinds returns where the user's Mango binds live: a non-empty @@ -88,24 +84,6 @@ func extractMangoLines(text string, match *regexp.Regexp) string { return strings.Join(out, "\n") + "\n" } -// mangoSystemdAutostart starts dms.service from mango-session.target and -// returns false when that is not possible, so the config keeps exec-once. -func (cd *ConfigDeployer) mangoSystemdAutostart(useSystemd bool) bool { - link := mangoconf.WantsLink() - if !useSystemd || !mangoSessionTargetInstalled() { - if err := os.Remove(link); err == nil { - cd.log("Removed dms.service from " + mangoconf.SessionTarget) - } - return false - } - if err := mangoAddWants(); err != nil { - cd.log(fmt.Sprintf("Warning: add-wants %s dms failed (%v); starting DMS from the Mango config instead", mangoconf.SessionTarget, err)) - return false - } - cd.log("DMS starts as dms.service with " + mangoconf.SessionTarget) - return true -} - func (cd *ConfigDeployer) deployMangoConfig(terminalCommand string, useSystemd bool) (DeploymentResult, error) { configDir := mangoconf.Dir() result := DeploymentResult{ @@ -134,7 +112,7 @@ func (cd *ConfigDeployer) deployMangoConfig(terminalCommand string, useSystemd b dialect := mangoDialect() newConfig := strings.ReplaceAll(MangoConfig, "{{TERMINAL_COMMAND}}", terminalCommand) - if cd.mangoSystemdAutostart(useSystemd) { + if cd.systemdAutostart(deps.WindowManagerMango, useSystemd) { newConfig = mangoExecOnceDMS.ReplaceAllString(newConfig, "") } if err := os.WriteFile(result.Path, []byte(dialect.Translate(newConfig)), 0o644); err != nil { diff --git a/core/internal/config/mango_deploy_test.go b/core/internal/config/mango_deploy_test.go index 99574a1df..1eff67ac7 100644 --- a/core/internal/config/mango_deploy_test.go +++ b/core/internal/config/mango_deploy_test.go @@ -15,13 +15,11 @@ import ( func stubMango(t *testing.T, dialect mangoconf.Dialect, target bool, addWants error) *int { t.Helper() - calls := 0 - prevDialect, prevTarget, prevWants := mangoDialect, mangoSessionTargetInstalled, mangoAddWants + prevDialect, prevTarget := mangoDialect, mangoSessionTargetInstalled mangoDialect = func() mangoconf.Dialect { return dialect } mangoSessionTargetInstalled = func() bool { return target } - mangoAddWants = func() error { calls++; return addWants } - t.Cleanup(func() { mangoDialect, mangoSessionTargetInstalled, mangoAddWants = prevDialect, prevTarget, prevWants }) - return &calls + t.Cleanup(func() { mangoDialect, mangoSessionTargetInstalled = prevDialect, prevTarget }) + return stubAddWants(t, addWants) } func readMango(t *testing.T, home, rel string) string { diff --git a/core/internal/config/session_units.go b/core/internal/config/session_units.go new file mode 100644 index 000000000..103e96985 --- /dev/null +++ b/core/internal/config/session_units.go @@ -0,0 +1,152 @@ +package config + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/deps" + "github.com/AvengeMedia/DankMaterialShell/core/internal/mangoconf" +) + +// dms.service is wanted by the compositor's own session unit rather than enabled +// for graphical-session.target, so it never starts under GNOME or KDE on the same user. +const ( + HyprlandSessionTarget = "hyprland-session.target" + NiriSessionUnit = "niri.service" +) + +var sessionUnits = []string{HyprlandSessionTarget, NiriSessionUnit, mangoconf.SessionTarget} + +var systemctlUser = func(args ...string) error { + output, err := exec.Command("systemctl", append([]string{"--user"}, args...)...).CombinedOutput() + if err == nil { + return nil + } + if detail := strings.TrimSpace(string(output)); detail != "" { + return fmt.Errorf("%s: %w", detail, err) + } + return err +} + +// SessionUnitName is the unit dms.service is wanted by under wm, with no side effects. +func SessionUnitName(wm deps.WindowManager) string { + switch wm { + case deps.WindowManagerHyprland: + return HyprlandSessionTarget + case deps.WindowManagerNiri: + return NiriSessionUnit + case deps.WindowManagerMango: + return mangoconf.SessionTarget + } + return "" +} + +// SessionUnit returns the unit dms.service should be wanted by under wm, making sure it +// exists. Hyprland before 0.57 gets our target written; Mango before 0.17.1 has none and yields "". +func SessionUnit(wm deps.WindowManager) (string, error) { + switch wm { + case deps.WindowManagerHyprland: + if _, err := EnsureHyprlandSessionTarget(); err != nil { + return "", fmt.Errorf("write %s: %w", HyprlandSessionTarget, err) + } + return HyprlandSessionTarget, nil + case deps.WindowManagerNiri: + return NiriSessionUnit, nil + case deps.WindowManagerMango: + if !mangoSessionTargetInstalled() { + return "", nil + } + return mangoconf.SessionTarget, nil + } + return "", fmt.Errorf("unsupported compositor %v", wm) +} + +// WantsLink is the add-wants symlink that starts dms.service with unit. +func WantsLink(unit string) string { + return filepath.Join(os.Getenv("HOME"), ".config", "systemd", "user", unit+".wants", "dms.service") +} + +// SessionWanted lists the compositor units that currently pull dms.service in. +func SessionWanted() []string { + var wanted []string + for _, unit := range sessionUnits { + if _, err := os.Lstat(WantsLink(unit)); err == nil { + wanted = append(wanted, unit) + } + } + return wanted +} + +// EnableSession makes wm's session unit want dms.service and returns that unit. +func EnableSession(wm deps.WindowManager) (string, error) { + unit, err := SessionUnit(wm) + if err != nil { + return "", err + } + if unit == "" { + return "", errors.New("this Mango has no mango-session.target (added in 0.17.1); start DMS from the Mango config instead") + } + if err := systemctlUser("add-wants", unit, "dms"); err != nil { + return unit, fmt.Errorf("add-wants %s dms: %w", unit, err) + } + return unit, nil +} + +// DisableSession drops dms.service from every compositor session unit and returns the ones it was wanted by. +func DisableSession() ([]string, error) { + var removed []string + for _, unit := range sessionUnits { + if err := os.Remove(WantsLink(unit)); err != nil { + if os.IsNotExist(err) { + continue + } + return removed, err + } + removed = append(removed, unit) + } + if len(removed) == 0 { + return nil, nil + } + return removed, systemctlUser("daemon-reload") +} + +// RunningCompositor identifies the compositor of the current session from its environment. +func RunningCompositor() (deps.WindowManager, bool) { + switch { + case os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") != "": + return deps.WindowManagerHyprland, true + case os.Getenv("NIRI_SOCKET") != "": + return deps.WindowManagerNiri, true + case os.Getenv("MANGO_INSTANCE_SIGNATURE") != "": + return deps.WindowManagerMango, true + } + return 0, false +} + +// systemdAutostart makes the compositor session start dms.service and returns false when +// that is not possible, so the deployed config keeps starting DMS itself. +func (cd *ConfigDeployer) systemdAutostart(wm deps.WindowManager, useSystemd bool) bool { + name := SessionUnitName(wm) + if !useSystemd { + if err := os.Remove(WantsLink(name)); err == nil { + cd.log("Removed dms.service from " + name) + } + return false + } + unit, err := EnableSession(wm) + if err != nil { + cd.log(fmt.Sprintf("Warning: %v; starting DMS from the compositor config instead", err)) + if unit == "" { + if err := os.Remove(WantsLink(name)); err == nil { + cd.log("Removed dms.service from " + name) + } + } + return false + } + cd.log("DMS starts as dms.service with " + unit) + return true +} diff --git a/core/internal/config/session_units_test.go b/core/internal/config/session_units_test.go new file mode 100644 index 000000000..2f059f303 --- /dev/null +++ b/core/internal/config/session_units_test.go @@ -0,0 +1,51 @@ +package config + +import ( + "os" + "path/filepath" + "testing" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/deps" + "github.com/AvengeMedia/DankMaterialShell/core/internal/mangoconf" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDisableSessionRemovesEveryWantsLink(t *testing.T) { + t.Setenv("HOME", t.TempDir()) + for _, unit := range []string{HyprlandSessionTarget, mangoconf.SessionTarget} { + link := WantsLink(unit) + require.NoError(t, os.MkdirAll(filepath.Dir(link), 0o755)) + require.NoError(t, os.Symlink("/dev/null", link)) + } + var reloads int + prev := systemctlUser + systemctlUser = func(args ...string) error { + if args[0] == "daemon-reload" { + reloads++ + } + return nil + } + t.Cleanup(func() { systemctlUser = prev }) + + assert.Equal(t, []string{HyprlandSessionTarget, mangoconf.SessionTarget}, SessionWanted()) + removed, err := DisableSession() + require.NoError(t, err) + assert.Equal(t, []string{HyprlandSessionTarget, mangoconf.SessionTarget}, removed) + assert.Empty(t, SessionWanted()) + assert.Equal(t, 1, reloads) + + removed, err = DisableSession() + require.NoError(t, err) + assert.Nil(t, removed) + assert.Equal(t, 1, reloads) +} + +func TestEnableSessionMangoWithoutTarget(t *testing.T) { + t.Setenv("HOME", t.TempDir()) + calls := stubMango(t, mangoconf.Snake, false, nil) + unit, err := EnableSession(deps.WindowManagerMango) + require.Error(t, err) + assert.Empty(t, unit) + assert.Equal(t, 0, *calls) +} diff --git a/core/internal/distros/base.go b/core/internal/distros/base.go index 72b9600bd..42d825e7c 100644 --- a/core/internal/distros/base.go +++ b/core/internal/distros/base.go @@ -600,18 +600,13 @@ TERMINAL=%s return nil } -func (b *BaseDistribution) EnableDMSService(ctx context.Context, wm deps.WindowManager) error { - switch wm { - case deps.WindowManagerNiri: - if err := exec.CommandContext(ctx, "systemctl", "--user", "add-wants", "niri.service", "dms").Run(); err != nil { - b.log("Warning: failed to add dms as a want for niri.service") - } - case deps.WindowManagerHyprland: - if err := exec.CommandContext(ctx, "systemctl", "--user", "add-wants", "hyprland-session.target", "dms").Run(); err != nil { - b.log("Warning: failed to add dms as a want for hyprland-session.target") - } +func (b *BaseDistribution) EnableDMSService(_ context.Context, wm deps.WindowManager) error { + unit, err := config.EnableSession(wm) + if err != nil { + b.log(fmt.Sprintf("Warning: %v", err)) + return nil } - + b.log("dms.service starts with " + unit) return nil } diff --git a/core/internal/mangoconf/session.go b/core/internal/mangoconf/session.go index 9a6abef94..4e193c693 100644 --- a/core/internal/mangoconf/session.go +++ b/core/internal/mangoconf/session.go @@ -46,8 +46,3 @@ func SessionTargetInstalled() bool { } return false } - -// WantsLink is the add-wants symlink that starts dms.service with the Mango session. -func WantsLink() string { - return filepath.Join(os.Getenv("HOME"), ".config", "systemd", "user", SessionTarget+".wants", "dms.service") -}