No description
Find a file
2025-08-24 21:52:26 -06:00
docs feat: add black_square example, make gradient to 02 2024-10-05 12:02:36 -06:00
examples refactor wire.Connection to integrate std.Io.Writer 2025-08-22 23:05:05 -06:00
src expose the scanner as a resusable module 2025-08-24 21:52:26 -06:00
.gitignore spin shimizu out of seizer, add 00_list_globals example 2024-10-03 01:17:33 -06:00
build.zig expose the scanner as a resusable module 2025-08-24 21:52:26 -06:00
build.zig.zon update to Zig 0.15.1 2025-08-22 23:04:58 -06:00
LICENSE spin shimizu out of seizer, add 00_list_globals example 2024-10-03 01:17:33 -06:00
README.md link to shimizu-scanner's help file 2024-10-16 14:01:13 -06:00

shimizu: The Wayland protocol, in Zig

shimizu is a library for interfacing with Wayland at a low level.

This repository contains:

  • wire: a Zig module that defines types for Wayland's wire format and functions to serialize and deserialize messages.
  • shimizu-scanner: a command to take Wayland XML protocol descriptions and turn them into Zig types.
  • core: The Wayland core protocol as a Zig module, generated by shimizu-scanner
  • shimizu: A Zig module that provides higher level Connection and Proxy types.
  • wayland-protocols: A Zig module containing stable protocols from the wayland-protocols project project. It targets the highest version of each protocol that is supported across all the compositors mentioned on wayland.app

One notable missing feature is a way to parse the keymap format that is sent by the compositor. If you want more than basic keyboard input, or text input, you will need a library to handle parsing the xkb_v1 format. libxkbcommon is the library that handles this, though I have a undocumented, partially working xkb parser, if you want a pure Zig solution.

Getting Started

Zig Version

First, decide which version of Zig you are using. For 0.13, use the dev branch. For nightly zig builds, use the zig-master branch.

You can use the following commands to add shimizu as a dependency, but you should replace dev and zig-master with specific commits, or else your build will break the next time that branch gets a new commit.

# For zig 0.13
$ zig fetch --save "https://git.sr.ht/~geemili/shimizu/archive/dev.tar.gz"
# For zig 0.14-dev
$ zig fetch --save "https://git.sr.ht/~geemili/shimizu/archive/zig-master.tar.gz"

Examples

Check out the "list globals" example. This shows one of the simplest apps you can make for Wayland, not showing any graphics, simply listing the protocols that the compositor implements.

$ river -version # I'm running the river compositor
0.3.2
$ zig build run-00_list_globals
name	version	interface
1	6	wl_compositor
2	1	wp_security_context_manager_v1
3	1	wl_shm
... # snip
41	1	ext_session_lock_manager_v1
42	4	wl_output

Example 01, "black square", shows the bare minimum necessary to get something displayed to the screen, in this case a black square. It doesn't respond to ping or close events, so you'll need to press Ctrl+C to close it.

$ zig build run-01_black_square
Press Ctrl+C to close 01_black_square

A small black square on top the terminal used to start it

Example 02, "gradient", is more interesting. This example creates a window and displays a scrolling gradient. The window is a fixed size and doesn't respond to resize requests, though it does respond to close events.

$ zig build run-02_gradient

A window with a semi-colourful repeating gradient

At the moment there are no more examples, though more are planned.

Using the Scanner

There are two methods you can use here, (1) depend on the shimizu-scanner artifact and directly pass arguments to it, or (2) import shimizu into your build.zig and use the generateProtocolZig function.

Using the shimizu-scanner CLI directly

The command line interface is documented in its help output. Use std.Build.addRunArtifact() to have the Zig build system handle invoking the shimizu-scanner executable. Note that shimizu-scanner requires you to specify protocol files you want to generate, and protocol files that have already been generated, so that it can determine which protocol an interface comes from.

Here is an example of generating the unstable protocol xdg-decoration:

$ zig fetch --save "https://git.sr.ht/~geemili/shimizu/archive/dev.tar.gz"
$ zig fetch --save="wayland" https://gitlab.freedesktop.org/wayland/wayland/-/archive/1.23.1/wayland-1.23.1.tar.gz
$ zig fetch --save="wayland-protocols" https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.38/wayland-protocols-1.38.tar.gz
const std = @import("std");

