Project 3: Custom Wayland Protocol Extension
Design and implement a custom Wayland protocol with XML, generate bindings, and build a client + compositor demo that uses it.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 4: Advanced |
| Time Estimate | 1-2 weeks |
| Main Programming Language | C (Alternatives: C++, Rust, Zig) |
| Alternative Programming Languages | C++, Rust, Zig |
| Coolness Level | Level 4: Hardcore Tech Flex |
| Business Potential | Level 3: Specialist Tooling |
| Prerequisites | Project 1 and 2, basic XML, build tooling |
| Key Topics | Wayland XML protocol design, wayland-scanner, versioning, client/server state machines |
1. Learning Objectives
By completing this project, you will:
- Design a Wayland protocol extension with a clear interface, requests, events, and versioning rules.
- Use wayland-scanner to generate client and server bindings.
- Implement server-side handlers and client-side proxies for your custom protocol.
- Validate protocol correctness with explicit state tracking and error handling.
- Document a protocol so others can implement it independently.
2. All Theory Needed (Per-Concept Breakdown)
2.1 Wayland Protocol XML and Interface Design
Fundamentals
Wayland protocols are defined in XML files that describe interfaces, requests, events, and object lifetimes. Each interface has a name and version, and each request or event defines typed arguments. The XML is the single source of truth that wayland-scanner uses to generate C bindings. A good protocol design is minimal, explicit, and versioned. Requests should model actions, events should model notifications, and object lifetimes should be obvious from the spec. If you design a protocol poorly, you will create ambiguity, deadlocks, or incompatible clients. The XML also encodes the wire format (argument order and types), so careful design avoids serialization surprises.
Deep Dive into the concept
The XML schema defines an interface as a set of requests and events. Each request is sent from client to server; each event is the reverse. Arguments are typed (int, uint, fixed, string, object, new_id, fd, array) and are 32-bit aligned. The most important design choice is object ownership: who creates an object ID, and who destroys it. In XML, new_id arguments indicate that the caller allocates an object ID; object arguments pass existing objects. This shapes the lifetime and reference model for your protocol.
Protocol design is essentially API design with stricter backward-compatibility rules. Once you release version 1, you cannot remove or change requests. You can only add new requests or events in higher versions. That means you must think about the future. A good approach is to keep version 1 minimal and add optional features in later versions. If you are unsure, create a separate interface for advanced features rather than bloating the core interface. Another design consideration is error handling. Wayland does not have explicit error returns for most requests; instead, you define your own error enums or send protocol errors and disconnect. This makes input validation important. If you accept invalid requests, you may corrupt state; if you disconnect too aggressively, clients may be fragile. The right balance depends on your protocol’s goals.
The XML also defines documentation:
Another essential point: naming and namespaces. Stable protocols typically live in the “wl_” or “xdg_” namespaces, while unstable experimental protocols use “zwp_” or “zwlr_” prefixes. For your custom protocol, use a private namespace (e.g., “my_” or “demo_”) and include a versioned interface name. This avoids collisions and makes it clear that the protocol is specific to your compositor.
Finally, consider the protocol’s state machine. Even a simple interface has implied states: created, configured, active, destroyed. Document these states in the XML description and enforce them in code. For example, if a client must call “set_size” before “commit”, write that down. Explicit state machines reduce bugs and make debugging with WAYLAND_DEBUG easier because you can compare messages to expected transitions.
It is also useful to think about how your protocol interacts with existing ones. If you invent a new object that is logically tied to a wl_surface or xdg_toplevel, you should pass that object explicitly as an argument and document the relationship. This avoids hidden coupling and makes it clear which lifetimes are linked. A protocol that composes with existing objects is easier to adopt and easier to test, because it fits into the existing client and compositor models. Clarity here prevents subtle integration bugs.
How this fit on projects
This concept is the foundation for Section 3 specification, Section 4 architecture, and Section 5 implementation phases. You cannot implement a custom protocol without a correct XML design.
Definitions & key terms
- interface -> protocol object type defined in XML
- request -> client-to-server message
- event -> server-to-client message
- new_id -> argument indicating caller allocates object ID
- version -> interface version negotiated at bind time
- unstable protocol -> protocol not yet considered stable, often zwp/zwlr
Mental Model Diagram (ASCII)
XML spec
|
v
wayland-scanner -> generated headers
|
v
client proxy <---------> server resource
How It Works (Step-by-Step)
- Write XML with interface, requests, events, and descriptions.
- Choose a version and namespace.
- Define object creation with new_id where appropriate.
- Document state transitions and constraints.
- Run wayland-scanner to generate bindings.
Invariants:
- Requests in version N must not change semantics.
- Object lifetimes must be unambiguous.
Failure modes:
- Ambiguous ownership -> memory leaks or crashes.
- Breaking changes -> clients fail to bind.
Minimal Concrete Example
<interface name="demo_alert" version="1">
<request name="show">
<arg name="message" type="string"/>
</request>
<event name="dismissed"/>
</interface>
Common Misconceptions
- “I can change request signatures later.” -> You cannot without breaking clients.
- “XML is only for docs.” -> It is the source of generated code.
Check-Your-Understanding Questions
- What does new_id mean in a request argument?
- Why must version 1 be minimal?
- How do you signal errors in a Wayland protocol?
Check-Your-Understanding Answers
- The caller allocates a new object ID.
- Because you cannot remove or change requests later.
- Typically with protocol errors or custom error events/enums.
Real-World Applications
- xdg-shell and layer-shell are defined exactly this way.
- Custom compositor protocols (Sway IPC, KDE protocols) use XML specs.
Where You’ll Apply It
- In this project: Section 3.2, Section 3.5, Section 4.1, Section 5.10.
- Also used in: P02 Simple Wayland Compositor for server-side globals.
References
- Wayland protocol XML schema
- wayland-protocols repository
Key Insights
Your XML is your API contract; treat it like a public specification.
Summary
Protocol design is about clear object lifetimes, explicit state machines, and careful versioning.
Homework/Exercises to Practice the Concept
- Draft a protocol interface for a “notification” surface.
- Identify which requests should create new objects.
- Write a description that explains correct usage.
Solutions to the Homework/Exercises
- A minimal interface could include show(message), dismiss(), and dismissed event.
- New surfaces or handles should use new_id.
- The description should list required call order and valid states.
2.2 wayland-scanner and Generated Bindings
Fundamentals
wayland-scanner reads XML and generates C headers for client and server. The client headers include proxy structures and request functions; the server headers include resource structures and event emitters. The generated code ensures argument marshalling matches the XML. Understanding what is generated helps you debug build errors and integrate the protocol into your project. You also need to integrate the generated headers into your build system so they are rebuilt when the XML changes. Treat the generated headers as build artifacts, not source, and keep them in a predictable output directory. This practice prevents stale bindings and keeps your builds reproducible.
Deep Dive into the concept
The scanner can generate three outputs: client header, server header, and protocol code. The client header defines a struct wl_interface for each interface and inline functions for requests. The server header defines resource types and event sending functions, plus the interface definition for server-side. The protocol code contains the protocol’s wire format description. In most projects, you generate the headers at build time using a custom rule in Make or Meson. This ensures that any XML change regenerates the bindings automatically.
A subtlety is include paths. wayland-scanner output often includes a header guard and expects to be included with a consistent path. If you generate to a build directory, you must add that directory to your include search path. Another subtlety is versioning: the generated code uses the interface version from XML; if you change it, you must rebuild all clients and servers. That is why protocols are usually kept in a shared repo and installed as part of wayland-protocols or your compositor’s install step.
The generated code is intentionally thin. It does not implement any logic. For the server side, you must provide an implementation struct with function pointers for each request. You also must define how to create resources and how to attach them to your internal objects. For the client side, you must create a proxy by binding to the global, then call generated request functions. The scanner does not protect you from invalid call ordering; it only ensures the wire format is correct. This is why you need explicit state tracking in your code.
Debugging scanner output is helpful. If you are unsure why a request crashes, inspect the generated header to see argument types and order. If you pass the wrong type (e.g., a pointer instead of an int), you will corrupt the message and likely get disconnected. This project is a good place to read the generated code at least once so you understand the mapping between XML and C.
In practice, you should also decide whether to install your protocol XML and headers system-wide or keep them private to your compositor. System-wide installation is useful if multiple clients will use the protocol, but it also creates a compatibility burden. Private protocols are easier to evolve but harder for third parties to adopt. This trade-off is a real design choice and should be documented in your README so future you knows which path you chose and why.
Another practical detail is the protocol-code output. Some build systems compile the generated protocol code into a shared library that client applications link against, while others embed it directly into each client. Both work, but the second option is simpler for small projects and avoids ABI concerns. If you plan to support multiple languages, you may also generate bindings in Rust or Python using the same XML, which is why keeping the XML clean and well-documented pays dividends beyond C. That choice impacts packaging, ABI stability, and deployment.
How this fit on projects
You will use scanner output in Section 3.5 (data formats), Section 4 architecture, and Section 5 implementation phases when integrating your custom protocol.
Definitions & key terms
- wayland-scanner -> tool that generates protocol bindings
- client header -> generated C header for client proxies
- server header -> generated C header for server resources
- protocol code -> generated wire format description
- build rule -> step that regenerates code when XML changes
Mental Model Diagram (ASCII)
protocol.xml -> wayland-scanner
| |
v v
client-protocol.h server-protocol.h
How It Works (Step-by-Step)
- Write protocol XML.
- Run wayland-scanner client-header and server-header.
- Include generated headers in your build.
- Link the protocol code or install it with your compositor.
Invariants:
- Generated headers must match the XML exactly.
- Client and server must use the same version.
Failure modes:
- Outdated headers -> protocol mismatches.
- Wrong include path -> build errors.
Minimal Concrete Example
wayland-scanner client-header demo.xml demo-client-protocol.h
wayland-scanner server-header demo.xml demo-server-protocol.h
Common Misconceptions
- “The scanner implements the protocol logic.” -> It only generates marshalling code.
- “I can hand-edit generated headers.” -> They will be overwritten; edit XML instead.
Check-Your-Understanding Questions
- Why should scanner output be generated during the build?
- What happens if client and server use different XML versions?
- Where do you define request handler functions?
Check-Your-Understanding Answers
- So changes to XML automatically regenerate correct bindings.
- Requests and events will not match, causing protocol errors.
- In your server implementation struct, not in the generated files.
Real-World Applications
- All Wayland protocols in wayland-protocols are generated this way.
Where You’ll Apply It
- In this project: Section 5.1 setup, Section 5.10 Phase 1.
- Also used in: P01 Bare-Metal Wayland Client for protocol headers.
References
- wayland-scanner manual
- wayland-protocols build scripts
Key Insights
Generated bindings are wire-format correct but logic-free; you supply the state machine.
Summary
Understand scanner output so you can integrate your protocol and debug mismatches.
Homework/Exercises to Practice the Concept
- Add a build rule that regenerates headers when XML changes.
- Inspect the generated client header and find request function signatures.
- Break the XML intentionally and observe the compiler errors.
Solutions to the Homework/Exercises
- Use a Make rule with XML as a dependency of the generated header.
- Look for functions named after the request names.
- The scanner will fail or the compiler will show missing symbols.
2.3 Client/Server State Machines and Message Flow
Fundamentals
Wayland does not define request ordering for you. Your protocol must define a state machine. Both client and server must implement that state machine consistently. A good protocol has clear required sequences (e.g., create -> configure -> commit -> destroy). If the client calls requests in the wrong order, the server should disconnect or send an error event. This is how Wayland maintains correctness. A small, explicit state machine also makes it easier to reason about resources and cleanup paths. When both sides share the same state model, the protocol feels deterministic instead of magical. This helps testing and documentation for both sides.
Deep Dive into the concept
Wayland messages are asynchronous. The client sends requests, the server sends events, and there is no implicit synchronization. This means your protocol must be explicit about ordering and about which requests are valid in each state. For example, suppose you design a “notification” protocol. You might require that the client call show() before update() or dismiss(). If update() is called before show(), that is a protocol error. You must implement this check in the server, and you should document it in the XML.
State machines should be simple. Each object should have a small number of states: created, configured, active, destroyed. Avoid hidden states that require implicit knowledge. If you need complex behavior, create a separate object. For example, a protocol for screen capture might use a session object that transitions from “ready” to “capturing” to “stopped”. Each transition should correspond to a request or event. This pattern makes it clear when resources are allocated and freed.
When you implement the server handlers, you should store per-resource state in a struct that you associate with the wl_resource. wlroots provides helpers for this, but you can also use wl_resource_set_user_data. On destroy, you must free that state and detach any listeners. On the client side, you should also store state: whether the server has acknowledged a request, whether an object is active, and what version was negotiated. This avoids sending illegal requests. It also makes testing easier because you can assert on state transitions.
Message flow diagrams help. When you debug a client-server protocol, capture the sequence of requests and events with WAYLAND_DEBUG and compare to your expected state machine. If something arrives out of order, you have a bug. This is why a deterministic golden path demo is important: it gives you a baseline sequence to compare against.
Finally, remember that Wayland allows out-of-order events if multiple objects are involved. If your protocol uses multiple objects, you must be careful to handle interleaving correctly. This is another argument for keeping the protocol small and focused. A clear state machine is the difference between a robust extension and a fragile one.
One practical technique is to write a tiny “fuzzer” client that sends requests in odd orders or with invalid arguments. Even a handful of negative tests will reveal assumptions you did not realize you were making. When the server rejects those requests deterministically, you gain confidence that the protocol is robust rather than lucky. This is especially important when you expect third-party clients to interact with your compositor.
Another useful technique is to add sequence numbers or serials to events when ordering matters. You do not always need them, but they help clients map asynchronous events to requests. If your protocol can trigger multiple concurrent operations, a serial lets the client match responses to the correct request and avoid race conditions. This small design choice can save hours of debugging later. Serials are cheap but valuable for debugging and test traces.
How this fit on projects
This concept is required for Section 3.2 functional requirements, Section 5.10 implementation phases, and Section 6 testing. You will explicitly encode and validate a state machine.
Definitions & key terms
- state machine -> defined set of states and transitions for an object
- protocol error -> compositor disconnect due to invalid request
- interleaving -> events arriving from multiple objects in mixed order
- user data -> custom struct attached to wl_resource or proxy
Mental Model Diagram (ASCII)
Created -> Configured -> Active -> Destroyed
| | |
| v v
| events cleanup
v
requests
How It Works (Step-by-Step)
- Define states in documentation.
- Implement server-side checks in each request handler.
- Update state on valid transitions.
- Send events to move the client forward.
- Destroy resources on invalid transitions.
Invariants:
- Each request is valid only in specified states.
- Destroy transitions free all resources.
Failure modes:
- Missing validation -> inconsistent state or crashes.
- Overly strict validation -> unnecessary disconnects.
Minimal Concrete Example
if (!state->configured) {
wl_resource_post_error(resource, ERROR_NOT_CONFIGURED,
"must configure before commit");
return;
}
Common Misconceptions
- “Events arrive in the order I expect.” -> They can interleave.
- “I can ignore invalid requests.” -> That often leads to state corruption.
Check-Your-Understanding Questions
- Why is a protocol state machine required in Wayland?
- What is the consequence of accepting invalid request order?
- How does WAYLAND_DEBUG help with protocol debugging?
Check-Your-Understanding Answers
- Because requests/events are asynchronous and need explicit ordering rules.
- You may corrupt state or behave unpredictably.
- It shows the raw request/event sequence for comparison to your expected flow.
Real-World Applications
- xdg-shell uses strict configure/ack ordering.
- Input protocols enforce grab lifetimes with explicit state transitions.
Where You’ll Apply It
- In this project: Section 3.2, Section 5.10 Phase 2, Section 6 Testing Strategy.
- Also used in: P01 Bare-Metal Wayland Client for client-side state management.
References
- Wayland protocol specification sections on errors
Key Insights
State machines are the only reliable way to coordinate asynchronous protocol interactions.
Summary
Define, document, and enforce state transitions for each custom object.
Homework/Exercises to Practice the Concept
- Draw a state machine for your protocol object.
- Add validation checks for each request.
- Simulate an invalid request in a test client and verify disconnect.
Solutions to the Homework/Exercises
- Include states: created, configured, active, destroyed.
- Use flags in your resource struct and post errors on invalid transitions.
- Send an out-of-order request and verify wl_display.error.
2.4 Versioning, Compatibility, and Error Semantics
Fundamentals
Wayland protocols are versioned and must remain backward compatible. You can only add requests and events in new versions. You cannot remove or change existing ones. This means you must plan for evolution. You should also define explicit error semantics so clients know how the server responds to invalid input. A versioned protocol with clear error behavior is easier to maintain and safer for clients. Even a one-person project benefits from this discipline because it prevents ad hoc changes that break clients later. Documenting errors in the XML makes client implementations predictable and easier to debug. Clear errors also reduce support time.
Deep Dive into the concept
Versioning is not optional in Wayland; it is how the ecosystem evolves. When a compositor advertises a global at version N, clients can choose any version <= N. The client should bind to the highest version it supports. Your protocol must behave correctly at every version it advertises. If you only implement version 1 features, you should not advertise version 2. This is why version numbers are not a marketing tool but a contract.
Compatibility also depends on careful use of optional features. If you want to add a feature, you can add a new request or event in a higher version or create a new interface. The latter is often cleaner because it reduces complexity in the existing interface. For example, xdg-activation is a separate protocol instead of adding activation methods to xdg-shell. This pattern keeps protocols small and reduces the number of states each object must handle.
Error semantics are equally important. Many protocols define error enums in the XML and use wl_resource_post_error to disconnect on protocol violations. The downside is that clients die on mistakes. Sometimes a softer approach is better: send a custom error event and ignore the invalid request. That preserves client stability but requires more code. For this project, pick a clear approach and document it. If you choose disconnect-on-error, be explicit in the XML description. If you choose soft errors, define an error event and specify which requests produce it.
Testing compatibility is also part of the story. You should write a client that binds to an older version and ensure it still works. You should also test invalid requests to make sure the errors are deterministic. This is where your golden path transcript is useful: it gives a known-good sequence of messages to compare against when you add features. If you add version 2 later, you can run the same client and confirm behavior is unchanged.
Another subtle compatibility issue is feature detection. Some protocols add new events rather than new requests, which means old clients will silently ignore those events. That can be fine if the events are informational, but it can be dangerous if the events are required for correctness. When designing version 2, prefer requests for required behavior and events for optional notifications, and clearly document which is which in the XML description.
You should also understand the “since” attribute in Wayland XML. It lets you annotate which version added a request or event. This is more than documentation: it is the key to generating bindings that correctly guard newer requests. If you add a request in version 2 but forget to mark it, clients may call it against version 1 servers and crash. Taking the time to annotate versions in XML prevents subtle compatibility regressions.
Unstable protocols are another compatibility tool. By using an unstable namespace and making it clear that the protocol may change, you give yourself freedom to iterate without breaking stable clients. When the protocol matures, you can publish a stable version with a new name. This staged approach is common in the Wayland ecosystem and helps you evolve a protocol responsibly.
How this fit on projects
This concept drives Section 3.2 functional requirements, Section 3.6 edge cases, and Section 6 testing. It also shapes how you design your XML in Section 2.1.
Definitions & key terms
- backwards compatible -> old clients still work with new servers
- error enum -> named error codes in the XML
- wl_resource_post_error -> server-side error mechanism
- version negotiation -> client chooses a version at bind time
Mental Model Diagram (ASCII)
Server advertises v3
Client supports v2
Client binds v2
Server must behave as v2 for this client
How It Works (Step-by-Step)
- Advertise a global with a version you fully support.
- Client binds at a chosen version.
- Server checks version before accepting requests.
- On invalid request, follow documented error policy.
Invariants:
- Behavior for version N clients must remain stable.
- Errors must be deterministic and documented.
Failure modes:
- Advertising unsupported version -> client crash.
- Silent failure -> hard-to-debug behavior.
Minimal Concrete Example
if (wl_resource_get_version(resource) < 2) {
wl_resource_post_error(resource, ERROR_UNSUPPORTED,
"request requires v2");
return;
}
Common Misconceptions
- “Version numbers are optional.” -> They are mandatory for compatibility.
- “Errors should be silent.” -> Silent failure breaks debugging.
Check-Your-Understanding Questions
- Why must a compositor not advertise a version it does not implement?
- What are two ways to handle invalid requests?
- How can you test compatibility across versions?
Check-Your-Understanding Answers
- It would allow clients to call unimplemented requests.
- Disconnect with protocol error, or send a custom error event.
- Bind with older versions and run golden path tests.
Real-World Applications
- xdg-shell versioning shows how to add features without breaking clients.
Where You’ll Apply It
- In this project: Section 3.2, Section 3.6, Section 6, and the XML definitions.
- Also used in: P02 Simple Wayland Compositor for protocol versioning.
References
- Wayland protocol versioning guidelines
Key Insights
Versioning is a promise; error semantics are how you enforce it.
Summary
Plan for evolution, and make errors explicit and deterministic.
Homework/Exercises to Practice the Concept
- Add a version 2 request and clamp behavior for version 1 clients.
- Write a client that binds at version 1 and verify it still works.
- Trigger an error and verify the error code is stable.
Solutions to the Homework/Exercises
- Check wl_resource_get_version before accepting the request.
- Bind with version=1 in wl_registry_bind.
- Use WAYLAND_DEBUG and compare the error output.
3. Project Specification
3.1 What You Will Build
You will build a custom protocol extension named demo_notification that:
- Lets clients request a notification to be displayed by the compositor
- Supports update and dismiss requests
- Sends events when notifications are shown or dismissed
Excluded:
- Networked notifications
- Persistent storage
3.2 Functional Requirements
- Protocol XML: Define an interface with show, update, dismiss requests and shown/dismissed events.
- Generated bindings: Use wayland-scanner to generate client and server headers.
- Server implementation: The compositor handles requests and emits events.
- Client implementation: A demo client sends show/update/dismiss and logs events.
- Versioning: Provide a version 1 spec and document upgrade path.
3.3 Non-Functional Requirements
- Performance: Event handling should be constant time per request.
- Reliability: Invalid request ordering should produce deterministic errors.
- Usability: Demo client should print clear logs for each event.
3.4 Example Usage / Output
$ ./demo-client "Hello"
[proto] show "Hello"
[event] shown id=1
3.5 Data Formats / Schemas / Protocols
- Protocol XML file: demo-notification.xml
- Generated headers: demo-notification-client.h, demo-notification-server.h
3.6 Edge Cases
- update called before show -> protocol error
- dismiss called twice -> ignored or error (documented)
- client disconnects -> server cleans up notifications
3.7 Real World Outcome
3.7.1 How to Run (Copy/Paste)
# Generate bindings
wayland-scanner client-header demo-notification.xml demo-notification-client.h
wayland-scanner server-header demo-notification.xml demo-notification-server.h
# Build
cc -O2 -Wall -o demo-client demo-client.c $(pkg-config --cflags --libs wayland-client)
cc -O2 -Wall -o demo-server demo-server.c $(pkg-config --cflags --libs wayland-server)
3.7.2 Golden Path Demo (Deterministic)
- Fixed message: “Hello Wayland”
- Fixed id: 1 (deterministic allocation in demo)
- Fixed sequence: show -> shown -> dismiss -> dismissed
3.7.3 If CLI: Exact Terminal Transcript
$ ./demo-client "Hello Wayland"
[proto] show "Hello Wayland"
[event] shown id=1
[proto] dismiss id=1
[event] dismissed id=1
Exit codes:
- 0 on success
- 1 if protocol global missing
3.7.4 If API
| Endpoint | Description | |———-|————-| | demo_notification.show | Create a notification | | demo_notification.update | Update text | | demo_notification.dismiss | Dismiss |
3.7.5 Failure Demo (Deterministic)
$ ./demo-client --update-without-show
[Error] protocol violation: update before show
[Exit] code=1
4. Solution Architecture
4.1 High-Level Design
protocol.xml -> scanner -> client + server headers
| |
v v
demo-client <---- socket ----> demo-server (compositor)
4.2 Key Components
| Component | Responsibility | Key Decisions | |———–|—————-|—————| | XML spec | Define interface and semantics | Keep version 1 minimal | | Server handlers | Validate state, emit events | Disconnect or error event | | Client demo | Drive protocol and log events | Deterministic sequence |
4.3 Data Structures (No Full Code)
struct notification {
uint32_t id;
char *message;
bool active;
};
4.4 Algorithm Overview
Key Algorithm: State Validation
- On show: create notification state, emit shown.
- On update: validate active state, update message.
- On dismiss: validate active, emit dismissed, destroy.
Complexity Analysis:
- Time: O(1) per request
- Space: O(n) notifications
5. Implementation Guide
5.1 Development Environment Setup
sudo apt install wayland-protocols wayland-scanner
5.2 Project Structure
custom-protocol/
|-- protocol/
| `-- demo-notification.xml
|-- src/
| |-- demo-client.c
| |-- demo-server.c
| `-- protocol-build.c
|-- generated/
| |-- demo-notification-client.h
| `-- demo-notification-server.h
`-- Makefile
5.3 The Core Question You’re Answering
“How do I extend Wayland with my own protocol while staying compatible and correct?”
5.4 Concepts You Must Understand First
- XML interface design
- wayland-scanner generated bindings
- Protocol state machines
- Versioning and error semantics
5.5 Questions to Guide Your Design
- Which requests are required vs optional?
- What should happen on invalid sequences?
- How will you test backward compatibility?
5.6 Thinking Exercise
Sketch a state machine for your notification object and identify invalid transitions.
5.7 The Interview Questions They’ll Ask
- How does wayland-scanner relate to the XML protocol definition?
- Why is backward compatibility strict in Wayland?
- How do you enforce valid request ordering?
5.8 Hints in Layers
Hint 1: Start minimal Define only show and dismiss for version 1.
Hint 2: Use errors carefully Choose whether to disconnect or send an error event.
5.9 Books That Will Help
| Topic | Book | Chapter | |——-|——|———| | Protocol design | “The Wayland Book” | protocol extension chapters | | API design | “Designing Data-Intensive Applications” | interface evolution sections |
5.10 Implementation Phases
Phase 1: Foundation (2-3 days)
Goals:
- Write XML spec
- Generate bindings
Tasks:
- Define interface and requests.
- Generate headers with wayland-scanner.
Checkpoint: Generated headers compile.
Phase 2: Core Functionality (3-4 days)
Goals:
- Implement server handlers
- Implement client demo
Tasks:
- Add request handlers and state validation.
- Emit events and log them in client.
Checkpoint: Demo client shows/dismisses successfully.
Phase 3: Polish and Edge Cases (2 days)
Goals:
- Add errors and tests
Tasks:
- Implement invalid sequence handling.
- Add tests for version negotiation.
Checkpoint: Invalid sequences fail deterministically.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale | |———-|———|—————-|———–| | Error handling | disconnect, error event | disconnect for v1 | simpler and consistent | | Versioning | bump interface, add new interface | bump with new requests | minimal complexity |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples | |———-|———|———-| | Unit Tests | Validate state machine | update before show | | Integration Tests | Client/server messaging | show -> shown -> dismiss | | Compatibility Tests | Versioned behavior | bind v1, ignore v2 features |
6.2 Critical Test Cases
- Update before show -> protocol error.
- Double dismiss -> deterministic outcome.
- Bind lower version -> still works.
6.3 Test Data
message="Hello Wayland"
id=1
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution | |———|———|———-| | Changing XML without regen | build errors or mismatch | regenerate headers | | Missing state checks | inconsistent behavior | enforce state machine | | Version mismatch | client disconnect | clamp version |
7.2 Debugging Strategies
- Use WAYLAND_DEBUG=1 to inspect protocol flow.
- Log every request and event on both sides.
7.3 Performance Traps
- Overly chatty protocols increase IPC overhead.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add a “priority” field to the notification.
8.2 Intermediate Extensions
- Add version 2 with optional actions (buttons).
8.3 Advanced Extensions
- Add a new interface for notification groups.
9. Real-World Connections
9.1 Industry Applications
- Desktop portals and compositor protocols are designed this way.
9.2 Related Open Source Projects
- wayland-protocols: real-world protocol specs
- Sway protocols: custom compositor extensions
9.3 Interview Relevance
- Discuss backward compatibility and protocol evolution.
10. Resources
10.1 Essential Reading
- Wayland protocol XML specification
- wayland-protocols repository docs
10.2 Video Resources
- Wayland protocol design talks
10.3 Tools & Documentation
- wayland-scanner: protocol generation tool
10.4 Related Projects in This Series
11. Self-Assessment Checklist
11.1 Understanding
- I can explain how XML maps to generated bindings.
- I can justify versioning decisions.
- I can describe the protocol state machine.
11.2 Implementation
- Client and server exchange messages correctly.
- Errors are deterministic and documented.
- Generated headers are rebuilt on XML changes.
11.3 Growth
- I can extend the protocol without breaking clients.
- I can read and understand other protocols in wayland-protocols.
12. Submission / Completion Criteria
Minimum Viable Completion:
- Custom XML protocol defined and generated.
- Demo client and server exchange messages.
- Deterministic success and failure demos.
Full Completion:
- All minimum criteria plus:
- Version negotiation and error tests.
- Documentation describing call order.
Excellence (Going Above & Beyond):
- Add a second interface and demonstrate compatibility.
- Publish the protocol spec with examples.