2 Concepts
dxtr edited this page 2026-10-14 04:26:09 +02:00

Concepts

cl-windowing is small. Its model fits on one page, and the rest of the documentation builds on it.

Window systems, backends and windows

A window system is a connection to the platform's display server: on Wayland, the compositor; on Windows, user32. open-window-system makes one, through a backend. A backend is an ASDF system, such as cl-windowing/wayland or cl-windowing/win32, that registers itself under a keyword, such as :wayland or :win32, when it loads. The core never refers to a backend by name, so backends can be added without changing it.

A window is a top-level window of a window system. create-window makes one. A popup is a window made on another, for a menu, a list to choose from or a tooltip; create-popup makes one. A window always belongs to one window system, and closing the window system destroys its windows.

All are CLOS objects with a subclass per backend, such as wayland-window. Programs use only the base classes window-system, window and popup, and the functions on them.

One event stream

Everything the window system tells the program arrives as an event: a window was resized or rescaled, asked to close, gained or lost focus; a key went down or up; text was typed; the pointer entered, moved, clicked or scrolled. It happens in two steps:

  1. process-events reads what the platform sent and turns it into events, which wait in a queue owned by the window system. It can wait for something to arrive.
  2. poll-event copies the oldest waiting event into an event struct that you made once with make-event. It never waits.

The event struct is reused for every event, and the queue reuses its own structs, so the event stream allocates nothing in steady state. That matters in code that runs every frame.

Wayland's rules, everywhere

The API follows Wayland's restrictions, so that every backend can meet them. A program written against cl-windowing therefore works the same on every platform.

  • No global coordinates. A window does not know where it is on screen, and pointer positions are relative to the window.
  • No positioning of top-level windows. The window system places them.
  • Popups are placed against a rectangle of their parent, such as the button that opens them, and the window system moves them where they would not fit. See Popups.
  • The window system has the last word on size. create-window asks for a size. The window system then proposes one, and may propose others at any time.
  • A window appears when it is first drawn. Until then it does not exist on screen.

The resize handshake

When the window system proposes a new size, the window does not take it at once. The proposal becomes the window's pending size, and a :resize event announces it. The code that renders recreates its swapchain at that size and calls adopt-window-size just before it presents the first frame at it. Only then is the window system told that the window follows its proposal. So the window system never sees a frame whose size disagrees with what it was told.

cl-gfx does all of this for you. See Resizing and Scaling for the details, and for writing it yourself against Vulkan.

Units and pixels

A window's size is in units: pixels at scale 1. Its scale is how many pixels its frames have per unit on the display it is on. It is an exact rational such as 1, 2 or 3/2, for a display scaled to 150%. Pointer positions are in units. Frames are drawn in pixels: window-pixel-size is the size times the scale. A new scale goes through the same handshake as a new size.

Physical keys, logical keys and text

The keyboard reports three separate things:

  • the physical key, event-key, named for where it is on a US keyboard whatever the layout. Use it for WASD-style controls.
  • the logical key, event-symbol, what the layout makes of the key. Use it for shortcuts such as Control+Z.
  • the text typed, in :text events, after dead keys and compose sequences. Use it for text fields.

See Keyboard.

Capabilities

What a window system can do beyond the basics differs between platforms and between compositors. window-system-capabilities says what it can do now. Check it instead of assuming.

Threads

process-events runs on one thread, the event thread. Everything else may be called from any thread, typically the render thread. A lock serialises access to the platform, and process-events does not hold it while it waits, so the render thread can adopt a size while the event thread waits. See Threads.

Three layers

cl-windowing sits between the bindings below it and the code that uses it:

  • Below it: cl-wl (Wayland) and cl-xkbcommon (keymaps), cl-win32 (Windows) and, for cl-windowing/vulkan only, cl-vk.
  • cl-windowing: windows and input, with no knowledge of rendering.
  • Above it: cl-gfx, through cl-gfx/windowing, and the Pyssel engine.

No foreign pointer and no Vulkan type appears in the core API. The one place where native objects cross is cl-windowing/vulkan.