Skip to content

Examples¤

To run the examples either ensure the development dependencies have been installed (see installation) or, if you are working from the project's git repository, be sure to enter the correct hatch environment. For example, from the project root:

hatch shell
python examples/00-simple-decorator.py

Display Global Interfaces¤

Using the decorator method of custom class registration.

# examples/00-simple-decorator.py

# Print a list of Wayland global interfaces
import wayland
from wayland.client import wayland_class


@wayland_class("wl_registry")
class Registry(wayland.wl_registry):

    def on_global(self, name, interface, version):
        print(f"{interface} (version {version})")


if __name__ == "__main__":

    display = wayland.wl_display()
    registry = display.get_registry()

    while True:
        display.dispatch_timeout(0.2)

Display Global Interfaces¤

Using explicit custom class registration.

# examples/10-simple-explicit.py

# Print a list of Wayland global interfaces
import wayland
from wayland.client import register_factory


class Registry(wayland.wl_registry):

    def on_global(self, name, interface, version):
        print(f"{interface} (version {version})")


if __name__ == "__main__":

    register_factory("wl_registry", Registry)

    display = wayland.wl_display()
    registry = display.get_registry()

    while True:
        display.dispatch_timeout(0.2)

List Available Monitors¤

Obtains and prints information about the available display outputs.

# examples/20-list-monitors.py

# Print information about the available display outputs.
import wayland
from wayland.client import wayland_class


@wayland_class("wl_registry")
class Registry(wayland.wl_registry):

    def __init__(self):
        super().__init__()
        self.outputs = []

    def on_global(self, name, interface, version):
        if interface == "wl_output":
            output = self.bind(name, interface, version)
            self.outputs.append(output)
        else:
            return  # ignore all other interfaces


@wayland_class("wl_output")
class Output(wayland.wl_output):

    def __init__(self):
        super().__init__()
        self.done = False

    def on_geometry(
        self, x, y, physical_width, physical_height, subpixel, make, model, transform
    ):
        print(f"  Monitor: {make} {model}")
        print(f"  Position: {x}, {y}")
        print(f"  Physical size: {physical_width}x{physical_height}mm")

    def on_mode(self, flags, width, height, refresh):
        if flags & 1:  # Current mode
            print(f"  Resolution: {width}x{height} @ {refresh / 1000:.1f}Hz")

    def on_description(self, description):
        print(f"{description}")

    def on_done(self):
        self.done = True


# Request the global registry from the wayland compositor
display = wayland.wl_display()
registry = display.get_registry()

# Simple event loop to get the responses
while not registry.outputs or not all(output.done for output in registry.outputs):
    display.dispatch_timeout(0.1)

Top-Level Window¤

This example demonstrates everything required to create a top-level application window and manage memory pools and surface buffers. It simply displays the Wayland logo in window and resizes the logo as the window is resized.

It demonstrates the use of the SharedMemoryPool helper class to handle all of the low level memory allocation for the surface buffers.

It also demonstrates a number of other python-wayland features, including three different methods of implementing event handlers.

# examples/30-main-window.py

# Copyright (c) 2024-2025 Graham R King
# Licensed under the MIT License. See LICENSE file for details.

import sys

import wayland
from wayland.client import wayland_class
from wayland.client.memory_pool import SharedMemoryPool
from png import PngImage


@wayland_class("xdg_wm_base")
class WmBase(wayland.xdg_wm_base):

   # Respond to compositor pings.
   def on_ping(self, serial):
       self.pong(serial)


@wayland_class("xdg_surface")
class Surface(wayland.xdg_surface):

   def __init__(self, app):
       super().__init__(app=app)
       self.app = app

   def on_configure(self, serial):
       self.ack_configure(serial)
       self.app.redraw()


@wayland_class("wl_shm")
class Shm(wayland.wl_shm):

   def __init__(self, app):
       super().__init__(app=app)
       self.pool = SharedMemoryPool(self)


@wayland_class("wl_registry")
class Registry(wayland.wl_registry):

   def __init__(self, app):
       super().__init__(app=app)
       self.wl_shm = None
       self.xdg_wm_base = None
       self.wl_compositor = None

   def on_global(self, name, interface, version):
       # Bind any interfaces that match our properties
       if hasattr(self, interface):
           setattr(self, interface, self.bind(name, interface, version))


@wayland_class("wl_display")
class Display(wayland.wl_display):

   def on_error(self, object_id, code, message):
       # Handle fatal errors
       print(f"Error: {object_id} {code} {message}")
       sys.exit(1)


class MainWindow:

   def __init__(self):
       self.running = True
       self.surface = None

       # Load image and set initial window size
       self.image = PngImage()
       self.image.load_png("wayland-logo.png")
       self.width, self.height = self.image.current_width, self.image.current_height

       # Initialize Wayland connection
       self.display = Display(app=self)
       self.registry = self.display.get_registry()

   def on_configure(self, width, height, states):
       """Handle window resize events."""
       if width and height:
           self.width, self.height = width, height
           self.redraw()

   def assert_initialised(self):
       """Initialize Wayland interfaces when available."""
       if self.surface:
           return True

       # Check if all required interfaces are available
       if all([self.registry.wl_compositor, self.registry.wl_shm, self.registry.xdg_wm_base]):
           # Create surface and configure window
           self.surface = self.registry.wl_compositor.create_surface()
           xdg_surface = self.registry.xdg_wm_base.get_xdg_surface(self.surface)
           toplevel = xdg_surface.get_toplevel()

           # Set up event handlers
           toplevel.events.configure += self.on_configure
           toplevel.events.close += lambda: setattr(self, 'running', False)

           # Commit initial surface
           self.surface.commit()
       return False

   def redraw(self):
       # Create buffer for current surface size
       buffer, ptr = self.registry.wl_shm.pool.create_buffer(self.width, self.height)

       # Copy image to buffer
       self.image.copy_to_buffer(ptr, self.width, self.height)

       # Commit buffer to surface
       self.surface.attach(buffer, 0, 0)
       self.surface.damage_buffer(0, 0, self.width, self.height)
       self.surface.commit()

   def run(self):
       # Main event loop
       while self.running:
           self.assert_initialised()
           self.display.dispatch_timeout(1/25)  # 25 FPS


if __name__ == "__main__":
   MainWindow().run()

The PNGImage class used in the example:

# We use opencv to handle the PNG image for this example
# pip install opencv-python
import ctypes
import os
import cv2

class PngImage:
   """Simple PNG image loader."""

   def __init__(self):
       self.src_image = None
       self.current_image = None
       self.current_width = 0
       self.current_height = 0

   def load_png(self, filename):
       script_dir = os.path.dirname(os.path.abspath(__file__))
       full_path = os.path.join(script_dir, filename)

       self.src_image = cv2.imread(full_path, cv2.IMREAD_UNCHANGED)

       # Ensure we have 4 channels (BGRA)
       while self.src_image.shape[2] < 4:
           self.src_image = cv2.cvtColor(self.src_image, cv2.COLOR_BGR2BGRA)

       self.current_image = self.src_image
       self.current_height, self.current_width = self.src_image.shape[:2]
       return True

   def copy_to_buffer(self, buffer, width, height):
       # Only resize if current image isn't the right size
       if self.current_width != width or self.current_height != height:
           self.current_image = cv2.resize(self.src_image, (width, height))
           self.current_width, self.current_height = width, height

       # Copy BGRA data directly (matches little-endian ARGB8888)
       pixels = ctypes.cast(buffer, ctypes.POINTER(ctypes.c_uint32))
       ctypes.memmove(pixels, self.current_image.tobytes(), width * height * 4)
       return True