mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-26 13:07:49 -04:00
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
This commit is contained in:
1 parent
d8a87a8e13
commit
ae361e37fc
11 files changed
+448
-64
No files matched your search
@@ -792,6 +792,7 @@ func getCommonCommands() []*cobra.Command {
|
||||
blurCmd,
|
||||
trashCmd,
|
||||
systemCmd,
|
||||
systemdCmd,
|
||||
switchUserCmd,
|
||||
dmenuCmd,
|
||||
}...)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
Reference in new issue
Block a user