8 Getting Started
dxtr edited this page 2026-10-18 16:01:10 +02:00

Getting Started

Requirements

  • SBCL.
  • For cl-windowing/wayland: a Wayland compositor and libwayland-client. Keyboards also need libxkbcommon, and cursor themes libwayland-cursor.
    • Debian and Ubuntu: libwayland-client0 libwayland-cursor0 libxkbcommon0
  • For cl-windowing/win32: Windows 10 version 1607 or later. It needs nothing beyond Windows' own DLLs.
  • For cl-windowing/vulkan: the Vulkan loader and a driver that presents to Wayland or Windows.
    • Debian and Ubuntu: libvulkan1, with mesa-vulkan-drivers for Mesa's drivers, lavapipe included.

The Lisp dependencies, cl-wl, cl-xkbcommon, cl-win32, cl-threads, cl-vk and cl-ffi, are pinned by commit in the stiftfile and fetched with stift.

The ASDF systems

System What it is Load it when
cl-windowing The backend-independent API: window systems, windows, events. It cannot open windows alone. Never on its own. The others load it.
cl-windowing/wayland The Wayland backend. You run on Wayland.
cl-windowing/win32 The Windows backend. You run on Windows.
cl-windowing/vulkan Vulkan surfaces for windows, through cl-vk. You use Vulkan directly. cl-gfx loads it for you.
cl-windowing/test The test suite. You work on cl-windowing.

Load the backend for your platform. cl-windowing itself never depends on a backend or on Vulkan.

A complete program

This opens a window, draws a dark blue background into it with cl-gfx, and runs until the window is closed or Escape is pressed:

(asdf:load-system :cl-windowing/wayland)   ; on Windows, :cl-windowing/win32
(asdf:load-system :cl-gfx/windowing)

(defpackage #:example
  (:use #:cl)
  (:local-nicknames (#:win #:cl-windowing)
                    (#:gfx #:cl-gfx)))
(in-package #:example)

(defun run ()
  (win:with-window-system (window-system)
    (win:with-window (window window-system :title "Hello" :width 800 :height 600)
      (gfx:with-device (device :present-to window-system)
        (let ((surface (gfx:create-surface device window))
              (event (win:make-event)))
          (loop
            (win:process-events window-system)
            (loop while (win:poll-event window-system event)
                  do (case (win:event-type event)
                       (:close (return-from run))
                       (:key-press (when (eq (win:event-key event) :escape)
                                     (return-from run)))))
            (gfx:with-frame (frame device)
              (let ((texture (gfx:acquire-surface-texture surface frame)))
                (when texture
                  (gfx:with-render-pass (pass frame :color ((texture :clear '(0.1 0.2 0.3 1))))
                    ;; Draw here.
                    ))))))))))

(run)

What happens:

  1. with-window-system connects to the compositor, with the platform's backend: :wayland, or :win32 on Windows.
  2. with-window creates the window. Nothing is visible yet.
  3. Each turn of the loop, process-events handles what the compositor sent without waiting, and poll-event hands out the events one by one.
  4. cl-gfx draws a frame. Until the window system has given the window its first size, acquire-surface-texture returns nil and nothing is drawn. After that, the first frame makes the window appear. The surface follows the window's size and scale by itself; see Resizing and Scaling.
  5. Leaving run closes the device, then destroys the window, then disconnects. The forms are nested so that this is the order.

The frame is paced by the display, because cl-gfx surfaces present in :fifo mode by default, so the loop does not spin.

In a larger program, events and rendering usually run on two threads. See Threads.

Using it from the REPL

Everything that with-window-system and with-window do can be done step by step:

(defvar *ws* (win:open-window-system))
(defvar *w* (win:create-window *ws* :title "REPL"))
(win:process-events *ws* :timeout 1)
(win:window-configured-p *w*)   ; => T
(win:window-size *w*)           ; => 800, 600 where the compositor keeps the size asked for
(setf (win:window-title *w*) "Renamed")
(win:destroy-window *w*)
(win:close-window-system *ws*)

The window system expects answers. A compositor may decide that a client that does not call process-events for a while is not responding.

Running the tests

cd cl-windowing
stift test

asdf:test-system signals an error if any test fails, so the command exits unsuccessfully.

  • On Linux, the protocol tests run against cl-wl's in-process compositor, cl-wl/test-compositor. They need only libwayland (libwayland-client0 libwayland-server0 libwayland-cursor0) and no running compositor.
  • The Vulkan tests start a headless Weston (weston) and present to it. They need a Vulkan driver that presents to Wayland, such as lavapipe.
  • When the Khronos validation layer is installed (vulkan-validationlayers), the Vulkan tests run under it and fail on any error it reports.
  • On Windows, the suite tests cl-windowing/win32 against the desktop: it opens windows, so it needs a desktop session. The clipboard test uses the desktop's clipboard and puts back the text it held, so it needs the session unlocked; the test of capturing the pointer moves the real pointer, and puts it back afterwards.

CI runs the suite on SBCL, on Linux. The Windows backend is tested on Windows, outside CI.