Help us shape the future of Go! Take the 2026 Go Developer Survey

zerobus

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 16 Imported by: 0

README

zerobus

A small D-Bus client for Go that does not allocate once it is running.

go get github.com/bnema/zerobus   # Go 1.27 or newer
  • Zero allocations per message. After the first few messages warm up its buffers, a call, a reply or a signal costs no heap allocation.
  • Zero-copy reads. Strings and byte arrays point straight into the receive buffer.
  • Standard library only.
  • Small. It connects over a Unix socket, authenticates with EXTERNAL, calls methods, emits signals, answers calls, and adds match rules. Nothing else.

Status: v0.x. The API may change between minor versions.

Call a method

c, err := zerobus.SessionBus()
if err != nil {
    log.Fatal(err)
}
defer c.Close()

e := c.NewCall("org.freedesktop.DBus", "/org/freedesktop/DBus",
    "org.freedesktop.DBus", "GetNameOwner", "s")
e.Str("org.freedesktop.Notifications")

m, err := c.Call()
if err != nil {
    log.Fatal(err) // a D-Bus error reply is a *zerobus.Error
}
owner := m.Body().Str()

Build a message with NewCall, NewSignal, NewReply or NewError, write its body in the order of its signature, then send it with Call (wait for the reply) or Send (do not wait). Before sending, zerobus reads the whole body back with its signature and checks names, paths and strings, so an invalid message returns ErrInvalid instead of getting the connection dropped by the bus.

To bound connection setup, use DialContext(ctx, address), SessionBusContext(ctx), or SystemBusContext(ctx). Cancellation and deadlines cover socket connection, authentication, and Hello; they do not affect the returned connection. Context-caused failures wrap context.Canceled or context.DeadlineExceeded for errors.Is. The original functions use context.Background() and have no setup timeout.

Read values

Read values in the order of the signature. Errors are sticky: check Err() once at the end.

r := m.Body()             // signature "a{sv}"
end := r.Array('{')
for r.More(end) {
    r.Struct()
    key := r.Str()
    switch sig := r.Variant(); sig {
    case "s":
        fmt.Println(key, r.Str())
    default:
        r.Skip(sig)       // skip values you do not need
    }
}
if err := r.Err(); err != nil {
    return err
}

A received message is valid only until the next ReadMessage or Call. The strings and byte slices it returns point into the receive buffer and are overwritten by the next message. Copy what you keep:

name := strings.Clone(r.Str())
icon := bytes.Clone(r.ByteArray())

Receive signals

c.AddMatch("type='signal',interface='org.kde.StatusNotifierItem'")
for {
    m, err := c.ReadMessage()
    if err != nil {
        return err
    }
    if m.Type == zerobus.TypeSignal {
        fmt.Println(m.Sender, m.Member)
    }
}

Call reads messages until its reply arrives. Set Conn.Handle to receive the messages that arrive meanwhile; they are dropped otherwise. Handle may send (answer a call, emit a signal), but not wait: Call and ReadMessage return ErrNested inside it.

Answer calls

c.RequestName("org.example.Echo", zerobus.NameFlagDoNotQueue)
for {
    m, err := c.ReadMessage()
    if err != nil {
        return err
    }
    if m.Type == zerobus.TypeMethodCall && m.Member == "Echo" {
        c.NewReply(m, "s").Str(m.Body().Str())
        c.Send()
    }
}

Concurrency

A Conn is not safe for concurrent use. Use it from one goroutine, usually an event loop around ReadMessage. Close may be called from any goroutine: a blocked ReadMessage then returns ErrClosed.

Not supported

  • Transports other than unix:path and unix:abstract (no TCP, no launchd, no autolaunch).
  • File-descriptor passing on platforms other than Linux.
  • Authentication other than EXTERNAL.
  • Reflection-based marshalling, introspection and code generation.

Example

examples/trayitems lists the system tray items on the session bus and prints their changes.

License

MIT

Unix file descriptors (Linux)

c.UnixFDs() reports whether the bus agreed to descriptor passing. Encode an h with e.UnixFD(int(file.Fd())); the caller retains ownership and must keep the descriptor open until Send or Call returns. Sending descriptors without negotiation fails before anything is written.

For a received h, use file, err := m.TakeFD(m.Body().UnixFD()) and close the returned file when finished. m.NumFDs() reports the attached descriptor count. Taking an index twice is an error. Untaken descriptors are closed at the start of the next ReadMessage or Call, or when the connection is closed.

