From 172a743de477aebcb14f3b116f5ec6142d898af8 Mon Sep 17 00:00:00 2001 From: Lucas <43530291+LuckShiba@users.noreply.github.com> Date: Thu, 15 Jan 2026 22:59:47 -0300 Subject: [PATCH] doctor: use dbus for checking on services (#1384) * doctor: use dbus for checking on services * doctor: show docs URL for failed checks * core: remove unused function --- core/cmd/dms/commands_doctor.go | 111 ++++++++++++++++---------------- core/internal/utils/dbus.go | 20 ++++++ core/internal/utils/exec.go | 14 ---- 3 files changed, 74 insertions(+), 71 deletions(-) create mode 100644 core/internal/utils/dbus.go diff --git a/core/cmd/dms/commands_doctor.go b/core/cmd/dms/commands_doctor.go index f6e17314..0ddce39c 100644 --- a/core/cmd/dms/commands_doctor.go +++ b/core/cmd/dms/commands_doctor.go @@ -477,7 +477,7 @@ func checkWindowManagers() []checkResult { results = append(results, checkResult{ catCompositor, c.name, statusOK, getVersionFromCommand(c.versionCmd, c.versionArg, c.versionRegex), details, - doctorDocsURL + "#compositor", + doctorDocsURL + "#compositor-checks", }) } @@ -486,7 +486,7 @@ func checkWindowManagers() []checkResult { catCompositor, "Compositor", statusError, "No supported Wayland compositor found", "Install Hyprland, niri, Sway, River, or Wayfire", - doctorDocsURL + "#compositor", + doctorDocsURL + "#compositor-checks", }) } @@ -634,19 +634,14 @@ func checkI2CAvailability() checkResult { return checkResult{catOptionalFeatures, "I2C/DDC", statusOK, fmt.Sprintf("%d monitor(s) detected", len(devices)), "External monitor brightness control", doctorDocsURL + "#optional-features"} } -func detectNetworkBackend() string { - result, err := network.DetectNetworkStack() - if err != nil { - return "" - } - - switch result.Backend { +func detectNetworkBackend(stackResult *network.DetectResult) string { + switch stackResult.Backend { case network.BackendNetworkManager: return "NetworkManager" case network.BackendIwd: return "iwd" case network.BackendNetworkd: - if result.HasIwd { + if stackResult.HasIwd { return "iwd + systemd-networkd" } return "systemd-networkd" @@ -657,75 +652,73 @@ func detectNetworkBackend() string { } } +func getOptionalDBusStatus(busName string) (status, string) { + if utils.IsDBusServiceAvailable(busName) { + return statusOK, "Available" + } else { + return statusWarn, "Not available" + } +} + func checkOptionalDependencies() []checkResult { var results []checkResult - if utils.IsServiceActive("accounts-daemon", false) { - results = append(results, checkResult{catOptionalFeatures, "accountsservice", statusOK, "Running", "User accounts", doctorDocsURL + "#optional-features"}) - } else { - results = append(results, checkResult{catOptionalFeatures, "accountsservice", statusWarn, "Not running", "User accounts", doctorDocsURL + "#optional-features"}) - } + optionalFeaturesURL := doctorDocsURL + "#optional-features" - if utils.IsServiceActive("power-profiles-daemon", false) { - results = append(results, checkResult{catOptionalFeatures, "power-profiles-daemon", statusOK, "Running", "Power profile management", doctorDocsURL + "#optional-features"}) - } else { - results = append(results, checkResult{catOptionalFeatures, "power-profiles-daemon", statusInfo, "Not running", "Power profile management", 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}) results = append(results, checkI2CAvailability()) terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"} if idx := slices.IndexFunc(terminals, utils.CommandExists); idx >= 0 { - results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, terminals[idx], "", doctorDocsURL + "#optional-features"}) + results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, terminals[idx], "", optionalFeaturesURL}) } else { - results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, or alacritty", doctorDocsURL + "#optional-features"}) + results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, 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, altCmd, desc string - important bool + 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}, - {"Network", "nmcli", "iwctl", "Network management", false}, - {"danksearch", "dsearch", "", "File search", false}, - {"loginctl", "loginctl", "", "Session management", false}, - {"fprintd", "fprintd-list", "", "Fingerprint auth", false}, + {"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, foundCmd := utils.CommandExists(d.cmd), d.cmd - if !found && d.altCmd != "" && utils.CommandExists(d.altCmd) { - found, foundCmd = true, d.altCmd - } + found := utils.CommandExists(d.cmd) switch { case found: - message := "Installed" - details := d.desc - if d.name == "Network" { - result, err := network.DetectNetworkStack() - if err == nil && result.Backend != network.BackendNone { - message = detectNetworkBackend() + " (active)" - if doctorVerbose { - details = result.ChosenReason - } - } else { - switch foundCmd { - case "nmcli": - message = "NetworkManager (installed)" - case "iwctl": - message = "iwd (installed)" - } - } - } - results = append(results, checkResult{catOptionalFeatures, d.name, statusOK, message, details, doctorDocsURL + "#optional-features"}) + 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, doctorDocsURL + "#optional-features"}) + 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, doctorDocsURL + "#optional-features"}) + results = append(results, checkResult{catOptionalFeatures, d.name, statusInfo, "Not installed", d.desc, optionalFeaturesURL}) } } @@ -893,6 +886,10 @@ func printResultLine(r checkResult, styles tui.Styles) { 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) { diff --git a/core/internal/utils/dbus.go b/core/internal/utils/dbus.go new file mode 100644 index 00000000..3f65aae2 --- /dev/null +++ b/core/internal/utils/dbus.go @@ -0,0 +1,20 @@ +package utils + +import ( + "github.com/godbus/dbus/v5" +) + +func IsDBusServiceAvailable(busName string) bool { + conn, err := dbus.ConnectSystemBus() + if err != nil { + return false + } + defer conn.Close() + + obj := conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus") + var owned bool + if err := obj.Call("org.freedesktop.DBus.NameHasOwner", 0, busName).Store(&owned); err != nil { + return false + } + return owned +} diff --git a/core/internal/utils/exec.go b/core/internal/utils/exec.go index c9bfafec..aa1eb319 100644 --- a/core/internal/utils/exec.go +++ b/core/internal/utils/exec.go @@ -2,7 +2,6 @@ package utils import ( "os/exec" - "strings" ) type AppChecker interface { @@ -43,16 +42,3 @@ func AnyCommandExists(cmds ...string) bool { } return false } - -func IsServiceActive(name string, userService bool) bool { - if !CommandExists("systemctl") { - return false - } - - args := []string{"is-active", name} - if userService { - args = []string{"--user", "is-active", name} - } - output, _ := exec.Command("systemctl", args...).Output() - return strings.EqualFold(strings.TrimSpace(string(output)), "active") -}