package main import ( "context" "encoding/json" "fmt" "sort" "strings" ) // busctlName is one line of `busctl list --json`. type busctlName struct { Name string `json:"name"` PID *uint32 `json:"pid"` Process *string `json:"process"` User *string `json:"user"` Connection string `json:"connection"` Unit *string `json:"unit"` } // Name is one well-known name on a bus, with what holds it. type Name struct { Name string `json:"name"` PID uint32 `json:"pid,omitempty"` Process string `json:"process,omitempty"` User string `json:"user,omitempty"` Unit string `json:"unit,omitempty"` Connection string `json:"connection,omitempty"` } // Names is dbus_names's answer. type Names struct { Bus string `json:"bus"` Running []Name `json:"running"` ActivatableNotRunning []string `json:"activatable_not_running"` Connections int `json:"connections"` Cut bool `json:"cut,omitempty"` } // ParseNames reads `busctl list --json=short`: the well-known names running, the activatable names // that are not, and how many connections (unique names) the bus has. func ParseNames(bus, out string) (Names, error) { var raw []busctlName if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &raw); err != nil { return Names{}, fmt.Errorf("busctl list answered what is not its JSON: %w", err) } n := Names{Bus: bus, Running: []Name{}, ActivatableNotRunning: []string{}} for _, r := range raw { switch { case r.Name == busName: // the bus itself, which busctl attributes to the service manager case strings.HasPrefix(r.Name, ":"): n.Connections++ case r.Connection == "(activatable)" || r.PID == nil: n.ActivatableNotRunning = append(n.ActivatableNotRunning, r.Name) default: n.Running = append(n.Running, Name{Name: r.Name, PID: deref(r.PID), Process: derefS(r.Process), User: derefS(r.User), Unit: derefS(r.Unit), Connection: r.Connection}) } } sort.Slice(n.Running, func(i, j int) bool { return n.Running[i].Name < n.Running[j].Name }) sort.Strings(n.ActivatableNotRunning) if len(n.Running) > AnswerCap { n.Running, n.Cut = n.Running[:AnswerCap], true } if len(n.ActivatableNotRunning) > AnswerCap { n.ActivatableNotRunning, n.Cut = n.ActivatableNotRunning[:AnswerCap], true } return n, nil } // Names lists one bus's names as this account sees them. func (m *Machine) Names(ctx context.Context, bus string) (Names, error) { args, err := m.busArgs(bus) if err != nil { return Names{}, err } out, err := m.Run(ctx, "busctl", append(args, "--json=short", "--no-pager", "list")...) if err != nil { return Names{}, err } return ParseNames(orWord(bus, "system"), out) } func deref(p *uint32) uint32 { if p == nil { return 0 } return *p } func derefS(p *string) string { if p == nil { return "" } return *p } func orWord(s, word string) string { if strings.TrimSpace(s) == "" { return word } return s }