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