# Onto the Screen

> The same buffer, handed to X11 through translate-c, and what changes on Wayland.

Writing a file is a one-way transaction: you produce bytes, you are done. A
window is a conversation. The display server decides when you may paint, asks
whether you are still alive, and reports input, and missing your side of any of
that leaves the window blank, frozen, or killed.

Three things change from [Writing an Image File](../image-files/).

**Pixels get wider.** Both X11 and Wayland want 32 bits per pixel, not 24. On a
little-endian machine both want a `u32` holding `0x00RRGGBB`, which lands in
memory as B, G, R, X. Writing whole `u32`s gets the byte order right without
thinking about it, which is why the framebuffer in this section has been 32-bit
from the first chapter.

**The buffer may become shared.** A file write is a copy. Putting pixels on
screen does not have to be: Wayland's `wl_shm` hands the compositor a file
descriptor for the same pages you drew into, and nothing is copied at all. X11's
`XPutImage` does copy, to the server, which is the whole reason the `MIT-SHM`
extension exists.

**Drawing once is not enough.** The server tells you when to paint, and it will
ask again.

## Xlib, the short version

Open a display, create a window, map it, then answer events. It is markedly less
ceremony than Wayland, and it still runs everywhere, including under XWayland on
a Wayland desktop.

```zig
const std = @import("std");

/// Generated by translate-c from the headers named above. `@cImport` was
/// removed from the language; C now arrives as a build-system step.
const c = @import("c");

const width: c_uint = 640;
const height: c_uint = 480;

fn drawFrame(pixels: []u32) void {
    for (0..height) |y| {
        for (0..width) |x| {
            const r: u32 = @intCast((x * 255) / (width - 1));
            const g: u32 = @intCast((y * 255) / (height - 1));
            // 0x00RRGGBB. On a little-endian machine that lands in memory as
            // B, G, R, X, which is what a 24-bit TrueColor visual expects.
            pixels[y * width + x] = (r << 16) | (g << 8) | 80;
        }
    }
}

pub fn main(init: std.process.Init) !void {
    _ = init;

    const display = c.XOpenDisplay(null) orelse return error.NoDisplay;
    defer _ = c.XCloseDisplay(display);

    // Xlib's `DefaultScreen`, `RootWindow`, `DefaultVisual` and friends are
    // function-like macros that poke at a private struct, and translate-c
    // cannot give them meaning. Every one has a real function behind it.
    const screen = c.XDefaultScreen(display);
    const root = c.XRootWindow(display, screen);
    const visual = c.XDefaultVisual(display, screen);
    const depth = c.XDefaultDepth(display, screen);
    const gc = c.XDefaultGC(display, screen);

    if (depth != 24 and depth != 32) return error.UnsupportedVisual;

    const window = c.XCreateSimpleWindow(
        display,
        root,
        0,
        0,
        width,
        height,
        0,
        c.XBlackPixel(display, screen),
        c.XBlackPixel(display, screen),
    );
    defer _ = c.XDestroyWindow(display, window);

    _ = c.XStoreName(display, window, "Zig pixels");
    _ = c.XSelectInput(display, window, c.ExposureMask | c.KeyPressMask);

    // Without this the close button tears down the connection instead of
    // sending an event the program can act on.
    var wm_delete = c.XInternAtom(display, "WM_DELETE_WINDOW", 0);
    _ = c.XSetWMProtocols(display, window, &wm_delete, 1);

    // XCreateImage takes ownership and frees this with `free()` inside
    // XDestroyImage, so it has to come from the C allocator, not a Zig one.
    const data: [*]u32 = @ptrCast(@alignCast(std.c.malloc(width * height * 4) orelse
        return error.OutOfMemory));
    drawFrame(data[0 .. width * height]);

    const image = c.XCreateImage(
        display,
        visual,
        @intCast(depth),
        c.ZPixmap,
        0,
        @ptrCast(data),
        width,
        height,
        32, // bitmap_pad: rows start on 32-bit boundaries
        0, // bytes_per_line: 0 means "work it out from width"
    ) orelse {
        std.c.free(data);
        return error.ImageFailed;
    };
    // XDestroyImage is another macro; it calls through the image's own
    // function table, which is a thing Zig can do directly.
    defer _ = image.*.f.destroy_image.?(image);

    _ = c.XMapWindow(display, window);

    var event: c.XEvent = undefined;
    while (true) {
        _ = c.XNextEvent(display, &event);
        switch (event.type) {
            // X11 keeps no copy of your pixels. Every time the window is
            // uncovered the server asks the client to paint it again, which is
            // why the image is kept around instead of blitted once.
            c.Expose => _ = c.XPutImage(display, window, gc, image, 0, 0, 0, 0, width, height),

            c.KeyPress => {
                if (c.XLookupKeysym(&event.xkey, 0) == c.XK_Escape) return;
            },

            c.ClientMessage => {
                if (event.xclient.data.l[0] == @as(c_long, @intCast(wm_delete))) return;
            },

            else => {},
        }
    }
}

// Marked `//! norun`: this opens a window and then waits for a keypress, which
// is not something a CI step can sit through. It is still compiled and linked
// against the real libX11 on every run, so a change in Xlib's headers or in
// how Zig translates them fails the build.
```

*Opens a window and waits for a keypress, which CI cannot sit through. Compiled and linked against the real libX11 on every run. (`09-graphics.x11-window`)*

Four things in that code are worth knowing before you write your own.

**`@cImport` is gone.** C headers now arrive through a build-system step. In
this repo that is what `//! link: X11` and `//! cinclude:` set up: `build.zig`
synthesizes a header, runs `addTranslateC` over it, and hands the snippet the
result as `@import("c")`. In your own project it is `b.addTranslateC(...)`
followed by `translate.createModule()`. See
[Importing C](../../working-with-c/cimport/) for the general shape.