Documentation

Overview

Package zerobus is a small D-Bus client that does not allocate in steady state. It speaks the D-Bus wire protocol over a Unix socket and decodes messages in place, so strings and arrays point into the receive buffer and stay valid only until the next read.

DialContext, SessionBusContext and SystemBusContext bound socket connection, authentication and Hello with a context. Cancellation does not affect a successfully returned Conn. Dial, SessionBus and SystemBus use a background context and impose no setup timeout.

Clients and servers

A Conn has one encoder and one receive buffer. A client may block in Call, which sends a method call and waits for its reply, and may set Handle to see the messages that arrive meanwhile.

A server loop uses only Send and ReadMessage, never Call or Handle. It answers a method call with NewReply or NewError, emits NewSignal or the directed NewSignalTo, and sends its own requests with NewMethodCall, which returns the serial. The reply comes back later through ReadMessage as a message whose ReplySerial is that serial. Decode a message completely before the next Send is built and the next read: both reuse the same buffers.

Unix file descriptors

On Linux, UnixFDs reports negotiated descriptor support. Encoder.UnixFD borrows an outgoing descriptor until Send or Call returns. Message.TakeFD takes ownership of a received descriptor indexed by Reader.UnixFD; the caller must close it. Untaken descriptors are closed on the next read, Call or Close. Other platforms do not negotiate descriptor support.

Polling

A loop that also waits for other descriptors gets the socket from SyscallConn and waits for it to be readable. ReadMessage reads all the bytes available, so call it while Buffered reports true before waiting again. After the descriptor turns readable, ReadMessage may still block for the rest of a message that has only partly arrived.

Index

Constants

View Source
const (
	NamePrimaryOwner = 1
	NameInQueue      = 2
	NameExists       = 3
	NameAlreadyOwner = 4
)

Reply codes of RequestName.

View Source
const (
	NameFlagAllowReplacement = 0x1
	NameFlagReplaceExisting  = 0x2
	NameFlagDoNotQueue       = 0x4
)

Flags of RequestName.

Variables

View Source
var (
	// ErrClosed reports a read or send on a closed or broken connection.
	ErrClosed = errors.New("zerobus: connection closed")
	// ErrNested reports a Call or ReadMessage made from inside Conn.Handle.
	ErrNested = errors.New("zerobus: Call or ReadMessage inside Handle")
)
View Source
var (
	// ErrMalformed reports bytes that do not follow the D-Bus wire format.
	ErrMalformed = errors.New("zerobus: malformed message")
	// ErrTooLarge reports a message or array over the D-Bus size limits.
	ErrTooLarge = errors.New("zerobus: message too large")
	// ErrInvalid reports an outgoing value D-Bus does not allow, such as a
	// string with a NUL byte or a body that does not match its signature.
	ErrInvalid = errors.New("zerobus: invalid value")
)

Functions

This section is empty.

Types

type ArrayMark

type ArrayMark struct {
	// contains filtered or unexported fields
}

ArrayMark is an array being written; pass it to EndArray.

type Conn

type Conn struct {

	// Handle receives the messages that arrive while Call waits for its
	// reply. It may be nil; those messages are then dropped.
	//
	// Handle may answer or emit messages with NewReply, NewError,
	// NewSignal and Send. It may not wait: Call and ReadMessage return
	// ErrNested inside it.
	Handle func(*Message)
	// contains filtered or unexported fields
}

Conn is a connection to a message bus.

A Conn is not safe for concurrent use: read and send from one goroutine, usually an event loop around ReadMessage. Close may be called from any goroutine to stop a blocked read.

func Dial

func Dial(address string) (*Conn, error)

Dial connects to a bus address such as "unix:path=/run/user/1000/bus", authenticates, and registers with the bus. Only unix:path and unix:abstract addresses are supported; with several addresses separated by ';', the first that connects is used.

func DialContext added in v0.4.0

func DialContext(ctx context.Context, address string) (*Conn, error)

DialContext is Dial with cancellation covering connect, authentication (including descriptor negotiation), and Hello. The context does not affect the returned Conn.

func SessionBus

func SessionBus() (*Conn, error)

SessionBus connects to the session bus named by DBUS_SESSION_BUS_ADDRESS, or $XDG_RUNTIME_DIR/bus when it is not set.

