Tutorial 136: A Native Wayland Build
What you’ll learn: how to build CNA with CNA_PLATFORM=WAYLAND and CNA_ENABLE_SDL=OFF, check that your machine can build it, validate it on your own compositor with CNA's harnesses, and why it is implemented but not CI-verified.
Before you start — Tutorial 135: A Native X11 Build (this tutorial follows the same steps; read its explanation of CNA_ENABLE_SDL=OFF first), Tutorial 02 for the workspace layout. You need a Linux machine running a Wayland session (GNOME, KDE Plasma, sway, Weston, ...).
Read this first: native Wayland has no CI. The backend is implemented and has its own test suites (an in-process test compositor, headless Weston, GNOME's mutter with a real input method), but no GitHub workflow selects CNA_PLATFORM=WAYLAND; the suites are launched locally, and the statement that they pass on Weston and mutter comes from CNA's own documentation — we did not run them. The commands below are the documented recipe, checked against CNA's CMake selection logic, not a result anyone's CI has reproduced. Compare Tutorial 135, whose configuration is built and run by a CI workflow (on a virtual X server).
A Wayland session can run CNA three ways. This tutorial builds the third and then shows you how to check what you got.
| Route | How | What you get |
|---|---|---|
| SDL3 (default) | Nothing special | SDL's own Wayland driver, if the vendored SDL was built with the Wayland development packages present (otherwise SDL falls back to X11 through Xwayland). Not exercised on Wayland by CI. |
| Native X11 through Xwayland | CNA_PLATFORM=X11 (Tutorial 135) | An X11 client on the compositor's Xwayland server. The configuration CNA's CI builds, but the compositor owns the screen: stale global pointer, focus rules, compositor-side scaling. |
| Native Wayland | CNA_PLATFORM=WAYLAND (this tutorial) | A direct Wayland client: no SDL, no Xlib, no Xwayland. Fractional scaling, text-input-v3, relative pointer, primary selection. |
1. Check your session
echo "$XDG_SESSION_TYPE" # wayland
echo "$WAYLAND_DISPLAY" # e.g. wayland-0
If WAYLAND_DISPLAY is empty you are on an X session (or a text console); the built program would have no compositor to talk to. (A process with no compositor still constructs the platform, but every display capability is false and creating a window reports the connection error.)
2. Install the packages and check what CMake will look for
sudo apt-get install -y g++-14 cmake ninja-build pkg-config git \
libwayland-dev libwayland-bin wayland-protocols libxkbcommon-dev \
libegl-dev libvulkan-dev libdbus-1-dev mesa-vulkan-drivers
export CC=gcc-14 CXX=g++-14
The backend is offered only when CMake's pkg-config checks succeed. You can run the same checks by hand:
pkg-config --modversion wayland-client xkbcommon wayland-protocols # wayland-client >= 1.18, xkbcommon >= 0.5
which wayland-scanner # generates the protocol bindings
ls "$(pkg-config --variable=pkgdatadir wayland-protocols)/stable/xdg-shell/xdg-shell.xml"
pkg-config --exists egl wayland-egl && echo "EGL headers found"
| Piece | Required? | If it is missing |
|---|---|---|
wayland-client ≥ 1.18, xkbcommon ≥ 0.5, wayland-scanner, wayland-protocols with stable/xdg-shell | Mandatory | Configure fails and names the package (for example libwayland-dev, libxkbcommon-dev, wayland-protocols, libwayland-bin). It never falls back to SDL3 or X11. |
| Optional protocol files (fractional-scale, viewporter, text-input-v3, relative-pointer, pointer-constraints, primary-selection, xdg-decoration, xdg-activation, xdg-foreign, idle-inhibit, xdg-output, tablet, cursor-shape) | Optional (part of wayland-protocols) | Exactly that protocol's capability is off; an old wayland-protocols means fewer features, not a failed build. |
EGL and wayland-egl headers | Optional; libraries load at run time | No openGlContext: the OpenGL renderer cannot run. |
| Vulkan headers | Optional | No vulkanSurface. |
| D-Bus headers | Optional | No file dialogs through the desktop portal. |
3. Clone the workspace
mkdir cna-workspace && cd cna-workspace
git clone -b next https://github.com/libcna/cna.git
git clone -b next https://github.com/libcna/sharp-runtime.git
git clone https://github.com/libcna/easy-gl.git # OPENGL33 is an EasyGL profile
git clone https://github.com/libcna/meta-gl.git
cd cna
git submodule update --init
4. Configure
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCNA_ENABLE_SDL=OFF \
-DCNA_PLATFORM=WAYLAND \
-DCNA_GRAPHICS_RENDERER=OPENGL33 \
-DCNA_GRAPHICS_RENDERERS="OPENGL33;VULKAN;SOFTWARE;HEADLESS" \
-DCNA_AUDIO_PLATFORM=NULL \
-DCNA_ENABLE_VIDEO=OFF \
-DCNA_BUILD_TESTS=OFF \
-DCNA_ENABLE_NET=OFF
What each flag does: CNA_ENABLE_SDL=OFF and the native platform remove SDL; NULL audio is required because the default audio (SDL3) needs SDL — swap in ALSA for real sound (Tutorial 137); the renderer list is the same set the X11 CI job uses, with OPENGL33 going through EGL and VULKAN through VK_KHR_wayland_surface; CNA_ENABLE_VIDEO=OFF keeps FFmpeg out of the link so the binary really has no X11 in it (Debian's FFmpeg itself links libX11, according to CNA's notes); the last two flags shorten the build. Expect log lines like:
-- CNA: Using WAYLAND platform implementation
-- CNA: Wayland platform dependencies -- wayland-client 1.22.0, xkbcommon 1.6.0, wayland-protocols 1.34; optional present: xdg-output-unstable-v1, viewporter, fractional-scale-v1, ...; absent: ...
-- CNA: SDL is NOT configured (CNA_ENABLE_SDL=OFF). No SDL source is fetched, built, found or linked by this configuration.
(The version numbers are examples.) The absent: list tells you which Wayland features your build lacks. The build also needs a C compiler, because wayland-scanner generates C protocol tables; CMake enables C for this platform automatically.
5. Build and run
cmake --build build --target cna_demo_2d cna_house3d_demo --parallel
cd build
./cna_demo_2d
./cna_house3d_demo
Run inside your Wayland session. The window is a native Wayland surface, not an Xwayland one. Choose another renderer with CNA_GRAPHICS_RENDERER=VULKAN ./cna_demo_2d. Remember that SOFTWARE, HEADLESS and STUB open no window on any windowing platform, including this one.
What the window looks like
GNOME's compositor offers no server-side decoration protocol. When the compositor does not decorate, the backend draws a minimal frame of its own: a bar with close, maximize and minimize buttons (only the ones the compositor allows), drag to move, double-click to maximise, right-click for the compositor's window menu, and an invisible border for resizing. It draws no title text (the platform module has no text renderer); the compositor still shows the title in its overview. On KDE and most wlroots compositors CNA asks the compositor to decorate instead. A click on the frame is never delivered to your game.
6. Prove it is native
ldd cna_demo_2d | grep -i -E 'sdl|libX11|xcb|libGLX' # nothing expected
readelf -d cna_demo_2d | grep NEEDED | grep -i -E 'sdl|x11|xcb'
ldd cna_demo_2d | grep -E 'wayland|xkbcommon' # libwayland-client, libxkbcommon
CNA's own link-closure test (CnaWaylandLinkClosure) asserts that no executable in a Wayland build NEEDs SDL, X11, xcb or GLX, and that the platform-only validation binary's closure is just libwayland-client, libxkbcommon, libffi and the C/C++ runtime; a full game additionally links what its engine modules use (for example libvulkan when the Vulkan renderer is compiled in). EGL, libwayland-egl, libwayland-cursor and libdbus are loaded at run time and do not appear as NEEDED.
7. Validate it on your desktop
Because there is no CI, the harnesses in the repository are how you find out whether it works on your compositor. Configure a second build tree with tests on (the platform-only test binary is enough) and build the validation tool:
cd .. # back to the cna source directory
cmake -S . -B build-test -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCNA_ENABLE_SDL=OFF -DCNA_PLATFORM=WAYLAND -DCNA_AUDIO_PLATFORM=NULL \
-DCNA_GRAPHICS_RENDERER=HEADLESS -DCNA_ENABLE_VIDEO=OFF -DCNA_ENABLE_NET=OFF \
-DCNA_BUILD_TESTS=ON -DCNA_PLATFORM_CTEST_BINARY=CnaPlatformModuleTests
cmake --build build-test --target cna_wayland_desktop_validation CnaPlatformModuleTests --parallel
./build-test/cna_wayland_desktop_validation info
./build-test/cna_wayland_desktop_validation lifecycle --windows 2
./build-test/cna_wayland_desktop_validation scale
cna_wayland_desktop_validation prints one PASS, FAIL or SKIP line per check and never injects input. info lists the compositor's globals and versions, the outputs (mode, scale, logical geometry), the seats and the capability set your build ended up with — the fastest way to see whether text-input-v3 or fractional scaling is really on offer. Other scenarios: presenter, gl [--allow-software], vulkan [--validation], lifetime, stress, soak and a person-driven interactive one. The clipboard scenario overwrites your real clipboard and requires --replace-my-clipboard.
The registered CTest suites are named CnaWayland*. Two of them need no compositor at all — one is pure decisions (scaling, buttons, MIME types, an SDL/X11 source scan), the other runs 78 cases against an in-process compositor that raises the protocol errors a strict compositor would:
ctest --test-dir build-test -R '^Cna(WaylandMappingTests|WaylandProtocolTests)$' --output-on-failure
The others (CnaWaylandWestonTests, CnaWaylandMutterTests, CnaWaylandIbusTests, ...) run through tools/platform/wayland_test_server.sh, which starts a private headless Weston or GNOME mutter (and never touches your desktop's compositor or session bus) and skips with exit status 77 when the compositor is not installed. The tests need libwayland-server (part of libwayland-dev); it is used only for the test compositor and is never linked into the platform library.
What is different from X11 and SDL3
| Behaviour | Native Wayland |
|---|---|
| HiDPI | Yes: fractional scale through wp_fractional_scale_v1 + wp_viewporter, integer scale otherwise. (X11: no, by design.) |
| Global pointer / warping | No, by design: globalPointer is false; capture, global position and set-global-position throw PlatformNotSupportedException. SetPosition moves no pointer. |
| Window position | The compositor's. x/y/centre in WindowDescription are ignored; there is no “moved” event. |
| Exclusive fullscreen | Requested, reported as borderless fullscreen (a client cannot own a display mode). |
| Message box, tray | Not available (draw your own dialog with a SpriteFont). File dialogs work through the desktop portal. |
| Keyboard repeat | Generated by CNA from the compositor's repeat_info (Wayland sends none). |
| Gamepads | Yes on Linux, through the kernel's evdev nodes (same as X11); no display needed. |
| Input method | Through text-input-v3 where offered (GNOME and KDE); candidate lists remain the input method's own window. |
Troubleshooting
| Symptom | Cause and fix |
|---|---|
| Configure fails: “CNA_PLATFORM=WAYLAND was requested but this machine cannot build it” | The message names the missing piece: a pkg-config module, wayland-scanner, or stable/xdg-shell/xdg-shell.xml (needs wayland-protocols ≥ 1.12). Not offered on Windows, Emscripten, Android or Apple targets. |
| The program exits at start-up mentioning a connection error | No compositor: WAYLAND_DISPLAY is unset or wrong, or you are on an X session. |
| A window opens but cannot be moved or closed | The compositor does not decorate and the frame CNA draws was hidden (fullscreen/borderless). Use the game's own exit, or leave fullscreen. |
| “OpenGL” renderer fails to start | EGL headers were absent at build time (see the absent: list) or libEGL.so.1/libwayland-egl.so.1 are missing at run time. |
| No window, no error | You selected a CPU renderer (SOFTWARE, PORTABLEGL, HEADLESS, STUB): these are off-screen on windowing platforms. |
| A protocol error ends the game | A protocol error or a vanished compositor ends the connection for good: the backend prints the interface, object and code once to stderr ([CNA][Wayland] ...) and posts one quit event. Please report it with the compositor name and version. |
Where to go next
- Tutorial 138: Building Without SDL — the rules behind
CNA_ENABLE_SDL. - Tutorial 137: Native Linux Audio with ALSA — sound with no SDL.
- Native Platforms — Wayland and Platform Support.
- CNA's own manual for this backend: docs/platform-wayland.md at this snapshot.