Project 4: Wayland Panel/Bar (Layer Shell)

Build a top-panel status bar using the wlr-layer-shell protocol, rendered with wl_shm and updated on a timer.

Quick Reference

Attribute Value
Difficulty Intermediate
Time Estimate 1-2 weeks
Main Programming Language C (Alternatives: C++, Rust, Zig)
Alternative Programming Languages C++, Rust, Zig
Coolness Level Level 4: “Shell component developer”
Business Potential Level 2: Product UI Component
Prerequisites Project 1 (Wayland client basics)
Key Topics wlr-layer-shell, buffer scaling, timers

1. Learning Objectives

By completing this project, you will:

  1. Bind and use zwlr_layer_shell_v1 to create a panel surface.
  2. Correctly handle configure events, size negotiation, and exclusive zones.
  3. Render a panel with text/icons using wl_shm buffers.
  4. Support output scale and update only changed regions.
  5. Implement a low-CPU update loop (timer-driven).

2. All Theory Needed (Per-Concept Breakdown)

2.1 Layer-Shell Protocol Semantics

Fundamentals

wlr-layer-shell is a Wayland protocol that lets clients create desktop shell surfaces such as panels, docks, wallpapers, and overlays. Instead of being normal windows, these surfaces have layers (background, bottom, top, overlay) and anchors (top, bottom, left, right) that control placement. The compositor decides final size and position via configure events. The client can request size, set margins, and specify an exclusive zone to reserve screen space. This protocol is essential for building shell components that are not normal windows.

Deep Dive into the Concept

The layer-shell protocol introduces a different surface role from xdg_toplevel. A layer surface is created by first making a regular wl_surface, then calling zwlr_layer_shell_v1_get_layer_surface. The client chooses a layer (background, bottom, top, overlay) and a namespace string to identify itself. The compositor then treats this surface as a special shell component, not as a window. This distinction is crucial because it changes how the compositor positions and manages it.

Anchors define how the surface is attached to the output. If you anchor to the top and left and right, the surface will stretch horizontally across the top edge. Setting size to 0 for width or height signals that the compositor can determine that dimension. For example, width=0 and anchors left+right means “full width.” If you anchor only to top and right, you get a panel aligned to the top-right corner with a fixed width.

Exclusive zones reserve space. If you set an exclusive zone equal to the panel’s height, the compositor will keep normal windows out of that region, so they do not overlap your bar. This is how traditional status bars work: they occupy the top strip and windows avoid it. If you set exclusive zone to 0, the bar becomes an overlay and windows can go underneath. This is how overlays, notifications, and OSDs work.

Configure events drive the handshake. The compositor sends zwlr_layer_surface_v1.configure(serial, width, height) to tell you the actual size you must use. You must acknowledge it with ack_configure before committing a buffer. This is similar to xdg_surface.configure, but the sizing semantics differ. You should always use the configured size for your buffer, even if it differs from your requested size. This is especially important on multi-output systems where output sizes vary.

Layer-shell is commonly used by real desktop components: Waybar and Yambar use it for top/bottom bars, Sway uses it for internal overlays, and many compositors use it for backgrounds. It is an unstable protocol (zwlr_ prefix) which means its API may change. When building production software, you must be prepared to update with protocol changes or lock to a specific version.

Another key consideration is that multiple layer surfaces can exist on the same output. The compositor controls stacking order: background layer surfaces are below normal windows, top layer surfaces are above, and overlay surfaces are above everything. This is how you can build notification popups or screen dimmers. Your panel will usually live in the top layer.

The layer-shell protocol also interacts with input. Typically, panels should receive pointer input (for buttons) but not keyboard focus. You can configure input regions and keep focus on the active window. While layer-shell itself doesn’t define input regions, you can use wl_surface_set_input_region to limit the panel’s clickable area. This prevents the panel from stealing input in unused areas.