func SessionBusContext added in v0.4.0

func SessionBusContext(ctx context.Context) (*Conn, error)

SessionBusContext is SessionBus with cancellation covering connect, auth and Hello.

func SystemBus

func SystemBus() (*Conn, error)

SystemBus connects to the system bus named by DBUS_SYSTEM_BUS_ADDRESS, or the standard socket when it is not set.

func SystemBusContext added in v0.4.0

func SystemBusContext(ctx context.Context) (*Conn, error)

SystemBusContext is SystemBus with cancellation covering connect, auth and Hello.

func (*Conn) AddMatch

func (c *Conn) AddMatch(rule string) error

AddMatch asks the bus to send this connection the messages that match rule, such as "type='signal',interface='org.kde.StatusNotifierItem'".

func (*Conn) Buffered added in v0.2.0

func (c *Conn) Buffered() bool

Buffered reports whether ReadMessage can return without waiting for the socket: a complete message, or a sticky error, is already in the receive buffer. A poll loop must call ReadMessage until Buffered is false before it waits on the descriptor again, because ReadMessage reads everything the socket has and a later poll would not see those bytes.

func (*Conn) Call

func (c *Conn) Call() (*Message, error)

Call sends the method call being built and waits for its reply. Messages that arrive meanwhile go to Handle. An error reply returns an *Error. A call flagged FlagNoReplyExpected gets no reply: send it with Send.

The reply is valid until the next read, like ReadMessage's.

func (*Conn) Close

func (c *Conn) Close() error

Close closes the connection. A blocked ReadMessage returns ErrClosed.

func (*Conn) NewCall

func (c *Conn) NewCall(dest, path, iface, member, sig string) *Encoder

NewCall starts a method call and returns the encoder for its body, which must match sig. Send it with Send or Call. Empty dest and iface are left out of the header.

The connection has one encoder: send a message before starting another.

func (*Conn) NewError

func (c *Conn) NewError(m *Message, name, sig string) *Encoder

NewError starts an error reply to the method call m. Write a message string as the body when sig is "s".

func (*Conn) NewMethodCall added in v0.2.0

func (c *Conn) NewMethodCall(dest, path, iface, member, sig string) (*Encoder, uint32)

NewMethodCall starts a method call like NewCall and also returns its serial. A loop that only uses Send and ReadMessage keeps the serial to recognize the reply later, as Message.ReplySerial, without blocking in Call. The serial is the one Send returns for this message.

func (*Conn) NewReply

func (c *Conn) NewReply(m *Message, sig string) *Encoder

NewReply starts the reply to the method call m and returns the encoder for its body.

func (*Conn) NewSignal

func (c *Conn) NewSignal(path, iface, member, sig string) *Encoder

NewSignal starts a signal and returns the encoder for its body.

func (*Conn) NewSignalTo added in v0.2.0

func (c *Conn) NewSignalTo(dest, path, iface, member, sig string) *Encoder

NewSignalTo starts a signal addressed to the single peer dest, a unique name such as ":1.42" or a well-known name, and returns the encoder for its body. Only dest receives it, whatever match rules other peers have. An empty dest makes Send fail: use NewSignal to broadcast.

func (*Conn) ReadMessage

func (c *Conn) ReadMessage() (*Message, error)

ReadMessage blocks until the next message arrives. The message and its strings are valid until the next call to ReadMessage or Call.

func (*Conn) RemoveMatch

func (c *Conn) RemoveMatch(rule string) error

RemoveMatch removes a rule added by AddMatch.

func (*Conn) RequestName

func (c *Conn) RequestName(name string, flags uint32) (uint32, error)

RequestName asks the bus for a well-known name and returns its reply code, such as NamePrimaryOwner.

func (*Conn) Send

func (c *Conn) Send() (uint32, error)

Send sends the message built since the last NewCall, NewSignal, NewReply or NewError and returns its serial.

func (*Conn) SyscallConn added in v0.2.0

func (c *Conn) SyscallConn() (syscall.RawConn, error)

SyscallConn returns the raw socket of the connection, for a poll loop that waits for it to become readable together with other file descriptors. Only wait on the descriptor: read, write and close through the Conn.

Bytes already read from the socket are not visible to poll: check Buffered before waiting. After the descriptor turns readable, ReadMessage may still block until the rest of a partially received message arrives.

func (*Conn) UniqueName

func (c *Conn) UniqueName() string

