SkillAgentSearch skills...

Zwl

a libwayland implemented in zig

Install / Use

/learn @ItaloYt/Zwl
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

ZWL - Zig Wayland

Zig wayland is an implementation(not a binding) of the libwayland using zig 0.15.2, it includes the client, the server, the scanner and a few utils, but it only contains the core, that means that none of the interfaces(like WlDisplay, WlRegistry, etc.) are available, being user's responsability to generate them

It follows the wayland wire protocol and all of its structures and functions work in a similar way to the C version of the library

How do I use it?

The first step is to stablish a connection between the compositor and the client, on the libwayland-client you would need to connect a display using wldisplay_connect, on zwl it works in a similar manner, but everything related to this connection is called Client, because WlDisplay is related to the requests and events of the display.

const wc = @import("wayland-core").client;
const wu = @import("wayland-core").util;

pub fn main() !void {
    // You can use any allocator, but you'll need one
    var memory = std.heap.DebugAllocator(.{}).init;
    defer _ = memory.deinit();
    
    const allocator = memory.allocator();

    var client = try wc.Client.connect(allocator, null);
    defer client.disconnect();
}

Now you have a client that connects to the compositor, but it still doesn't have much yet, so the next step is to create a WlDisplay:

...
const wl = @import("wayland");

pub fn main() !void {
    ...

    var display = try wl.WlDisplay.init(client);
    defer display.deinit();
}

Note that you'll need the zig implementation of the wayland protocol, but how do you do this? Fortunately, there's a zwl-scanner, which receives as input the xml file describing the protocol and outputs the zig implementation of it. Here's an example on how to generate it: ~/myProject$ zwl-scanner client /usr/share/wayland/wayland.xml > wayland.zig then you create a module with the generated file and you should name every import that uses it as the name of the protocol(which, in that case, is wayland), because in a project where you'll need multiple generated protocols, they import them using the protocols name, for example, if a protocol xdg_decoration depends on the protocol xdg_shell, it will import it using @import("xdg_shell"), so you should name the import using the protocols name, or you can change the import path directly on the generated file. The scanner is also available through @import("wayland-core").scanner, and it has a scan function which can be called to generate the zig code. The scanner is also available as an artifact called zwl-scanner, which you can use to generate custom steps

Every generated interface has a setListener function even when an interface doesn't have any events. A few interfaces have requests like destroy or release which are marked as destructors(you can see details about requests and events on [https://wayland.app/protocols/]), these requests call deinit too, so it is an error to call deinit after a destroyer request.

Since the Client structure is a bit special, the roundtrip and roundtripQueues functions are a bit different, because this project contains only the core of the wayland, that means that none of its interfaces are available and that's why it must be generated, but since they aren't available and the roundtrip/roundtripQueues function needs it, it receives a Proxy as a parameter, and that proxy must be the display

Here's an example of an application that logs all the interfaces supported by the compositor:

const std = @import("std");
const wc = @import("wayland-core").client;
const wu = @import("wayland-core").util;
const wl = @import("wayland");

pub fn main() !void {
    var memory = std.heap.DebugAllocator(.{}).init;
    defer _ = memory.deinit();
    
    const allocator = memory.allocator();

    var client = try wc.Client.connect(allocator, null);
    defer client.disconnect();

    var display = try wl.WlDisplay.init(client);
    defer display.deinit();

    var registry = try display.getRegistry();
    defer registry.deinit();

    registry.setListener(.{ .global = global }, null);

    try client.roundtrip(&display.proxy);
}

/// The `user` parameter is the same pointer that is passed on the second argument of `setListener`
/// The `registry` parameter is the pointer to the registry which this event is being called
/// The remaining arguments are event-dependent, and only the first two are similar across all events
fn global(user: ?*anyopaque, registry: *wl.WlRegistry, name: u32, interface: []const u8, version: u32) void {
    _ = .{ user, registry, name };

    std.log.info("interface={s}, version={}", .{ interface, version });
}

What's missing on ZWL?

Currently, it may have a lot of bugs on its multithreading logic and the wayland-server is not implemented yet and, therefore, the zwl-scanner doesn't support the server side too

Related Skills

View on GitHub
GitHub Stars8
CategoryDevelopment
Updated1mo ago
Forks0

Languages

Zig

Security Score

85/100

Audited on Feb 8, 2026

No findings