aboutsummaryrefslogtreecommitdiffhomepage
path: root/dbus
diff options
context:
space:
mode:
authorOphestra Umiker <cat@ophivana.moe>2024-09-09 03:11:50 +0900
committerOphestra Umiker <cat@ophivana.moe>2024-09-09 03:11:50 +0900
commit357cc4ce4d2888033a8163fe39861c8a88301da6 (patch)
treea6dbdd09fc5ef2dea82ad1c85627b6455c2f3477 /dbus
parent3242ce340621839d03ecf34bb3ed20d7c6f3cbb3 (diff)
dbus: implement xdg-dbus-proxy wrapper
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
Diffstat (limited to 'dbus')
-rw-r--r--dbus/config.go59
-rw-r--r--dbus/run.go134
-rw-r--r--dbus/setup.go73
3 files changed, 266 insertions, 0 deletions
diff --git a/dbus/config.go b/dbus/config.go
new file mode 100644
index 00000000..9173d78d
--- /dev/null
+++ b/dbus/config.go
@@ -0,0 +1,59 @@
+package dbus
+
+type Config struct {
+ See []string `json:"see"`
+ Talk []string `json:"talk"`
+ Own []string `json:"own"`
+
+ Log bool `json:"log,omitempty"`
+ Filter bool `json:"filter"`
+}
+
+func (c *Config) Args(address, path string) (args []string) {
+ argc := 2 + len(c.See) + len(c.Talk) + len(c.Own)
+ if c.Log {
+ argc++
+ }
+ if c.Filter {
+ argc++
+ }
+
+ args = make([]string, 0, argc)
+ args = append(args, address, path)
+ for _, name := range c.See {
+ args = append(args, "--see="+name)
+ }
+ for _, name := range c.Talk {
+ args = append(args, "--talk="+name)
+ }
+ for _, name := range c.Own {
+ args = append(args, "--own="+name)
+ }
+ if c.Log {
+ args = append(args, "--log")
+ }
+ if c.Filter {
+ args = append(args, "--filter")
+ }
+
+ return
+}
+
+// NewConfig returns a reference to a Config struct with optional defaults.
+// If id is an empty string own defaults are omitted.
+func NewConfig(id string, defaults, mpris bool) (c *Config) {
+ c = &Config{Filter: true}
+
+ if defaults {
+ c.Talk = []string{"org.freedesktop.DBus", "org.freedesktop.portal.*", "org.freedesktop.Notifications"}
+
+ if id != "" {
+ c.Own = []string{id}
+ if mpris {
+ c.Own = append(c.Own, "org.mpris.MediaPlayer2."+id)
+ }
+ }
+ }
+
+ return
+}
diff --git a/dbus/run.go b/dbus/run.go
new file mode 100644
index 00000000..315b31de
--- /dev/null
+++ b/dbus/run.go
@@ -0,0 +1,134 @@
+package dbus
+
+import (
+ "errors"
+ "os"
+ "os/exec"
+)
+
+// Start launches the D-Bus proxy and sets up the Wait method.
+// ready should be buffered and should only be received from once.
+func (p *Proxy) Start(ready *chan bool) error {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+
+ if p.seal == nil {
+ return errors.New("proxy not sealed")
+ }
+
+ // acquire pipes
+ if pr, pw, err := os.Pipe(); err != nil {
+ return err
+ } else {
+ p.statP[0], p.statP[1] = pr, pw
+ }
+ if pr, pw, err := os.Pipe(); err != nil {
+ return err
+ } else {
+ p.argsP[0], p.argsP[1] = pr, pw
+ }
+
+ p.cmd = exec.Command(p.path,
+ // ExtraFiles: If non-nil, entry i becomes file descriptor 3+i.
+ "--fd=3",
+ "--args=4",
+ )
+ p.cmd.Env = []string{}
+ p.cmd.ExtraFiles = []*os.File{p.statP[1], p.argsP[0]}
+ p.cmd.Stdout = os.Stdout
+ p.cmd.Stderr = os.Stderr
+ if err := p.cmd.Start(); err != nil {
+ return err
+ }
+
+ statsP, argsP := p.statP[0], p.argsP[1]
+
+ if _, err := argsP.Write([]byte(*p.seal)); err != nil {
+ if err1 := p.cmd.Process.Kill(); err1 != nil {
+ panic(err1)
+ }
+ return err
+ } else {
+ if err = argsP.Close(); err != nil {
+ if err1 := p.cmd.Process.Kill(); err1 != nil {
+ panic(err1)
+ }
+ return err
+ }
+ }
+
+ wait := make(chan error)
+ go func() {
+ // live out the lifespan of the process
+ wait <- p.cmd.Wait()
+ }()
+
+ read := make(chan error)
+ go func() {
+ n, err := statsP.Read(make([]byte, 1))
+ switch n {
+ case -1:
+ if err1 := p.cmd.Process.Kill(); err1 != nil {
+ panic(err1)
+ }
+ read <- err
+ case 0:
+ read <- err
+ case 1:
+ *ready <- true
+ read <- nil
+ default:
+ panic("unreachable") // unexpected read count
+ }
+ }()
+
+ p.wait = &wait
+ p.read = &read
+ p.ready = ready
+
+ return nil
+}
+
+// Wait waits for xdg-dbus-proxy to exit or fault.
+func (p *Proxy) Wait() error {
+ p.lock.RLock()
+ defer p.lock.RUnlock()
+
+ if p.wait == nil || p.read == nil {
+ return errors.New("proxy not running")
+ }
+
+ defer func() {
+ if err1 := p.statP[0].Close(); err1 != nil && !errors.Is(err1, os.ErrClosed) {
+ panic(err1)
+ }
+ if err1 := p.statP[1].Close(); err1 != nil && !errors.Is(err1, os.ErrClosed) {
+ panic(err1)
+ }
+
+ if err1 := p.argsP[0].Close(); err1 != nil && !errors.Is(err1, os.ErrClosed) {
+ panic(err1)
+ }
+ if err1 := p.argsP[1].Close(); err1 != nil && !errors.Is(err1, os.ErrClosed) {
+ panic(err1)
+ }
+
+ }()
+
+ select {
+ case err := <-*p.wait:
+ *p.ready <- false
+ return err
+ case err := <-*p.read:
+ if err != nil {
+ *p.ready <- false
+ return err
+ }
+ return <-*p.wait
+ }
+}
+
+// Close closes the status file descriptor passed to xdg-dbus-proxy, causing it to stop.
+func (p *Proxy) Close() error {
+ return p.statP[0].Close()
+}
diff --git a/dbus/setup.go b/dbus/setup.go
new file mode 100644
index 00000000..8fb7b1b5
--- /dev/null
+++ b/dbus/setup.go
@@ -0,0 +1,73 @@
+package dbus
+
+import (
+ "errors"
+ "os"
+ "os/exec"
+ "strings"
+ "sync"
+)
+
+// Proxy holds references to a xdg-dbus-proxy process, and should never be copied.
+// Once sealed, configuration changes will no longer be possible and attempting to do so will result in a panic.
+type Proxy struct {
+ cmd *exec.Cmd
+
+ statP [2]*os.File
+ argsP [2]*os.File
+
+ address [2]string
+ path string
+
+ wait *chan error
+ read *chan error
+ ready *chan bool
+
+ seal *string
+ lock sync.RWMutex
+}
+
+func (p *Proxy) String() string {
+ if p.cmd != nil {
+ return p.cmd.String()
+ }
+
+ if p.seal != nil {
+ return *p.seal
+ }
+
+ return "(unsealed dbus proxy)"
+}
+
+// Seal seals the Proxy instance.
+func (p *Proxy) Seal(c *Config) error {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+
+ if p.seal != nil {
+ panic("dbus proxy sealed twice")
+ }
+ args := c.Args(p.address[0], p.address[1])
+
+ seal := strings.Builder{}
+ for _, arg := range args {
+ // reject argument strings containing null
+ for _, b := range arg {
+ if b == '\x00' {
+ return errors.New("argument contains null")
+ }
+ }
+
+ // write null terminated argument
+ seal.WriteString(arg)
+ seal.WriteByte('\x00')
+ }
+ v := seal.String()
+ p.seal = &v
+ return nil
+}
+
+// New returns a reference to a new unsealed Proxy.
+func New(binPath, address, path string) *Proxy {
+ return &Proxy{path: binPath, address: [2]string{address, path}}
+}