UniqueName returns the name the bus gave this connection, such as ":1.42".

func (*Conn) UnixFDs added in v0.3.0

func (c *Conn) UnixFDs() bool

UnixFDs reports whether Linux descriptor passing was negotiated.

type Encoder

type Encoder struct {
	// contains filtered or unexported fields
}

Encoder appends D-Bus values to an outgoing message. Get one from Conn.NewCall, Conn.Signal, Conn.Reply or Conn.Error, write the body values in the order of the message signature, then call Conn.Send.

Errors are sticky and reported by Conn.Send.

func (*Encoder) BeginArray

func (e *Encoder) BeginArray(elem byte) ArrayMark

BeginArray starts an ARRAY whose element type starts with the signature byte elem. Write the elements, then call EndArray:

a := e.BeginArray('s')
e.Str("one")
e.Str("two")
e.EndArray(a)

func (*Encoder) Bool

func (e *Encoder) Bool(v bool)

Bool writes a BOOLEAN (b).

func (*Encoder) Byte

func (e *Encoder) Byte(v byte)

Byte writes a BYTE (y).

func (*Encoder) ByteArray

func (e *Encoder) ByteArray(b []byte)

ByteArray writes an ARRAY of BYTE (ay).

func (*Encoder) EndArray

func (e *Encoder) EndArray(a ArrayMark)

EndArray finishes the array started by BeginArray.

func (*Encoder) Float64

func (e *Encoder) Float64(v float64)

Float64 writes a DOUBLE (d).

func (*Encoder) Int16

func (e *Encoder) Int16(v int16)

Int16 writes an INT16 (n).

func (*Encoder) Int32

func (e *Encoder) Int32(v int32)

Int32 writes an INT32 (i).

func (*Encoder) Int64

func (e *Encoder) Int64(v int64)

Int64 writes an INT64 (x).

func (*Encoder) ObjectPath

func (e *Encoder) ObjectPath(p string)

ObjectPath writes an OBJECT_PATH (o).

func (*Encoder) SetFlags

func (e *Encoder) SetFlags(f Flags)

SetFlags sets the flags of the message being built.

func (*Encoder) Signature

func (e *Encoder) Signature(sig string)

Signature writes a SIGNATURE (g).

func (*Encoder) Str

func (e *Encoder) Str(s string)

Str writes a STRING (s).

func (*Encoder) Struct

func (e *Encoder) Struct()

Struct aligns the encoder to the start of a STRUCT or DICT_ENTRY. Call it before writing the first field.

func (*Encoder) Uint16

func (e *Encoder) Uint16(v uint16)

Uint16 writes a UINT16 (q).

func (*Encoder) Uint32

func (e *Encoder) Uint32(v uint32)

Uint32 writes a UINT32 (u).

func (*Encoder) Uint64

func (e *Encoder) Uint64(v uint64)

Uint64 writes a UINT64 (t).

func (*Encoder) UnixFD added in v0.3.0

func (e *Encoder) UnixFD(fd int)

UnixFD appends a UNIX_FD index and attaches fd to the outgoing message. The caller retains ownership and must keep fd valid until Send or Call returns. Descriptor passing requires successful Linux Unix-FD negotiation.

func (*Encoder) Variant

func (e *Encoder) Variant(sig string)

Variant writes the signature of a VARIANT (v). Write its value next.

type Error

type Error struct {
	Name    string // such as org.freedesktop.DBus.Error.ServiceUnknown
	Message string // the first string of the body, if any
}

Error is a D-Bus error reply.

func (*Error) Error

func (e *Error) Error() string

type Flags

type Flags byte

Flags are the header flags of a message.

const (
	// FlagNoReplyExpected tells the receiver not to answer a method call.
	FlagNoReplyExpected Flags = 0x1
	// FlagNoAutoStart tells the bus not to start the destination service.
	FlagNoAutoStart Flags = 0x2
	// FlagAllowInteractiveAuth allows the receiver to prompt the user.
	FlagAllowInteractiveAuth Flags = 0x4
)

Message flags.

type Message

type Message struct {
	Type        Type
	Flags       Flags
	Serial      uint32
	ReplySerial uint32
	Path        string
	Interface   string
	Member      string
	ErrorName   string
	Destination string
	Sender      string
	Signature   string // the signature of the body
	// contains filtered or unexported fields
}

Message is a received message.

