AnotherOS

Documentation

The display protocol

How a client talks to the Fasadas compositor — surfaces, buffers, input and the pasteboard.

This is how a client talks to fasadasd, the Fasadas compositor and window server.

Most applications should use the AO toolkit instead. You need this page if you are porting a toolkit, writing a backend, or debugging something that draws its own windows. #include "fasadas-proto.h" gives you the constants; libfasadas covers the rest.

Shape

An AF_UNIX SOCK_STREAM socket at /run/fasadas-0, self-framed: each message is a 32-byte header (type, serial, generic arguments a–e, len) followed by len payload bytes.

Two design decisions are worth knowing before anything else.

Buffers are never sent inline. They travel as a memfd over SCM_RIGHTS and the compositor maps the same physical pages. There is no copy.

Surface ids are client-allocated, so creating a window needs no round trip.

The header is exactly 32 bytes and always will be. Framing is size-based, so growing the header would silently desynchronise every client not rebuilt in lockstep. New capability arrives as new opcodes, never as new header fields.

Declare opacity, or leak the window body

FZ_ATTACH_BUFFER carries a format in its payload, and picking the wrong one is the single most common visible bug in a new client.

  • FZ_FORMAT_ARGB32 — premultiplied 32-bit ARGB. The compositor alpha-blends your buffer over its grey window body. Any pixel you leave with alpha below 255 shows that grey, which reads on screen as white.
  • FZ_FORMAT_XRGB32 — opaque 32-bit. The alpha channel is ignored entirely.

A window that is never translucent must attach XRGB32. Otherwise a partial or idle frame that leaves stale alpha anywhere bleeds the body colour through — the "goes white when idle" class of bug.

libfasadas exposes this as fz_window_set_opaque(win, 1); the default is ARGB32. The GDK backend picks it automatically from the window's visual depth.

Versions, and what an older client gets wrong

The compositor speaks one version to everyone, and says so loudly on the console when a client announces an older one — naming the concrete misreading rather than "version mismatch". It does not refuse the client: breaking a desktop over a scroll wheel is worse than a wrong scroll.

Version What it added What a client below it misreads
v2 Parenting and modality, real key modifiers and codepoints, size limits, cursors, the clipboard Nothing — new opcodes, or fields that had always been sent as zero
v3 Scroll events repacked; every pointer event gained a timestamp A v2 client reads both scroll axes packed together — a plausible number that is wrong — and cannot tell a double-click from two clicks
v4 All three pointer buttons; the implicit pointer grab; surface position; popup roles A v3 client takes every right-click for a left-click, and one that unpacks coordinates as unsigned reads a small negative as about 65000
v5 Real unmap (hide), and maximise / fullscreen / restore Nothing. A v4 client simply cannot hide without stranding the window, or go fullscreen
v6 Frame callbacks become post-composite; real damage; configure back-pressure; buffer scale Nothing — appended fields that were zero before
v7 Zero-copy GPU window buffers over dma-buf, advertised as a feature flag Nothing — a new opcode a client uses only when the flag is advertised
v8 The work area: the screen minus the menu bar and the dock's shelf Nothing on the wire, but a v7 client has no work area and must substitute the whole screen — which makes short popups near the bottom land under the dock instead of flipping above the widget that opened them
v9 The typed pasteboard, sealed memfds in both directions; the old clipboard becomes the general board's text Nothing — the new events go only to a client that subscribed or asked
v10 Trackpad gestures and scroll phases, behind a subscription Nothing — everyone else keeps receiving a trackpad's scrolling as ordinary scroll events
v11 HiDPI: the real UI scale, but only for a client that declares it understands scaling Nothing — a client that does not set the bit is told the scale is 1 and composited as a 1× client: right size, upsampled

Pointer input

All three buttons

The button field is FZ_BTN_LEFT, FZ_BTN_RIGHT or FZ_BTN_MIDDLE. Check it; do not assume it.

The numbers are the PS/2 mask order plus one. They are not X11's, where 2 is middle and 3 is right — a backend translating to X11 or GDK must map, not cast.

The implicit pointer grab

A press gives the pressed surface the pointer until the last button is released. Until then:

  • every motion goes to that surface, in its coordinates, however far outside it the pointer travels — so coordinates may be negative or past the edge;
  • no other surface is entered, and the grabbing surface is not sent a leave;
  • the button-up goes to the surface that was pressed, never to whatever the pointer ended up over.

This is X11's and Wayland's semantic, and it is what makes drag-to-select, rubber-band selection, sliders and drag-and-drop work at all.

Sign-extend your coordinates. Motion coordinates are 32-bit; the button's packed pair is two 16-bit values. Unpacking either as unsigned turns a drag three pixels off the left edge into one 65533 pixels off the right.

Surface position

void fz_window_set_position(fz_window *, int x, int y);  /* ask           */
void fz_window_position(fz_window *, int *x, int *y);    /* last reported */
/* fz_listener.position(win, x, y, user)                     on every move */

The coordinates are the content origin in root coordinates — the first pixel you drew, not the first pixel of the compositor's title bar. Reporting the frame origin instead would put every conversion out by the title height on a decorated window and be right on an undecorated one, which is the sort of bug that only shows up in one of your two window kinds.

Setting a position is a request. The compositor still places new windows.

The work area

The work area is the usable rectangle: the screen minus the global menu bar and the dock's shelf.

Use it for anything that must not be covered — popup placement above all. A popup positioned against the screen rather than the work area lands under the dock, and does so only near the bottom of the screen, which is why it survives casual testing.

Frame pacing and damage

From v6, a frame callback fires after the compositor has composited, not when it accepted your buffer. Pace your drawing on that, and you are pacing on what reached the panel.

Report real damage rather than the whole surface where you can; the compositor composites what you declare. Configure events apply back-pressure, so a client that draws faster than the screen is told to slow down rather than building a queue.

The typed pasteboard

From v9 the clipboard is a typed, system-wide pasteboard: declare types, provide data on demand, read, list types, and subscribe to changes. Data moves as sealed memfds in both directions, and the compositor keeps the bytes, so a copy survives the process that made it.

Through the toolkit this is AO_Pasteboard; see the toolkit page for the API and for the one performance trap in it.

HiDPI

A client declares that it understands scaling. Only then is it told the real UI scale and composited at it. A client that says nothing is told the scale is 1 and is composited as a 1× client — the right size on screen, upsampled.

This is deliberate: an old client on a HiDPI panel looks soft, which is a great deal better than looking quarter-sized.

Trackpad gestures

Scroll phases and gestures are delivered only to a client that subscribes. Everyone else continues to receive a trackpad's scrolling as ordinary scroll events, so nothing breaks by not opting in.