00 Wayland mimarisi
Wayland, display server protokolünü yeniden tasarlar. X11'in "server aracılar" mimarisinin aksine, compositor doğrudan display donanımıyla konuşur; client'lar compositing sonucunu görmez.
X11 vs Wayland
wayland.xml interface tanımları, wayland-scanner ile C stub'larına derlenir.Temel protokol nesneleri
wl_display — sunucu bağlantısı, event loop
↓
wl_registry — global nesne listesi (compositor, shm, seat...)
↓
wl_compositor — wl_surface oluşturur
↓
wl_surface — ham piksel tamponu (boyutsuz)
↓
xdg_surface — pencere soyutlaması (xdg-shell.xml)
↓
xdg_toplevel — maximize, minimize, resize, title
xdg-shell protokolü
Standart pencere yönetimi için xdg-shell.xml kullanılır. Kiosk shell ise xdg-shell yerine weston-kiosk-shell kullanır ve fullscreen zorunlu kılar.
01 Weston kurulumu ve yapılandırması
Weston, referans Wayland compositor'ıdır. Embedded kullanımda drm backend ile DRM/KMS üzerinde doğrudan çalışır; X11 gerektirmez.
weston.ini temel yapılandırması
[core]
backend=drm-backend.so # KMS/DRM backend
shell=kiosk-shell.so # kiosk: tek uygulama
idle-time=0 # screensaver kapalı
require-input=false # input cihazı olmasa da başla
[output]
name=HDMI-A-1
mode=1920x1080@60 # veya "preferred", "current"
transform=normal # rotate-90, rotate-180, flipped
scale=1
[output]
name=DSI-1
mode=1024x600@60
[keyboard]
keymap_rules=evdev
keymap_layout=tr # Türkçe klavye düzeni
[input-method]
path=/usr/libexec/weston-keyboard # ekran klavyesi
Backend seçimi
seatd ile birlikte kullanılır.Weston başlatma
# Doğrudan başlat (TTY1'de, root olarak)
weston --backend=drm-backend.so \
--shell=kiosk-shell.so \
--config=/etc/xdg/weston/weston.ini
# WAYLAND_DISPLAY soket adını belirt
WAYLAND_DISPLAY=wayland-1 weston &
# Compositor logunu ayrıntılı göster
weston --log=/var/log/weston.log --verbose
02 Kiosk shell — fullscreen tek uygulama HMI
Weston kiosk shell, embedded HMI ürünleri için tasarlanmıştır: tek uygulama, fullscreen, pencere çerçevesi yok, görev çubuğu yok.
Kiosk shell özellikleri
weston-keyboard sanal klavyesi, text-input-v1 protokolü üzerinden etkinleştirilir.kiosk.ini — uygulama başlatma
[kiosk-shell]
# Weston başladığında otomatik uygulamayı başlat
launcher=/usr/bin/my-hmi-app
# Alternatif: systemd servisi ile (önerilen)
# [core] bölümünde: modules=systemd-notify.so
Dokunmatik ekran kiosk — tam senaryo
# Weston kiosk servisini kur
cat > /etc/systemd/system/weston.service <<'EOF'
[Unit]
Description=Weston Wayland Compositor
After=systemd-udevd.service
[Service]
Type=notify
User=weston
Environment=WAYLAND_DISPLAY=wayland-0
Environment=XDG_RUNTIME_DIR=/run/user/1000
ExecStart=/usr/bin/weston \
--backend=drm-backend.so \
--shell=kiosk-shell.so
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
systemctl enable weston
systemctl start weston
# HMI uygulamasını kiosk içinde başlat
cat > /etc/systemd/system/hmi-app.service <<'EOF'
[Unit]
After=weston.service
Requires=weston.service
[Service]
Environment=WAYLAND_DISPLAY=wayland-0
Environment=XDG_RUNTIME_DIR=/run/user/1000
ExecStart=/usr/bin/my-hmi-app
Restart=always
EOF
03 Wayland client yazma
Wayland client'ı yazmak X11'e göre daha kurallıdır: her şey protokol üzerinden nesne oluşturma ve event işleme döngüsüne dayanır.
Temel bağlantı ve registry
#include <wayland-client.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct wl_compositor *compositor = NULL;
struct wl_shm *shm = NULL;
/* registry'de yeni global nesne bildirildiğinde çağrılır */
static void registry_handler(void *data,
struct wl_registry *registry,
uint32_t id, const char *interface, uint32_t version)
{
if (strcmp(interface, "wl_compositor") == 0) {
compositor = wl_registry_bind(registry, id,
&wl_compositor_interface, 4);
} else if (strcmp(interface, "wl_shm") == 0) {
shm = wl_registry_bind(registry, id,
&wl_shm_interface, 1);
}
}
static void registry_remover(void *data,
struct wl_registry *registry, uint32_t id) {}
static const struct wl_registry_listener registry_listener = {
.global = registry_handler,
.global_remove = registry_remover,
};
int main(void)
{
struct wl_display *display = wl_display_connect(NULL);
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, ®istry_listener, NULL);
wl_display_roundtrip(display); /* registry olaylarını işle */
struct wl_surface *surface = wl_compositor_create_surface(compositor);
/* shared memory buffer oluştur ve içeriği yaz... */
wl_surface_commit(surface);
while (wl_display_dispatch(display) != -1)
; /* event döngüsü */
wl_display_disconnect(display);
return 0;
}
wl_shm ile shared memory buffer
#include <sys/mman.h>
#include <fcntl.h>
int create_shm_buffer(struct wl_shm *shm, int width, int height,
struct wl_buffer **out_buf, void **out_data)
{
int stride = width * 4; /* ARGB8888: 4 byte/piksel */
int size = stride * height;
/* anonim geçici dosya */
int fd = memfd_create("wl_shm_buf", MFD_CLOEXEC);
ftruncate(fd, size);
void *data = mmap(NULL, size,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
struct wl_buffer *buf = wl_shm_pool_create_buffer(pool,
0, width, height, stride, WL_SHM_FORMAT_ARGB8888);
wl_shm_pool_destroy(pool);
close(fd);
*out_buf = buf;
*out_data = data;
return 0;
}
04 EGL ve OpenGL ES — donanım hızlandırmalı render
Yüksek performanslı render için wl_shm yerine EGL window surface kullanılır. Bu yaklaşımda GPU doğrudan Wayland surface'ine render eder.
EGL başlatma
#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <wayland-egl.h>
/* EGL display al — Wayland platform */
EGLDisplay egl_display =
eglGetDisplay((EGLNativeDisplayType)wl_display);
eglInitialize(egl_display, NULL, NULL);
eglBindAPI(EGL_OPENGL_ES_API);
/* EGL config seç */
EGLint attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_NONE
};
EGLConfig config;
EGLint num_configs;
eglChooseConfig(egl_display, attribs, &config, 1, &num_configs);
/* GLES2 context */
EGLint ctx_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
EGLContext context = eglCreateContext(egl_display,
config, EGL_NO_CONTEXT, ctx_attribs);
Wayland EGL window surface
/* wl_egl_window: Wayland surface'ini EGL'e bağlar */
struct wl_egl_window *egl_window =
wl_egl_window_create(wl_surface, width, height);
EGLSurface egl_surface =
eglCreateWindowSurface(egl_display, config,
(EGLNativeWindowType)egl_window, NULL);
eglMakeCurrent(egl_display, egl_surface, egl_surface, context);
/* GLES2 ile kırmızı ekran */
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
/* swap — compositor'a teslim et */
eglSwapBuffers(egl_display, egl_surface);
wl_display_flush(wl_display);
GLES2 üçgen örneği
const char *vert_src =
"attribute vec2 pos;\n"
"void main() { gl_Position = vec4(pos, 0.0, 1.0); }\n";
const char *frag_src =
"precision mediump float;\n"
"void main() { gl_FragColor = vec4(0.0, 0.8, 0.2, 1.0); }\n";
/* shader derleme (compile_shader yardımcı fonksiyonu varsayılıyor) */
GLuint prog = link_program(vert_src, frag_src);
glUseProgram(prog);
GLfloat verts[] = { 0.0f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f };
GLint pos_loc = glGetAttribLocation(prog, "pos");
glVertexAttribPointer(pos_loc, 2, GL_FLOAT, GL_FALSE, 0, verts);
glEnableVertexAttribArray(pos_loc);
glDrawArrays(GL_TRIANGLES, 0, 3);
eglSwapBuffers(egl_display, egl_surface);
05 Input handling — pointer, keyboard, touch
wl_seat nesnesi tüm input cihazlarını (fare, klavye, dokunmatik ekran) kapsayan soyutlamadır. Her bir cihaz için ayrı listener kaydedilir.
wl_seat ve input nesneleri
/* wl_seat'i registry'den al */
struct wl_seat *seat = wl_registry_bind(registry, id,
&wl_seat_interface, 5);
/* wl_touch al */
struct wl_touch *touch = wl_seat_get_touch(seat);
/* touch event listener */
static void touch_down(void *data, struct wl_touch *wl_touch,
uint32_t serial, uint32_t time,
struct wl_surface *surface,
int32_t id, wl_fixed_t x, wl_fixed_t y)
{
double fx = wl_fixed_to_double(x);
double fy = wl_fixed_to_double(y);
printf("Touch DOWN: id=%d, x=%.1f, y=%.1f\n", id, fx, fy);
}
static void touch_up(void *data, struct wl_touch *wl_touch,
uint32_t serial, uint32_t time, int32_t id)
{
printf("Touch UP: id=%d\n", id);
}
static void touch_motion(void *data, struct wl_touch *wl_touch,
uint32_t time, int32_t id,
wl_fixed_t x, wl_fixed_t y) {}
static void touch_frame(void *data, struct wl_touch *wl_touch) {}
static void touch_cancel(void *data, struct wl_touch *wl_touch) {}
static const struct wl_touch_listener touch_listener = {
.down = touch_down,
.up = touch_up,
.motion = touch_motion,
.frame = touch_frame,
.cancel = touch_cancel,
};
wl_touch_add_listener(touch, &touch_listener, NULL);
Dokunmatik ekran kalibrasyonu
# libinput ile dokunma noktalarını izle
libinput debug-events --device /dev/input/event1
# Weston için evdev kalibrasyon matrisi
# weston.ini [libinput] bölümüne:
# calibration_matrix=1.0 0.0 0.0 0.0 1.0 0.0
# Döndürülmüş panel için (90 derece CCW):
# calibration_matrix=0.0 -1.0 1.0 1.0 0.0 0.0
# Yanlış yönlendirme için transform:
weston-calibrator # interaktif kalibrasyon aracı
Wayland, çoklu dokunma noktasını id parametresi ile takip eder. Her parmak farklı bir id alır; touch_down ile başlar, touch_up ile biter. Gesture kütüphaneleri bu ham olayları jestlere dönüştürür.
06 Yocto Wayland image — meta-weston, systemd servisi
Yocto ile Wayland/Weston içeren minimal embedded image oluşturmak için DISTRO_FEATURES ve IMAGE_INSTALL yapılandırması gerekir.
local.conf yapılandırması
# Wayland desteğini etkinleştir, X11'i çıkar
DISTRO_FEATURES:append = " wayland opengl"
DISTRO_FEATURES:remove = " x11"
# systemd kullan (önerilen Weston ile)
DISTRO_FEATURES:append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
# GPU desteği — RPi4 için
MACHINE_FEATURES:append = " vc4graphics"
IMAGE_INSTALL ve bağımlılıklar
require recipes-graphics/images/core-image-weston.bb
IMAGE_INSTALL:append = " \
weston \
weston-init \
weston-examples \
libdrm \
mesa \
mesa-megadriver \
wayland-utils \
libinput \
seatd \
my-hmi-application \
"
# weston.ini'yi image'a dahil et
ROOTFS_POSTPROCESS_COMMAND:append = " install_weston_config; "
install_weston_config() {
install -d ${IMAGE_ROOTFS}/etc/xdg/weston
install -m 0644 ${THISDIR}/files/weston.ini \
${IMAGE_ROOTFS}/etc/xdg/weston/weston.ini
}
meta-weston systemd servisi
# meta-weston katmanını ekle
bitbake-layers add-layer ../sources/meta-openembedded/meta-oe
bitbake-layers add-layer ../sources/meta-raspberrypi # RPi için
# image derle
bitbake weston-hmi-image
# Weston init servisi otomatik gelir:
# /lib/systemd/system/weston.service
# /lib/systemd/system/weston@.service (multi-user)
07 XWayland — X11 uygulama uyumluluğu
XWayland, Wayland compositor içinde tam X11 server emülasyonu sunar. Native Wayland desteği olmayan Qt5, GTK3 veya SDL2 uygulamaları XWayland üzerinden çalışır.
XWayland nasıl çalışır
X11 Uygulama
↓ DISPLAY=:0 bağlantısı
XWayland Server
↓ Wayland client olarak
Weston Compositor
↓ DRM/KMS
Fiziksel Ekran
Weston'da XWayland etkinleştirme
# weston.ini [core] bölümüne ekle:
[core]
xwayland=true
# Yocto'da xwayland paketi ekle:
IMAGE_INSTALL:append = " xwayland"
# X11 uygulamasını Wayland ortamında başlat
export DISPLAY=:0
export WAYLAND_DISPLAY=wayland-0
# XWayland otomatik olarak başlar, DISPLAY değişkeni ayarlanır
# Mevcut uygulamaları sınama:
DISPLAY=:0 xterm
DISPLAY=:0 glxinfo | grep "OpenGL renderer"
XWayland üzerinden çalışan uygulamalar bir Wayland buffer kopyasından geçer — native Wayland client'lara göre biraz daha yavaştır. Mümkünse uygulamaları native Wayland desteğine taşı: Qt5 için -platform wayland, SDL2 için SDL_VIDEODRIVER=wayland.
08 Pratik
Üç gerçek senaryo: i.MX8 Weston kiosk HMI, Raspberry Pi CM4 dual display ve weston-terminal ile serial konsol.
i.MX8 Weston kiosk HMI — Python + GTK4/Wayland
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, GLib
class KioskWindow(Gtk.ApplicationWindow):
def __init__(self, app):
super().__init__(application=app, title="HMI")
# Fullscreen — kiosk shell zaten zorlar ama güvenlik için
self.fullscreen()
self.set_decorated(False)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
box.set_margin_top(40)
label = Gtk.Label(label="Üretim Hattı Monitörü")
label.add_css_class("title-1")
box.append(label)
btn = Gtk.Button(label="Başlat")
btn.connect("clicked", self.on_start)
box.append(btn)
self.set_child(box)
def on_start(self, btn):
print("Üretim başlatıldı")
class App(Gtk.Application):
def __init__(self):
super().__init__(application_id="com.example.hmi")
self.connect("activate", self.on_activate)
def on_activate(self, app):
win = KioskWindow(app)
win.present()
if __name__ == "__main__":
import sys
App().run(sys.argv)
export WAYLAND_DISPLAY=wayland-0
export XDG_RUNTIME_DIR=/run/user/1000
GDK_BACKEND=wayland python3 hmi_app.py
Raspberry Pi CM4 — dual display konfigürasyonu
[output]
name=HDMI-A-1
mode=1920x1080@60
transform=normal
[output]
name=HDMI-A-2
mode=1280x720@60
x=1920 # ikinci ekran yatayda yanına
y=0
weston-terminal ile serial konsol
# Weston compositor çalışırken terminal başlat
WAYLAND_DISPLAY=wayland-0 weston-terminal
# SSH üzerinden uzak terminal (headless debug)
WAYLAND_DISPLAY=wayland-0 \
DISPLAY= \
weston-terminal --shell=/bin/bash
# Üretim sisteminde debug: kiosk'u bypass etmeden
# weston-terminal'i gizli touch kombinasyonu ile aç
# (4 köşeye aynı anda 3 saniye basma gibi)