Architecture
Overview
wayland-cxx-scanner is a code-generation tool and companion C++23 framework
for the Wayland display protocol. Given a
Wayland XML protocol definition it produces type-safe, zero-overhead C++23
headers that replace the hand-written C bindings normally provided by
wayland-scanner.
The project has two distinct deliverables:
Deliverable |
Description |
|---|---|
Scanner tool ( |
Reads a |
Framework library ( |
A header-only C++23 base library installed to |
Repository Layout
wayland-cxx-scanner/
├── include/wl/ # Framework headers (installed)
├── src/ # Scanner tool source code
├── protocols/ # Bundled Wayland XML protocol definitions
├── tests/ # Unit and integration tests
├── examples/ # Example Wayland client/server applications
├── subprojects/ # Meson wrap files (e.g. pugixml)
└── meta-wayland-cxx-scanner/ # Yocto/OpenEmbedded recipe metadata
Scanner Tool
Pipeline
┌──────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ protocol.xml │─────▶│ XML Parser │─────▶│ Intermediate │
│ (Wayland) │ │ (xml_parser) │ │ Representation │
└──────────────┘ └──────────────────┘ │ (ir.hpp) │
└────────┬────────────┘
│
┌─────────────────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ codegen_client │ │ codegen_server │ │ codegen_c │
│ _cxx │ │ _cxx │ │ │
└────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘
│ │ │
▼ ▼ ▼
client proxy .hpp server resource .hpp C-style .h
Modules
xml_parser (src/xml_parser.{cpp,hpp})
Parses a Wayland XML protocol document using the pugixml library. The two entry points are:
Protocol parse_protocol(const std::filesystem::path& path);
Protocol parse_protocol_from_string(std::string_view xml);
Both return a fully populated ir::Protocol tree or throw ir::ParseError on
malformed input.
Intermediate Representation (src/ir.hpp)
A simple, self-contained data model in the wl::scanner::ir namespace:
Protocol
└── Interface[]
├── Message[] (requests and events, tagged by opcode)
│ └── Arg[] (typed arguments with optional interface/enum qualifiers)
└── Enum[]
└── EnumEntry[]
Key types:
Type |
Description |
|---|---|
|
Top-level container; holds all parsed interfaces. |
|
One |
|
A single |
|
One typed argument; |
|
Named enumeration with optional bitfield flag. |
|
Exception thrown for malformed XML. |
name_transform (src/name_transform.{cpp,hpp})
Utility functions that convert Wayland snake_case identifiers into C++
PascalCase and camelCase names used by the generated code.
Code Generators
Module |
Generates |
Entry point |
|---|---|---|
|
C++23 client proxy headers (CRTP, event maps) |
|
|
C++23 server resource headers (CRTP, request maps) |
|
|
C-style protocol headers (compatible with |
|
All three generators accept a CppStd enum (Cpp17, Cpp20, Cpp23) that
gates which language features appear in the output.
main.cpp
Command-line driver. Accepts --mode=<mode> and --std=<std> flags,
delegates to the appropriate generator, and writes the result to a file or
stdout.
Usage: wayland-cxx-scanner [--mode=<mode>] [--std=<std>] <protocol.xml> [<output.hpp>]
Modes:
client-header Generate C++ client proxy header (default)
server-header Generate C++ server resource header
c-header Generate C-style protocol header
Framework Library (include/wl/)
The framework is a header-only base library that generated code builds upon. It follows the WTL (Windows Template Library) message-map pattern adapted for Wayland events and requests.
Class Hierarchy
wl::CEventMap (abstract: ProcessEvent)
└── wl::CProxyImpl<Derived, Traits> (CRTP client proxy base)
└── <Generated> CXxxInterface<App>
wl::CProxy<Traits> (non-owning handle wrapper, ≈ WTL CWindow)
└── wl::CProxyImpl (also inherits CProxy)
wl::WlPtr<T> (RAII owning wrapper, ≈ WTL CAutoPtr)
Key Headers
proxy.hpp — wl::CProxy<Traits>
Non-owning, type-safe handle around a wl_proxy*. Analogous to WTL’s
CWindow. The Traits template parameter must satisfy the WlProxyTraits
concept, requiring:
static constexpr std::string_view interface_namestatic constexpr uint32_t versionstatic const wl_interface& wl_iface() noexcept
event_map.hpp — wl::CEventMap + macros
Abstract base for opcode dispatch. Generated proxy and resource classes
implement ProcessEvent using the macro DSL:
BEGIN_EVENT_MAP(MyProxy)
EVENT_HANDLER(0, OnEnter)
EVENT_HANDLER(1, OnLeave)
END_EVENT_MAP()
Server-side equivalents: BEGIN_REQUEST_MAP / REQUEST_HANDLER /
END_REQUEST_MAP.
proxy_impl.hpp — wl::CProxyImpl<Derived, Traits>
CRTP base that combines CProxy and CEventMap. Provides:
_SetProxy(wl_proxy*)— attach and install the static listener table._Marshal(opcode, args…)— send a request to the compositor._MarshalNew(opcode, &iface, args…)— send a request that creates a new object.
wl_ptr.hpp — wl::WlPtr<T>
RAII owning wrapper for a CProxy-derived object. Calls T::Destroy() on
destruction or reset. Ownership is transferred via Attach()/Detach() on
the underlying wl_proxy* (the deleted copy/move constructors of CProxy
prevent naive ownership transfer).
registry.hpp — wl::CRegistry and wl::CGlobal<Traits>
CRegistry: client-side registry wrapper. Exposes typedOnGlobal/OnRemovecallbacks and a templatedBind<Traits>()helper.CGlobal<Traits>: server-side global factory that advertises an interface on the Wayland display.
display.hpp — wl::CDisplay
RAII wrapper around wl_display. Manages connection lifecycle and the event
loop.
raii.hpp
Lightweight RAII helpers for raw Wayland C objects not covered by the typed wrappers.
client_helpers.hpp
Utility functions and types used by client-side generated code and example applications.
keyboard.hpp / seat.hpp
Higher-level seat and keyboard input handlers. wl::KeyboardHandler<App>
requires the application class to implement OnKey(key, state) and optionally
OnKeySym(xkb_keysym_t, key, state).
wayland.hpp / xdg_shell.hpp
Pre-generated proxy headers for the core Wayland and XDG shell protocols, provided as part of the installed framework so consumers do not need to run the scanner themselves for these common protocols.
Build System (Meson)
The project uses Meson ≥ 1.1 with C++23.
Build Options
Option |
Type |
Default |
Description |
|---|---|---|---|
|
boolean |
|
Build and run unit/integration tests |
|
boolean |
|
Build example Wayland applications |
|
boolean |
|
Generate Doxygen HTML documentation |
Dependencies
Dependency |
Required |
Notes |
|---|---|---|
|
Yes |
Bundled as a Meson subproject fallback |
|
Optional |
Required for examples and client headers |
|
Optional |
Required for examples and server headers |
Generated Code Conventions
Client Side
For each <interface> the scanner emits:
Traits struct — satisfies
WlProxyTraits; holdsinterface_name,version, and a reference to the protocol’swl_interface.Proxy class
CXxx<Derived>— CRTP template inheritingwl::CProxyImpl<Derived, XxxTraits>. One_EvtNstatic function per event; oneSendFoo()method per request.Listener table
s_listener_table_—inline static const void*[]array of_EvtNfunction pointers passed towl_proxy_add_listener.
Server Side
For each <interface> the scanner emits:
Traits struct — server-side interface traits.
Resource class
CXxx<Derived>— CRTP template inheritingwl::CResourceImpl<Derived, XxxTraits>with_ReqNstatic functions andSendFoo()event senders.Dispatch table for
wl_resource_set_implementation.
Opcode Macros
EVENT_HANDLER and REQUEST_HANDLER macros require raw integer opcodes (not
symbolic names) because the ## token-paste operator must produce single
identifiers such as _CrackEvent_0.
Data Flow Example
wayland-cxx-scanner --mode=client-header xdg-shell.xml xdg_shell.hpp
│
┌───────────────▼────────────────┐
│ parse_protocol("xdg-shell.xml") │
│ → ir::Protocol │
└───────────────┬────────────────┘
│
┌───────────────▼────────────────┐
│ generate_client_cxx_header() │
│ (codegen_client_cxx.cpp) │
└───────────────┬────────────────┘
│
xdg_shell.hpp
│
┌───────────────▼────────────────┐
│ Application code │
│ #include "xdg_shell.hpp" │
│ class App : CXdgSurface<App> │
│ { BEGIN_EVENT_MAP(App) │
│ EVENT_HANDLER(0, OnConfigure│
│ END_EVENT_MAP() } │
└────────────────────────────────┘