In short, layer-shell gives you a privileged surface role designed for shell UI. It is the right tool for bars, docks, wallpapers, and overlays. Mastering its configure handshake, anchoring, and exclusive zones is the key to building a correct panel.

How This Fits on the Project

You will create a top bar anchored to the top edge with an exclusive zone equal to its height. You must handle configure events, ack, and render at the negotiated size.

Definitions & Key Terms

  • Layer -> Stacking layer (background/bottom/top/overlay).
  • Anchor -> Edge constraints for positioning.
  • Exclusive Zone -> Reserved space that other windows avoid.
  • Namespace -> Identifier string for compositor policy.

Mental Model Diagram (ASCII)

Output
+----------------------------------+
| [TOP LAYER]   Panel (exclusive)  |
|----------------------------------|
| Normal windows                   |
|                                  |
+----------------------------------+

How It Works (Step-by-Step)

  1. Bind zwlr_layer_shell_v1 from registry.
  2. Create wl_surface and get layer_surface.
  3. Set layer, anchors, size, exclusive zone.
  4. Commit and wait for configure.
  5. Ack configure and render.

Minimal Concrete Example

layer = zwlr_layer_shell_v1_get_layer_surface(shell, surface, output,
    ZWLR_LAYER_SHELL_V1_LAYER_TOP, "panel");
