~rkintzi/statusbar

2d31ef0eb894fede5f75a78fdc3f1f65ea85546b — RadosÅ‚aw Kintzi 1 year, 23 days ago 5361e1e master
Add KeyringUnlocker widget
4 files changed, 217 insertions(+), 0 deletions(-)

M go.mod
M go.sum
A widgets/keyring.go
A widgets/keyring_dbus.go
M go.mod => go.mod +1 -0
@@ 4,6 4,7 @@ go 1.21.3

require (
	github.com/adrg/xdg v0.4.0
	github.com/coreos/go-systemd/v22 v22.5.0
	github.com/godbus/dbus/v5 v5.1.0
	github.com/google/go-cmp v0.6.0
	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510

M go.sum => go.sum +3 -0
@@ 1,10 1,13 @@
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=

A widgets/keyring.go => widgets/keyring.go +92 -0
@@ 0,0 1,92 @@
package widgets

import (
	"context"
	"os/exec"
	"sync"

	"github.com/godbus/dbus/v5"
	"gobytes.dev/statusbar"
	"gobytes.dev/statusbar/internal"

	sd "github.com/coreos/go-systemd/v22/dbus"
)

const targetName = "keyring-unlocked.target"

type KeyringUnlocker struct {
	mutext sync.Mutex
	locked bool

	baseWidget `yaml:",inline"`
}

func NewKeyringWidget() *KeyringUnlocker { return new(KeyringUnlocker) }

func (w *KeyringUnlocker) Run(ctx context.Context, update statusbar.UpdateFunc) {
	mon := make(chan *keyringEvent)
	sdconn, err := sd.NewUserConnectionContext(ctx)
	if err != nil {
		w.errorf("Error conecting to SystemD: %s", err)
	}
	go func(mon <-chan *keyringEvent, update statusbar.UpdateFunc, w *KeyringUnlocker) {
		var (
			seq    uint64
			block  = w.Block()
			blocks = []statusbar.Block{block}
		)
		block.FullText = "<span foreground='#fbb86c'>\uF1A7</span>"
		block.Name = "keyring"
		block.Markup = "pango"

		for ev := range mon {
			if ev.Seq < seq {
				continue
			}
			w.mutext.Lock()
			w.locked = ev.Locked
			w.mutext.Unlock()
			blocks = blocks[:0]
			if ev.Locked {
				blocks = append(blocks, block)
				_, err := sdconn.StopUnitContext(ctx, targetName, "replace", nil)
				if err != nil {
					w.errorf("Error stopping target %s: %s", targetName, err)
				}
			} else {
				_, err := sdconn.StartUnitContext(ctx, targetName, "replace", nil)
				if err != nil {
					w.errorf("Error starting target %s: %s", targetName, err)
				}
			}
			update(blocks)
		}
	}(mon, update, w)
	conn, err := dbus.ConnectSessionBus()
	if err != nil {
		w.errorf("Error conecting to dbus: %s", err)
	}
	err = newSecretService(conn).MonitorDefaultKeyring(ctx, mon)
	if err != nil {
		w.errorf("Error monitoring keyring: %s", err)
	}
}

func (w *KeyringUnlocker) Event(ev statusbar.Event) {
	w.mutext.Lock()
	locked := w.locked
	w.mutext.Unlock()
	go func(locked bool) {
		if locked {
			cmd := exec.Command("unlock_keyring")
			err := cmd.Run()
			if err != nil {
				w.errorf("Failed to unlock keyring: %s")
			}
		}
	}(locked)
}

func init() {
	statusbar.RegisterInternalWidget[KeyringUnlocker](internal.M, "KeyringUnlocker")
}

A widgets/keyring_dbus.go => widgets/keyring_dbus.go +121 -0
@@ 0,0 1,121 @@
package widgets

import (
	"context"

	"github.com/godbus/dbus/v5"
)

const (
	dBusSecretsPath           = "/org/freedesktop/secrets"
	dBusSecretsServiceName    = "org.freedesktop.secrets"
	dBusSecretsServiceIface   = "org.freedesktop.Secret.Service"
	dBusCollectionChangeSig   = "CollectionChanged"
	dBusDefaultCollectionPath = "/org/freedesktop/secrets/aliases/default"
)

type keyringEvent struct {
	Locked bool
	Seq    uint64
}

type keyringDBus struct {
	conn *dbus.Conn
	dbus dbus.BusObject
}

func newKeyring(conn *dbus.Conn, path dbus.ObjectPath) *keyringDBus {
	return &keyringDBus{
		conn: conn,
		dbus: conn.Object(dBusSecretsServiceName, path),
	}
}

func (k *keyringDBus) Locked() (bool, error) {
	val, err := k.dbus.GetProperty("org.freedesktop.Secret.Collection.Locked")
	if err != nil {
		return true, err
	}
	return val.Value().(bool), nil
}

func (k *keyringDBus) Path() dbus.ObjectPath {
	return k.dbus.Path()
}

type secretsService struct {
	conn *dbus.Conn
	dbus dbus.BusObject
}

func newSecretService(conn *dbus.Conn) *secretsService {
	return &secretsService{
		conn: conn,
		dbus: conn.Object(dBusSecretsServiceName, dBusSecretsPath),
	}
}

// ReadAlias (IN String name, OUT ObjectPath Collection)
func (s *secretsService) ReadAlias(alias string) (*keyringDBus, error) {
	var path dbus.ObjectPath
	err := s.dbus.Call("org.freedesktop.Secret.Service.ReadAlias", 0, alias).Store(&path)
	if err != nil {
		return nil, err
	}
	return newKeyring(s.conn, path), nil
}

func (s *secretsService) Path() dbus.ObjectPath {
	return s.dbus.Path()
}

func (s *secretsService) MonitorDefaultKeyring(ctx context.Context, mon chan<- *keyringEvent) error {
	keyring, err := s.ReadAlias("default")
	if err != nil {
		return err
	}
	locked, err := keyring.Locked()
	if err != nil {
		return err
	}
	ch := make(chan *dbus.Signal)
	errs := make(chan error)
	go func(conn *dbus.Conn, k *keyringDBus, ch <-chan *dbus.Signal, errs chan<- error, mon chan<- *keyringEvent) {
		defer close(errs)
		for s := range ch {
			path := s.Body[0].(dbus.ObjectPath)
			if k.Path() != path {
				continue
			}
			locked, err := k.Locked()
			if err != nil {
				errs <- err
				return
			}
			mon <- &keyringEvent{
				Locked: locked,
				Seq:    uint64(s.Sequence + 2),
			}
		}
	}(s.conn, keyring, ch, errs, mon)

	s.conn.Signal(ch)
	err = s.conn.AddMatchSignal(
		dbus.WithMatchInterface(dBusSecretsServiceIface),
		dbus.WithMatchMember(dBusCollectionChangeSig))
	if err != nil {
		return err
	}
	mon <- &keyringEvent{
		Locked: locked,
		Seq:    1,
	}
	select {
	case err := <-errs:
		return err
	case <-ctx.Done():
		s.conn.RemoveSignal(ch)
		close(ch)
	}
	return <-errs
}