pub fn build(b: *std.Build) !void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const wayland_dep = b.dependency("wayland", .{});
    const wayland_protocols_dep = b.dependency("wayland-protocols", .{});

    const shimizu_dep = b.dependency("shimizu", .{
        .target = target,
        .optimize = optimize,
    });

    const generate_wayland_unstable_zig_cmd = b.addRunArtifact(shimizu_dep.artifact("shimizu-scanner"));
    generate_wayland_unstable_zig_cmd.addFileArg(wayland_protocols_dep.path("unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"));
    generate_wayland_unstable_zig_cmd.addArgs(&.{ "--interface-version", "zxdg_decoration_manager_v1", "1" });

    generate_wayland_unstable_zig_cmd.addArg("--import");
    generate_wayland_unstable_zig_cmd.addFileArg(wayland_dep.path("protocol/wayland.xml"));
    generate_wayland_unstable_zig_cmd.addArg("@import(\"core\")");

    generate_wayland_unstable_zig_cmd.addArg("--import");
    generate_wayland_unstable_zig_cmd.addFileArg(wayland_protocols_dep.path("stable/xdg-shell/xdg-shell.xml"));
    generate_wayland_unstable_zig_cmd.addArg("@import(\"wayland-protocols\").xdg_shell");

    generate_wayland_unstable_zig_cmd.addArg("--output");
    const wayland_unstable_dir = generate_wayland_unstable_zig_cmd.addOutputDirectoryArg("wayland-unstable");

    const wayland_unstable_module = b.addModule("wayland-unstable", .{
        .root_source_file = wayland_unstable_dir.path(b, "root.zig"),
        .target = target,
        .optimize = optimize,
        .imports = &.{
            .{ .name = "wire", .module = shimizu_dep.module("wire") },
            .{ .name = "core", .module = shimizu_dep.module("core") },
            .{ .name = "wayland-protocols", .module = shimizu_dep.module("wayland-protocols") },
        },
    });
}

Using the build.zig function generateProtocolZig

Here is an example of doing the same thing as above, except using generateProtocolZig:

$ zig fetch --save "https://git.sr.ht/~geemili/shimizu/archive/dev.tar.gz"
$ zig fetch --save="wayland" https://gitlab.freedesktop.org/wayland/wayland/-/archive/1.23.1/wayland-1.23.1.tar.gz
$ zig fetch --save="wayland-protocols" https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.38/wayland-protocols-1.38.tar.gz
const std = @import("std");
const shimizu_build = @import("shimizu");

pub fn build(b: *std.Build) !void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const wayland_dep = b.dependency("wayland", .{});
    const wayland_protocols_dep = b.dependency("wayland-protocols", .{});

    const shimizu_dep = b.dependencyFromBuildZig(shimizu_build, .{
        .target = target,
        .optimize = optimize,
    });

    const wayland_unstable_dir = shimizu_build.generateProtocolZig(shimizu_dep.builder, shimizu_dep.artifact("shimizu-scanner"), .{
        .output_directory_name = "wayland-unstable",
        .source_files = &.{
            wayland_protocols_dep.path("unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"),
        },
        .interface_versions = &.{
            .{ .interface = "zxdg_decoration_manager_v1", .version = 1 },
        },
        .imports = &.{
            .{ .file = wayland_dep.path("protocol/wayland.xml"), .import_string = "@import(\"core\")" },
            .{ .file = wayland_protocols_dep.path("stable/xdg-shell/xdg-shell.xml"), .import_string = "@import(\"wayland-protocols\").xdg_shell" },
        },
    });

    const wayland_unstable_module = b.addModule("wayland-unstable", .{
        .root_source_file = wayland_unstable_dir.path(b, "root.zig"),
        .target = target,
        .optimize = optimize,
        .imports = &.{
            .{ .name = "wire", .module = shimizu_dep.module("wire") },
            .{ .name = "core", .module = shimizu_dep.module("core") },
            .{ .name = "wayland-protocols", .module = shimizu_dep.module("wayland-protocols") },
        },
    });
}

Answers to Questions that could potentially have been Frequently Asked

Why is it called "shimizu"?

This project was spun off of the seizer library. seizer is a reference to the "Seizer Beam" from the game Zero Wing, developed by Toaplan. Toaplan's headquarters was in Shimizu, Suginami, Tokyo (according to Wikipedia).

This is a nod to how the Wayland protocol (and the related project Weston) got it's name. I also considered naming it after some other Massachusetts town, but none of the names really stuck out.