Goose is a library written in Zig that provides a native implementation of the D-Bus protocol. It allows you to connect to the D-Bus system or session bus, send and receive messages, handle signals, and introspect D-Bus objects.
- Pure Zig: No C dependencies.
- Bus Connection: Support for Session, System, and Accessibility buses.
- Method Calls: Call methods on remote objects easily using Proxies.
- Signals: Subscribe to and handle D-Bus signals.
- Object Export: Export local Zig objects to the bus with minimal boilerplate.
- Introspection: Parse and generate D-Bus introspection XML.
- Code Generation: Includes a tool to generate Zig proxy code from introspection XML.
Goose is designed to be a lightweight, pure-Zig alternative to libdbus or GDBus.
It handles the entire D-Bus stack, from socket communication to type marshaling.
The Connection struct manages the Unix Domain Socket and implements the SASL1 EXTERNAL
authentication handshake. It buffers stream data and runs a blocking message loop
that routes incoming signals and method replies to the appropriate handlers.
Goose uses compile-time reflection to map Zig types to D-Bus signatures.
Integers, structs, strings, and variants are automatically marshaled into the
D-Bus wire format with correct alignment and endianness. Wrappers like
GStr and GPath ensure strict type safety for D-Bus specific concepts.
Standard Zig structs can be exported as D-Bus objects. The library analyzes public
methods and fields to set up automatic routing for method calls. It also generates
introspection XML and exposes Property fields via the standard
org.freedesktop.DBus.Properties interface.
The Proxy abstraction simplifies client-side interactions by handling the
construction of method call messages and parsing of replies, hiding the
low-level details of the protocol.
You can add goose to your project using zig fetch:
zig fetch --save git+https://github.com/luxluth/goose.git#masterMore examples of client and server usage can be found in the src/tests/ directory.
const std = @import("std");
const goose = @import("goose");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Connect to the session bus
var conn = try goose.Connection.init(allocator, .Session);
defer conn.close();
// ... use the connection
}You can use the Proxy to interact with remote objects.
const proxy = goose.proxy;
const GStr = goose.core.value.GStr;
// Initialize a proxy for a specific object and interface
const p = proxy.Proxy.init(&conn, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus");
// Call a method (e.g., ListNames)
var result = try p.call("ListNames", .{});
defer result.deinit();
// Parse the result
// (Note: Implementation details for parsing specific complex return types may vary)Register a callback function to handle specific signals.
fn onSignal(_: ?*anyopaque, msg: goose.core.Message) void {
std.debug.print("Received signal!\n", .{});
}
// ... inside main
try conn.addMatch("type='signal',interface='dev.myinterface.test'");
try conn.registerSignalHandler("dev.myinterface.test", "MySignal", onSignal, null);Define a struct to represent your D-Bus interface and register it on the connection.
const std = @import("std");
const goose = @import("goose");
// Define the Interface
const MyInterface = struct {
conn: *goose.Connection,
// Properties (exposed via org.freedesktop.DBus.Properties)
Count: goose.Property(i32, .ReadWrite) = goose.property(i32, .ReadWrite, 0),
// Signals
Tick: goose.Signal(i32) = goose.signal("Tick", i32),
pub fn init(conn: *goose.Connection, _: void) @This() {
return .{ .conn = conn };
}
// Methods are automatically exported
pub fn Increment(self: *MyInterface) !i32 {
self.Count.value += 1;
// Trigger signal
try self.Tick.trigger(self.conn, self.Count.value);
return self.Count.value;
}
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var conn = try goose.Connection.init(allocator, .Session);
defer conn.close();
// Register Object: (Interface Type, Bus Name, Object Path)
const handle = try conn.registerObject(
MyInterface,
"com.example.MyService",
"/com/example/MyObject"
);
// Serve requests
try conn.waitOnHandle(handle);
}Goose includes helper tools for introspection and code generation.
To use these tools in your own project, you must explicitly install them as
artifacts in your build.zig:
const goose_dep = b.dependency("goose", .{
.target = target,
.optimize = optimize,
});
b.installArtifact(goose_dep.artifact("goose-introspection"));
b.installArtifact(goose_dep.artifact("goose-generate"));A tool to introspect a D-Bus object and output its XML definition.
./zig-out/bin/goose-introspection <destination> <path> <bus_type>Generate type-safe Zig bindings from D-Bus introspection XML.
./zig-out/bin/goose-generate <destination> <path> <bus_type>This project is licensed under the MIT License - see the LICENSE file for details.
Footnotes
-
SASL (Simple Authentication and Security Layer) is used here to implement the
EXTERNALmechanism, which leverages Unix domain socket credentials for authentication. ↩
