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
+}