zwlr_layer_surface_v1_set_anchor(layer,
    ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
    ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
    ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
zwlr_layer_surface_v1_set_exclusive_zone(layer, bar_height);

Common Misconceptions

  • “Layer surfaces are just windows.” -> They have special roles and placement rules.
  • “I can ignore configure events.” -> You must ack configure before commit.
  • “Exclusive zone is optional.” -> Without it, windows can overlap your bar.

Check-Your-Understanding Questions

  1. What does anchoring left+right mean for width?
  2. What is the purpose of the exclusive zone?
  3. Why must you ack configure before rendering?

Check-Your-Understanding Answers

  1. It stretches the surface across the output width.
  2. It reserves screen space so normal windows avoid the bar.
  3. It confirms the size negotiated by the compositor.

Real-World Applications

  • Status bars (Waybar, Yambar)
  • Docks and launchers
  • Wallpapers and overlays

Where You’ll Apply It

References

  • wlr-layer-shell protocol documentation.
  • Sway and Waybar source code.

Key Insights

Layer-shell is the compositor-approved way to build desktop UI components.

Summary

Understanding layer-shell semantics makes it possible to create panels that integrate cleanly with the compositor’s layout and policy.

Homework/Exercises to Practice the Concept

  1. Create a layer surface without exclusive zone and observe overlap.
  2. Change anchors and observe panel placement.
  3. Use different layers (bottom vs top) and compare stacking.

Solutions to the Homework/Exercises

  1. Windows will overlap the bar if exclusive zone is 0.
  2. Anchors determine stretch and alignment.
  3. Top layer appears above windows; bottom layer appears below.

2.2 Rendering Text and UI with wl_shm

Fundamentals

A panel must render text, icons, and backgrounds. The simplest approach is CPU rendering into a wl_shm buffer. You can use a tiny rasterizer (or a library like Cairo or pixman) to draw rectangles and text. The output is just pixels in a buffer. Since a panel updates frequently (clock, status), you must manage buffer lifetimes and only redraw what changes.

Deep Dive into the Concept

Rendering with wl_shm is simple but requires careful planning for text and layout. Unlike toolkits, you are responsible for drawing every pixel. A common approach is to use a lightweight 2D library (Cairo or pixman) to draw rectangles, lines, and text onto a memory buffer. Cairo is easy to use but adds a dependency; pixman is lower-level but already part of many graphics stacks. You can also implement a basic bitmap font for maximum control and zero dependencies.

The rendering pipeline is: allocate buffer -> draw background -> draw text/icons -> attach -> commit. The background is often a solid color, while the text is rendered using a font. If you use Cairo, you create an image surface backed by your wl_shm memory and call cairo_show_text. If you use a bitmap font, you blit glyph bitmaps into the buffer. Either way, the buffer is just pixels; the compositor does not know or care what they represent.

Layout is another challenge. A panel usually has left, center, and right sections (e.g., workspaces, title, clock). You must compute positions based on text width. If you use Cairo, you can measure text extents. If you use a bitmap font, you can approximate width by character count times glyph width. For a minimal project, fixed-width fonts keep things simple.

Text rendering also benefits from caching. If your panel shows repeated labels (like workspace numbers), you can pre-render those glyphs into small off-screen buffers and composite them into the main buffer. This reduces CPU usage and makes updates more predictable. Even if you skip caching, understanding the idea is useful: it mirrors how real UI toolkits cache glyphs and textures. For a simple project, you can cache just the clock digits, updating only the digits that change.

You must also handle updates efficiently. A clock updating every second should not require redrawing the whole panel if only the time changes. You can track dirty regions and damage only those rectangles. However, for a small panel, full redraw is acceptable, as long as you do it at a low frequency (e.g., once per second). The key is not to busy-loop: use a timer and redraw only when the content changes.

Color formats matter. Most wl_shm renderers use XRGB8888 or ARGB8888. If you draw text with alpha, you need ARGB8888. If you want a fully opaque panel, XRGB8888 is enough. But many panels use translucent backgrounds, which require alpha. Ensure your compositor supports the format you choose by checking wl_shm.format events.

Finally, text rendering introduces scaling challenges. A font size defined in logical pixels must be scaled for HiDPI outputs. If the output scale is 2, you should draw the text at twice the pixel size to keep it crisp. You then set wl_surface_set_buffer_scale so the compositor knows the buffer is scaled. If you ignore scaling, text will appear blurry or too small.

Alpha blending and transparency add another layer of complexity. If you use ARGB buffers, your panel can have translucent backgrounds, which look modern but require careful blending. A fully transparent pixel is still part of the buffer; the compositor blends it with the background. If you leave uninitialized memory in the buffer, you might get garbage pixels with random alpha values, resulting in flicker or artifacts. This is why you should always clear the buffer or explicitly draw every pixel when using transparency.

How This Fits on the Project

You will render a panel with a left title, center placeholder, and right clock, all drawn into a wl_shm buffer with a deterministic layout.

Definitions & Key Terms

  • Rasterization -> Converting text/shapes into pixels.
  • Glyph -> Bitmap representation of a character.
  • Dirty Region -> Portion of the buffer that changed.
  • Alpha -> Transparency channel in ARGB formats.

Mental Model Diagram (ASCII)

[Panel Buffer]
+----------------------------------------------------+
| Workspaces |         Title        |     12:34:56   |
+----------------------------------------------------+

How It Works (Step-by-Step)

  1. Allocate a wl_shm buffer sized to panel dimensions.
  2. Draw background color.
  3. Render text sections into buffer.
  4. Attach buffer and commit.
  5. On timer tick, update text and redraw.

Minimal Concrete Example

// Pseudocode
fill_rect(buf, 0, 0, w, h, 0x222222);
draw_text(buf, 10, 20, "Workspaces", white);
draw_text(buf, w-100, 20, "12:34:56", white);

Common Misconceptions

  • “Wayland draws my text.” -> You must render text into pixels.
  • “Full redraw every millisecond is fine.” -> It wastes CPU.
  • “Scaling is automatic.” -> You must manage buffer scale.

Check-Your-Understanding Questions

  1. Why must you handle text rendering manually in wl_shm?
  2. How do you avoid high CPU usage in a panel?
  3. Why does output scale matter for text clarity?

Check-Your-Understanding Answers

  1. Wayland only transports buffers; it does not draw UI.
  2. Use timer-driven updates and frame callbacks.
  3. Scale affects pixel density; wrong scale leads to blurry text.

Real-World Applications

  • Status bars that update system info
  • Music players and notification overlays

Where You’ll Apply It

References

  • Cairo or pixman documentation.
  • Wayland Book buffer chapter.

Key Insights

Rendering is just pixels; text is a drawing problem, not a protocol problem.

Summary

By rendering directly into wl_shm buffers, you control every pixel of your panel, at the cost of managing text layout and updates yourself.

Homework/Exercises to Practice the Concept

  1. Render a static string into the buffer.
  2. Change the background color every second.
  3. Measure CPU usage with different update rates.

Solutions to the Homework/Exercises

  1. Use a fixed-width bitmap font or Cairo text.
  2. Update the buffer in a timer callback.
  3. Lower update rate to reduce CPU usage.

2.3 Output Scale, Buffer Scale, and HiDPI

Fundamentals

Wayland separates logical size from buffer size. An output has a scale factor (e.g., 2 for HiDPI). If the panel is 1920x30 logical pixels and scale is 2, the buffer must be 3840x60 pixels. The client must set wl_surface_set_buffer_scale so the compositor interprets the buffer correctly. Ignoring scale causes blurry or incorrectly sized rendering.

Deep Dive into the Concept

HiDPI displays require more physical pixels per logical unit. Wayland handles this by letting the compositor define a scale for each output. Clients receive wl_output.scale events. The scale is an integer (for core protocols), meaning a scale of 2 indicates the buffer should have twice the resolution. The compositor then samples the buffer at the correct density. If you provide a buffer with the wrong size, the compositor will scale it, which can blur text and UI.

The client’s responsibility is to multiply its logical size by the output scale to compute the buffer size. For example, a bar height of 30 logical pixels becomes 60 buffer pixels at scale 2. You then draw at buffer pixel coordinates. This means your text rendering must also scale. If you use a fixed-size font in logical units, you must multiply the font size by the scale when drawing. Otherwise, text appears too small and blurry.

wl_surface_set_buffer_scale informs the compositor how to interpret your buffer. If you set buffer scale to 2, the compositor will treat a 3840x60 buffer as representing a 1920x30 logical area. This is how you avoid manual downscaling. If you forget to set the buffer scale, the compositor will treat the buffer as logical size, leading to a double-sized bar.

The tricky part is handling scale changes. If the output scale changes (e.g., moving to a different monitor), you must recreate buffers at the new size and re-render. Layer surfaces provide configure events which can include size changes. You should treat a new configure as a signal to reallocate buffers with the correct scale.

Note that some protocols support fractional scaling (e.g., fractional-scale-v1). If you use only core protocols, you will see integer scales only. For simplicity, this project can ignore fractional scaling, but you should be aware it exists. In production, you would need to support fractional scales by rendering at a higher resolution and using viewport or fractional-scale protocols.

Finally, scale affects input regions. If you set input regions in buffer coordinates without applying scale, your clickable areas will be off. The correct approach is to define input regions in logical coordinates (unscaled) because Wayland expects them to be in surface coordinates, not buffer pixels. This is another reason to maintain a clear separation between logical size and buffer size.

Transforms are another scaling-related detail. Outputs can be rotated or transformed (e.g., portrait displays). wlroots and compositors report a transform for each output. If you ignore transform, your panel may appear rotated or clipped incorrectly on rotated displays. For a minimal project, you can assume normal transform, but it is worth logging the transform and understanding how it would affect layout. In production, you would rotate your drawing or let the compositor handle it if you use proper buffer scale and output layout.

Multi-output scaling also complicates things: if you create a panel per output, each panel may have a different scale. That means you cannot share buffers across outputs; each panel must allocate buffers with the appropriate scale. Your code should track scale per output and update buffers when a panel moves or when an output scale changes.

How This Fits on the Project

You will detect the output scale and allocate buffers at logical_size * scale, then set buffer scale accordingly. This ensures crisp text on HiDPI displays.

Definitions & Key Terms

  • Logical Size -> Size in compositor coordinate space.
  • Buffer Size -> Size in actual pixels.
  • Buffer Scale -> Factor mapping buffer pixels to logical size.
  • HiDPI -> High pixel density displays.

Mental Model Diagram (ASCII)

Logical 1920x30  -> scale=2 -> buffer 3840x60
   |                                  |
   |---- wl_surface_set_buffer_scale(2)

How It Works (Step-by-Step)

  1. Receive output scale event.
  2. Compute buffer size = logical_size * scale.
  3. Allocate buffers and render at buffer size.
  4. Call wl_surface_set_buffer_scale(surface, scale).

Minimal Concrete Example

int scale = output_scale; // e.g., 2
int buf_w = logical_w * scale;
int buf_h = logical_h * scale;
wl_surface_set_buffer_scale(surface, scale);

Common Misconceptions

  • “Scale only affects text size.” -> It affects the entire buffer size.
  • “The compositor will fix scaling.” -> It will scale, but may blur.
  • “Input regions are in buffer pixels.” -> They are in logical coordinates.

Check-Your-Understanding Questions

  1. What happens if you ignore buffer scale on a HiDPI output?
  2. Why must you reallocate buffers when scale changes?
  3. What coordinate system is used for input regions?

Check-Your-Understanding Answers

  1. Your panel appears blurry or incorrectly sized.
  2. The buffer size no longer matches logical size.
  3. Logical surface coordinates, not buffer pixels.

Real-World Applications

  • All modern panels and toolkits handle HiDPI scaling.
  • Multi-monitor setups with mixed DPI require dynamic scaling.

Where You’ll Apply It

References

  • Wayland output scale documentation.
  • fractional-scale protocol notes.

Key Insights

Correct scaling is what separates a toy panel from a production-ready one.

Summary

Handle output scale explicitly to ensure sharp, correctly sized UI.

Homework/Exercises to Practice the Concept

  1. Print the output scale and log buffer dimensions.
  2. Simulate scale=2 and confirm buffer size doubles.
  3. Move your panel between outputs and observe scaling.

Solutions to the Homework/Exercises

  1. Log scale and computed buffer width/height.
  2. Buffer width/height should be exactly double.
  3. Reconfigure and reallocate buffers on scale change.

2.4 Timers, Damage, and Low-CPU Updates

Fundamentals

A panel updates periodically (clock, system stats). The correct approach is to use a timer and update only when necessary. Busy loops waste CPU. Wayland provides frame callbacks to synchronize drawing. You can combine a timer (e.g., timerfd) with frame callbacks: the timer triggers content updates, and you request a frame callback to render. This keeps CPU usage low.

Deep Dive into the Concept

Wayland does not provide a direct “timer” interface. Instead, you use standard Linux timers (timerfd, clock_gettime, or poll with timeouts). In a panel, you might update the clock once per second. You create a timer fd, add it to your event loop, and when it fires, you mark your content as dirty. Then, in the next frame callback, you redraw and commit. This decouples content updates from rendering. If nothing changes, you do not redraw.

Damage is the mechanism to tell the compositor which parts changed. The wl_surface_damage_buffer request lets you specify a rectangle in buffer coordinates. For a clock that updates on the right side, you can damage just that rectangle. This reduces the compositor’s work. Even if you redraw the whole buffer, you can still specify full damage for correctness.

Combining timers with frame callbacks prevents over-rendering. You set a flag needs_redraw when the timer fires. Then you request a frame callback and redraw only when the callback arrives. This ensures you render at most once per frame and only when content changed. If you draw immediately upon timer firing, you may render multiple times before the compositor is ready, wasting CPU.

A common pitfall is to call wl_surface_commit inside the timer callback without waiting for a frame callback. This can cause the compositor to schedule redundant frames. A better strategy is: timer fires -> set dirty=true -> if no frame callback pending, request one -> when frame done, draw and commit. This maintains pacing.

You also need to consider time determinism for testing. For this project, you can freeze time by using a fixed string (e.g., “12:34:56”) for the golden demo. For dynamic runs, you can use real time. The important part is to ensure updates are bounded and CPU usage remains low.

Timers should use a monotonic clock to avoid jumps when system time changes. If the system time is adjusted (e.g., NTP sync), a clock based on wall time might jump backward or forward. For a status bar, that might be acceptable, but for animations or rate-limited updates, monotonic time is safer. Using timerfd with CLOCK_MONOTONIC avoids surprises. You can still display wall-clock time by reading time() at render time while using the monotonic timer to schedule updates.

Another optimization is to coalesce multiple updates into a single frame. If multiple data sources update at once (e.g., network and battery), you can set dirty=true and request one frame. By the time the frame callback arrives, you render the current snapshot of all data. This prevents “frame storms” where multiple updates cause redundant rendering. Even in a minimal project, thinking this way prepares you for more complex UI components.

Integration with your event loop is the last piece. A Wayland client usually blocks on poll() for the display fd, but if you add a timer fd you must include it in the same poll set. When either fd is readable, you dispatch the relevant handler. This prevents the common bug of “sleeping” in a separate loop that starves Wayland events. A single unified event loop keeps your panel responsive while still allowing periodic updates.

How This Fits on the Project

You will implement a timer-driven update loop so your panel can update its clock without consuming excessive CPU.

Definitions & Key Terms

  • Timerfd -> File descriptor that triggers periodic events.
  • Dirty Flag -> Indicates content needs redraw.
  • Frame Callback -> Synchronizes rendering with compositor.

Mental Model Diagram (ASCII)

Timer tick -> mark dirty -> request frame
Frame done -> redraw -> commit -> wait

How It Works (Step-by-Step)

  1. Create a timer fd that fires every second.
  2. On timer event, mark dirty and request a frame if none pending.
  3. On frame callback, redraw and commit.
  4. Clear dirty flag.

Minimal Concrete Example

if (timer_fired) {
    dirty = true;
    if (!frame_pending) request_frame();
}

void frame_done(...) {
    if (dirty) render_and_commit();
    dirty = false;
}

Common Misconceptions

  • “I should redraw on a tight loop for smoothness.” -> That wastes CPU.
  • “Frame callback is optional.” -> It is essential for pacing.
  • “Damage is cosmetic.” -> It affects compositor efficiency.

Check-Your-Understanding Questions

  1. Why not draw directly in a timer callback?
  2. How does damage improve performance?
  3. What happens if you request multiple frames simultaneously?

Check-Your-Understanding Answers

  1. Because you should render only when the compositor is ready.
  2. It limits the region that needs recompositing.
  3. You may get redundant frames and wasted CPU.

Real-World Applications

  • Status bars update once per second without heavy CPU usage.
  • Notifications animate smoothly using frame callbacks.

Where You’ll Apply It

References

  • Linux timerfd documentation.
  • Wayland frame callback semantics.

Key Insights

Timers trigger updates; frame callbacks control when you render.

Summary

A low-CPU panel uses timers for updates and frame callbacks for pacing.

Homework/Exercises to Practice the Concept

  1. Measure CPU usage with and without frame callbacks.
  2. Change the update interval to 100ms and observe impact.
  3. Implement partial damage for only the clock area.

Solutions to the Homework/Exercises

  1. CPU should drop significantly with callbacks.
  2. Higher update rates increase CPU usage.
  3. Damage only the rectangle containing the clock text.

3. Project Specification

3.1 What You Will Build

A top panel that:

  • Uses wlr-layer-shell to anchor at the top edge
  • Renders left/center/right sections with text
  • Reserves space using exclusive zone
  • Updates a clock once per second

Included:

  • Layer-shell handshake
  • wl_shm rendering
  • Timer-based updates

Excluded:

  • Full widget system
  • GPU rendering
  • User configuration system

3.2 Functional Requirements

  1. Layer Shell Binding: Bind zwlr_layer_shell_v1 from registry.
  2. Panel Surface: Create layer surface anchored top-left-right.
  3. Exclusive Zone: Reserve bar height.
  4. Rendering: Draw background + text.
  5. Clock Updates: Update once per second using timer.
  6. Scale Handling: Respect output scale.

3.3 Non-Functional Requirements

  • Performance: < 2% CPU idle.
  • Reliability: No protocol errors under WAYLAND_DEBUG=1.
  • Usability: Panel visible and non-overlapping.

3.4 Example Usage / Output

$ ./panel
[panel] bound wlr-layer-shell v1
[panel] configured 1920x30 scale=1
[panel] rendering clock 12:34:56

3.5 Data Formats / Schemas / Protocols

  • wlr-layer-shell protocol
  • wl_shm buffer format XRGB8888

3.6 Edge Cases

  • Output scale changes (HiDPI)
  • Multiple outputs (create one panel per output)
  • Configure width/height of 0

3.7 Real World Outcome

You will see a top panel bar with text sections and a clock, occupying reserved space so windows avoid it.

3.7.1 How to Run (Copy/Paste)

cc -Wall -O2 -o panel panel.c $(pkg-config --cflags --libs wayland-client)
./panel

3.7.2 Golden Path Demo (Deterministic)

  • Render a fixed time string “12:34:56”.
  • Background color #222222, text color #ffffff.

Expected behavior: a crisp, full-width bar at the top with the fixed time.

3.7.3 Failure Demo (Deterministic)

  • Skip ack_configure and attempt commit.

Expected behavior: compositor disconnects the client.

Exit codes:

  • 0 success
  • 2 protocol violation

3.7.7 GUI Wireframe (ASCII)

+--------------------------------------------------------------------------------+
| [1] [2] [3]      My Minimal Panel                        12:34:56              |
+--------------------------------------------------------------------------------+

4. Solution Architecture

4.1 High-Level Design

Wayland client -> layer-shell -> panel surface -> shm buffers -> compositor

4.2 Key Components

Component Responsibility Key Decisions
Layer Shell Create panel surface Top layer, anchored left/right
Buffer Manager Allocate wl_shm buffers Double-buffering
Renderer Draw background and text CPU rendering
Timer Trigger updates timerfd (1s)

4.3 Data Structures (No Full Code)

struct panel_state {
    struct wl_surface *surface;
    struct zwlr_layer_surface_v1 *layer;
    struct buffer bufs[2];
    int width, height, scale;
    bool configured;
};

4.4 Algorithm Overview

Key Algorithm: Timer-Driven Redraw

  1. Timer tick marks dirty.
  2. Frame callback triggers redraw.
  3. Commit buffer.

Complexity Analysis:

  • Time: O(width * height) per redraw
  • Space: O(width * height)

5. Implementation Guide

5.1 Development Environment Setup

sudo apt install build-essential pkg-config libwayland-dev

5.2 Project Structure

panel/
+-- src/
|   +-- main.c
|   +-- render.c
|   +-- timer.c
+-- README.md

5.3 The Core Question You’re Answering

“How do desktop shell components reserve screen space and render in Wayland?”

5.4 Concepts You Must Understand First

  1. Layer-shell semantics
  2. wl_shm rendering
  3. Output scaling
  4. Timer-driven updates

5.5 Questions to Guide Your Design

  1. How will you layout left/center/right text?
  2. How will you handle multiple outputs?
  3. How will you avoid high CPU usage?

5.6 Thinking Exercise

Design a layout with left/center/right sections and define how text widths are measured.

5.7 The Interview Questions They’ll Ask

  1. “What is the difference between xdg_toplevel and layer-shell?”
  2. “Why does a panel use exclusive zones?”
  3. “How do you handle HiDPI scaling?”

5.8 Hints in Layers

Hint 1: Start with a fixed-size bar on a single output.

Hint 2: Log configure events and use the provided size.

Hint 3: Update the clock with a timerfd once per second.

5.9 Books That Will Help

Topic Book Chapter
Wayland protocols “The Wayland Book” Shell protocols
Linux IPC “The Linux Programming Interface” Timers

5.10 Implementation Phases

Phase 1: Layer Shell Setup (2-3 days)

Goals:

  • Bind layer shell
  • Create panel surface

Tasks:

  1. Bind zwlr_layer_shell_v1.
  2. Create layer surface and set anchors.

Checkpoint: Panel appears (blank).

Phase 2: Rendering (3-4 days)

Goals:

  • Allocate buffers
  • Render background/text

Tasks:

  1. Implement wl_shm buffers.
  2. Draw text into buffer.

Checkpoint: Panel shows static text.

Phase 3: Updates and Scaling (3-4 days)

Goals:

  • Add clock updates
  • Handle output scale

Tasks:

  1. Use timerfd for updates.
  2. Implement scale-aware buffers.

Checkpoint: Clock updates without high CPU.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Rendering Cairo vs bitmap bitmap fewer deps
Update rate 1s vs 100ms 1s low CPU
Exclusive zone on/off on avoid overlap

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Rendering Tests Validate pixels compare checksum of buffer
Protocol Tests Ensure configure/ack log events
Performance Tests CPU usage top idle <2%

6.2 Critical Test Cases

  1. Configure Handling: Panel appears after ack.
  2. Exclusive Zone: Windows avoid bar.
  3. Scale: HiDPI scale renders crisp text.

6.3 Test Data

Fixed time string: 12:34:56
Bar height: 30

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
Missing ack_configure Panel invisible Ack configure before commit
Wrong buffer size Blurry or clipped Use scale * logical size
Tight redraw loop High CPU Use timer + frame callback

7.2 Debugging Strategies

  • Use WAYLAND_DEBUG=1 to confirm configure/ack.
  • Log output scale and buffer dimensions.
  • Measure CPU usage with top.

7.3 Performance Traps

  • Updating too frequently.
  • Redrawing entire bar for small changes.

8. Extensions & Challenges

8.1 Beginner Extensions

  • Add a workspace indicator on the left.
  • Add a battery percentage on the right.

8.2 Intermediate Extensions

  • Add clickable areas for launching apps.
  • Support multiple outputs.

8.3 Advanced Extensions

  • Implement tray icons via custom protocol.
  • Support fractional scaling with viewporter.

9. Real-World Connections

9.1 Industry Applications

  • Desktop panels (Waybar, Yambar)
  • Kiosk status displays
  • Waybar (layer-shell panel)
  • Swaybar

9.3 Interview Relevance

  • Understanding shell protocols is valuable for UI systems roles.

10. Resources

10.1 Essential Reading

  • wlr-layer-shell protocol documentation
  • The Wayland Book (shell protocols)

10.2 Video Resources

  • Waybar/Wlroots panel demos

10.3 Tools & Documentation

  • WAYLAND_DEBUG=1
  • wayland-info

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain layer-shell anchoring and exclusive zones.
  • I can explain buffer scale and HiDPI handling.
  • I can explain timer-driven rendering.

11.2 Implementation

  • Panel appears and reserves space.
  • Clock updates once per second.
  • CPU usage remains low.

11.3 Growth

  • I can extend the panel with additional sections.
  • I can explain differences vs xdg_toplevel.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Panel renders with fixed text and reserved space.

Full Completion:

  • Clock updates with timer.
  • HiDPI scaling handled correctly.

Excellence (Going Above & Beyond):

  • Multi-output support and custom protocol integration.