**Xlib's `Default*` macros do not survive translation.** `DefaultScreen(dpy)`,
`RootWindow`, `DefaultVisual`, and `DefaultGC` all expand to member accesses on a
private struct, and translate-c cannot give that meaning. Every one has a real
function behind it (`XDefaultScreen`, `XRootWindow`, and so on), so call those.
`XDestroyImage` is the same problem with a different answer: it expands to a call
through the image's own function table, which Zig can do directly:

```zig
_ = image.*.f.destroy_image.?(image);
```

**`XCreateImage` takes ownership of your pixels.** It frees them with `free()`
inside `XDestroyImage`, so the buffer has to come from `std.c.malloc` and not
from a Zig allocator. Hand it a `GeneralPurposeAllocator` block and you get a
free of a pointer that allocator never owned.

**X11 keeps no copy of your pixels.** Every time the window is uncovered, the
server sends `Expose` and expects the client to paint again. That is why the
program keeps the image around instead of blitting once and forgetting it. The
close button also needs opting into: without `WM_DELETE_WINDOW` registered
through `XSetWMProtocols`, clicking it tears down the connection instead of
sending an event you can act on.

## What Wayland changes

Wayland is the newer protocol and the default on most current Linux desktops.
The drawing code is unchanged. Everything around it is different, and the
differences are all in the same direction: the compositor does less on your
behalf.

**Discovery is a handshake.** You connect, ask for the registry, and the
compositor advertises its globals. You bind the ones you need: `wl_compositor`,
`wl_shm`, `xdg_wm_base`, `wl_seat`. This takes two roundtrips, not one, because
the second is what runs the listeners you registered from inside the first.

**A surface is not a window.** A bare `wl_surface` is just a rectangle of
pixels. It becomes a window by being wrapped in an `xdg_surface` and then an
`xdg_toplevel`, which is what carries the title and the app id.

**The first commit carries no buffer.** It asks the compositor for the initial
`configure`. Attaching a buffer before that handshake completes is a protocol
error. The `configure` handler acks the serial, attaches, damages, and commits,
and that last commit is the moment the window appears.

**You must answer `xdg_wm_base.ping` with `pong`.** It is the compositor's
liveness check. Ignore it and your client is killed as unresponsive. This is the
easiest thing in the protocol to forget, because nothing goes wrong until it
does.

**The shell protocol is not in the library.** `xdg-shell` ships as XML that
`wayland-scanner` turns into a header plus glue at build time. That is a normal
`addSystemCommand` step with `addOutputFileArg`, so the generated files can live
in the Zig cache with nothing checked in.

**There are no window decorations.** A compositor *may* offer to draw them via
`zxdg_decoration_manager_v1`, but it is not obliged to, and GNOME's Mutter does
not advertise that global at all. A client that just posts a buffer gets exactly
that buffer: no titlebar, no close button, no way to move the window. Drawing
your own means allocating a taller buffer, painting a strip, tracking the
pointer, and turning a drag into `xdg_toplevel_move`, because window positions
belong to the compositor rather than to you. The cursor image is the client's
job too: the pointer is undefined until you call `wl_pointer.set_cursor`, so
without a cursor theme loaded it simply vanishes over your window.

That list is why the X11 version above is one file and a Wayland one is not.

## What neither version here does

**Animation.** One static frame. On Wayland, redraws are driven by a
`wl_surface_frame` callback so they land in step with the compositor. On X11 you
drive them from a timer or an idle loop.

**Resizing.** A real client allocates a new buffer on every `ConfigureNotify` or
`configure`. Reusing a fixed one leaves the extra area unpainted on X11 and
scaled on Wayland.

**Double buffering.** Writing into a buffer the compositor may be reading gets
you a torn frame. A real client keeps two and waits for `wl_buffer.release`.

**Format negotiation.** `XRGB8888` is mandatory in the Wayland protocol so it is
safe to assume. The X11 code above checks only that the default visual is 24- or
32-bit, which is true on any desktop from the last twenty years and is not the
same as checking what is actually offered.
