package main import ( "context" "github.com/godbus/dbus/v5" ) const ( busName = "org.freedesktop.DBus" busPath = "/org/freedesktop/DBus" ) // systemBus is the machine's system bus as the watcher uses it, over its own private connection: the // bus driver's answers and its NameOwnerChanged signal, never another peer's messages. type systemBus struct { conn *dbus.Conn out chan NameChange } // DialSystemBus connects to the system bus as this account and listens for names changing owner. func DialSystemBus() (Bus, error) { conn, err := dbus.SystemBusPrivate() if err != nil { return nil, err } if err := conn.Auth(nil); err != nil { conn.Close() return nil, err } if err := conn.Hello(); err != nil { conn.Close() return nil, err } if err := conn.AddMatchSignal(dbus.WithMatchSender(busName), dbus.WithMatchInterface(busName), dbus.WithMatchMember("NameOwnerChanged")); err != nil { conn.Close() return nil, err } raw := make(chan *dbus.Signal, 256) conn.Signal(raw) b := &systemBus{conn: conn, out: make(chan NameChange, 256)} go func() { // The signal channel closes when the connection is lost; so does this one, which is how the // watcher learns the bus went away. defer close(b.out) for { select { case s, open := <-raw: if !open { return } if s.Name != busName+".NameOwnerChanged" || len(s.Body) != 3 { continue } name, _ := s.Body[0].(string) old, _ := s.Body[1].(string) nw, _ := s.Body[2].(string) b.out <- NameChange{Name: name, Old: old, New: nw} case <-conn.Context().Done(): return } } }() return b, nil } func (b *systemBus) driver() dbus.BusObject { return b.conn.Object(busName, busPath) } // Ping asks the bus driver its id. Not org.freedesktop.DBus.Peer.Ping: dbus-broker's system policy // refuses that to an account that is not root, and logs the refusal as a denial. func (b *systemBus) Ping(ctx context.Context) error { var id string return b.driver().CallWithContext(ctx, busName+".GetId", 0).Store(&id) } func (b *systemBus) ID(ctx context.Context) (string, error) { var id string err := b.driver().CallWithContext(ctx, busName+".GetId", 0).Store(&id) return id, err } func (b *systemBus) PID(ctx context.Context, name string) (uint32, error) { var pid uint32 err := b.driver().CallWithContext(ctx, busName+".GetConnectionUnixProcessID", 0, name).Store(&pid) return pid, err } func (b *systemBus) Names(ctx context.Context) ([]string, error) { var names []string err := b.driver().CallWithContext(ctx, busName+".ListNames", 0).Store(&names) return names, err } func (b *systemBus) Activatable(ctx context.Context) ([]string, error) { var names []string err := b.driver().CallWithContext(ctx, busName+".ListActivatableNames", 0).Store(&names) return names, err } func (b *systemBus) Changes() <-chan NameChange { return b.out } func (b *systemBus) Close() { b.conn.Close() }