Its strings point into the connection's receive buffer: they and the message itself are valid only until the next read on the connection.

func (*Message) Body

func (m *Message) Body() *Reader

Body returns a reader at the start of the message body. Each call starts over from the beginning.

func (*Message) NumFDs added in v0.3.0

func (m *Message) NumFDs() int

NumFDs returns the number of descriptors attached to this message.

func (*Message) TakeFD added in v0.3.0

func (m *Message) TakeFD(i uint32) (*os.File, error)

TakeFD transfers descriptor i to the caller, who must close the returned file. Untaken descriptors are closed on the next ReadMessage or Call, or Close. Like Body, this method is valid only until the next read.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader decodes D-Bus values in place.

Strings and byte slices it returns point into the message buffer: they are valid only until the next Conn.ReadMessage. Copy them (strings.Clone, bytes.Clone) to keep them longer.

Read values in the order of the message signature. Errors are sticky: after the first one, every read returns a zero value and Err reports it.

func (*Reader) Array

func (r *Reader) Array(elem byte) int

Array reads the header of an ARRAY whose element type starts with the signature byte elem, and returns the offset where the array ends. Read the elements while More returns true:

end := r.Array('s')
for r.More(end) {
	name := r.Str()
}

func (*Reader) Bool

func (r *Reader) Bool() bool

Bool reads a BOOLEAN (b).

func (*Reader) Byte

func (r *Reader) Byte() byte

Byte reads a BYTE (y).

func (*Reader) ByteArray

func (r *Reader) ByteArray() []byte

ByteArray reads an ARRAY of BYTE (ay). The result points into the message buffer.

func (*Reader) Done

func (r *Reader) Done() bool

Done reports whether every byte was read without error.

func (*Reader) Err

func (r *Reader) Err() error

Err returns the first error the reader met, or nil.

func (*Reader) Float64

func (r *Reader) Float64() float64

Float64 reads a DOUBLE (d).

func (*Reader) Int16

func (r *Reader) Int16() int16

Int16 reads an INT16 (n).

func (*Reader) Int32

func (r *Reader) Int32() int32

Int32 reads an INT32 (i).

func (*Reader) Int64

func (r *Reader) Int64() int64

Int64 reads an INT64 (x).

func (*Reader) More

func (r *Reader) More(end int) bool

More reports whether the array that ends at end has another element.

func (*Reader) ObjectPath

func (r *Reader) ObjectPath() string

ObjectPath reads an OBJECT_PATH (o). The result points into the message buffer.

func (*Reader) Signature

func (r *Reader) Signature() string

Signature reads a SIGNATURE (g). The result points into the message buffer.

func (*Reader) Skip

func (r *Reader) Skip(sig string)

Skip reads past values of the given signature. Arrays are skipped by their length, without reading their elements.

func (*Reader) SkipTo

func (r *Reader) SkipTo(end int)

SkipTo moves to end, the value returned by Array. Use it to leave an array before its last element.

func (*Reader) Str

func (r *Reader) Str() string

Str reads a STRING (s). The result points into the message buffer.

func (*Reader) Struct

func (r *Reader) Struct()

Struct aligns the reader to the start of a STRUCT or DICT_ENTRY. Call it before reading the first field.

func (*Reader) Uint16

func (r *Reader) Uint16() uint16

Uint16 reads a UINT16 (q).

func (*Reader) Uint32

func (r *Reader) Uint32() uint32

Uint32 reads a UINT32 (u).

func (*Reader) Uint64

func (r *Reader) Uint64() uint64

Uint64 reads a UINT64 (t).

func (*Reader) UnixFD

func (r *Reader) UnixFD() uint32

UnixFD reads a UNIX_FD (h): an index into the fds sent with the message. Pass this index to Message.TakeFD to take ownership.

func (*Reader) Variant

func (r *Reader) Variant() string

Variant reads the signature of a VARIANT (v) and returns it. Read the value that follows with the matching method, or skip it with Skip.

type Type

type Type byte

Type is the kind of a message.

const (
	TypeMethodCall   Type = 1
	TypeMethodReturn Type = 2
	TypeError        Type = 3
	TypeSignal       Type = 4
)

Message types.

Directories

Path Synopsis
examples
trayitems command
Command trayitems lists the StatusNotifierItems registered on the session bus, then prints their changes as they happen.
Command trayitems lists the StatusNotifierItems registered on the session bus, then prints their changes as they happen.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL