Графический стек: X11, Wayland, оконные менеджеры, композиторы и DE
Экран один. Приложений — сорок. Клавиатура одна, а претендентов на её ввод — все окна сразу. Видеопамять общая, но браузер не должен читать пиксели вашего банковского клиента. Всё это — классические задачи ОС: арбитраж разделяемого ресурса, изоляция, синхронизация. Разница лишь в том, что в Unix их решают не в ядре, а в пользовательском процессе, который исторически назывался «X-сервер», а сегодня чаще называется «композитор».
Статья — про то, из чего реально собран рабочий стол: какие объекты живут в ядре, какой
протокол ходит по сокету, кто решает, где будет окно, почему xdotool не работает в GNOME на
Wayland и откуда берутся 16.7 мс задержки.
Проверочный вопрос, отделяющий пользователя от инженера: почему в X11 приложение может само подвинуть своё окно в точку (0,0), а в Wayland — нет, и это не недоработка, а следствие модели безопасности? Ответ объясняет добрую половину «регрессий» при переезде.
Карта: из чего состоит графический стек
Сразу разделим три роли, которые в речи слипаются в слово «графика»:
- display server — владеет устройствами вывода и ввода, принимает подключения клиентов;
- window manager (WM) — решает политику: где окно, какого размера, что в фокусе;
- compositor — собирает из буферов окон финальный кадр и отдаёт его железу.
В X11 это три сменяемых на лету процесса. В Wayland — один. Вся разница между мирами вырастает отсюда.
стек)) Ядро DRM: доступ к GPU KMS: режимы и плоскости dma-buf и modifiers evdev: /dev/input/event* Протокол X11: request/reply/event Wayland: объекты и роли расширения и версии сокет + передача fd Сервер Xorg Mutter / KWin / Sway XWayland как мост seat и права на устройства Политика stacking / tiling / dynamic ICCCM и EWMH xdg-shell порталы xdg-desktop-portal Клиент тулкит: GTK / Qt EGL / Vulkan / GBM масштаб и HiDPI буфер обмена и DnD
Ядро: DRM, KMS и dma-buf — общий фундамент
Ни X-сервер, ни Wayland-композитор не говорят с видеокартой напрямую. Между ними — подсистема DRM (Direct Rendering Manager) и её часть KMS (Kernel Mode Setting). Это обычные символьные устройства:
$ ls -l /dev/dri/
crw-rw----+ 1 root video 226, 0 Jul 16 09:12 card0
crw-rw----+ 1 root render 226, 128 Jul 16 09:12 renderD128
Два узла — не дубликаты, а разные уровни привилегий:
card0— primary node: modesetting, смена разрешения, page flip. Владеть им может только один процесс — DRM master. ОтсюдаdrmModeSetCrtc failed: Permission deniedпри попытке поднять второй композитор поверх работающего.renderD128— render node (в ядре с 3.12, по умолчанию с 3.17): только счёт — аллокация буферов, шейдеры, вычисления. Ни modeset, ни доступа к чужим буферам. Именно его достаточно пробросить в контейнер, чтобы внутри работали OpenGL/Vulkan/VA-API без права трогать экран.
# минимально достаточный проброс GPU в контейнер для рендера/транскодинга
$ docker run --rm --device /dev/dri/renderD128 \
--group-add "$(getent group render | cut -d: -f3)" myimage vainfo
Это прямое следствие темы из статьи про безопасность и изоляцию: render node — capability «использовать GPU» без capability «управлять экраном».
Объекты KMS
KMS описывает вывод небольшим набором объектов — именно ими оперируют все инструменты диагностики:
- framebuffer — «вот эта память, в таком формате и размере»;
- plane — аппаратный слой:
PRIMARY,OVERLAY(часто YUV + масштабирование),CURSOR(двигается без перерисовки кадра); - CRTC — смешивает плоскости и задаёт видеорежим (разрешение, таймингы, частоту);
- encoder — превращает пиксели в сигнал конкретного типа;
- connector — физический разъём (DP-1, eDP-1), хранит EDID, сообщает о hotplug.
$ cat /sys/class/drm/card0-eDP-1/status && head -2 /sys/class/drm/card0-eDP-1/modes
connected
2880x1800
1920x1200
$ drm_info | head -40 # полная картина объектов и свойств
$ sudo modetest -M i915 -c -p # connector'ы и плоскости; только без активного сервера
Atomic modesetting. Старый API менял состояние по кускам — отдельно CRTC, отдельно
плоскость, отдельно курсор, — и промежуточные состояния могли быть невалидны (мигания,
«сначала съехало, потом встало»). Atomic API (пользовательский ioctl разблокирован в ядре 4.2,
2015) принимает весь набор изменений одним drmModeAtomicCommit(), умеет TEST_ONLY
(«потянет ли железо?») и применяет всё в один vblank. Тот же принцип, что атомарный коммит
транзакции, только транзакция — это кадр.
dma-buf. Клиент рисует в буфер GPU, композитор должен его показать; копировать 16 МБ на
кадр — не вариант. dma-buf (мейнлайн с 3.3) превращает буфер в файловый дескриптор, который
едет по unix-сокету через SCM_RIGHTS (механизм из
статьи про системные вызовы и IPC), а получатель
импортирует его в свой GPU-контекст. К дескриптору прилагается modifier — 64-битный код
физической раскладки пикселей (линейная, тайлинг, сжатие такого-то поколения). Не договорились
о modifier — картинка либо рассыпается, либо втихую копируется медленным линейным путём.
Отсюда классика «на ноутбуке с двумя GPU видео тормозит»: буфер, оптимальный для дискретной
карты, перекладывается для встроенной.
Как это устроено в других системах
| Система | Слой доступа к дисплею | Кто владеет экраном |
|---|---|---|
| Linux | DRM/KMS, /dev/dri/* |
Xorg или Wayland-композитор |
| FreeBSD | DRM в портах drm-kmod через LinuxKPI |
то же, плюс seatd вместо logind |
| OpenBSD | DRM в базе (inteldrm, amdgpu), wsdisplay |
Xenocara с разделением привилегий |
| NetBSD | drmkms в ядре, wscons |
модульный X.Org |
| macOS | IOKit + IOSurface, публичного API нет | WindowServer, единственный и несменяемый |
| Windows NT | WDDM, dxgkrnl.sys; оконный менеджер в ядре (win32k.sys) |
DWM, композиция обязательна с Windows 8 |
FreeBSD осознанно вынес DRM из базы в порты: код драйверов Linux меняется быстрее цикла релизов base. Деталь для читавших статью о семействе BSD: «FreeBSD не поддерживает новые GPU» обычно означает «вы поставили kmod старой ветки».
X11: сетевой протокол 1987 года у вас на столе
X появился в MIT в 1984-м, протокол X11 зафиксирован в 1987-м и с тех пор совместим на уровне байтов: клиент, собранный в 1990 году, подключится к сегодняшнему X.Org.
Модель протокола
X11 — асинхронный бинарный протокол поверх сокета с четырьмя типами сообщений: request
(CreateWindow, MapWindow), reply (только на запросы, которые его требуют), event
(KeyPress, Expose, ConfigureNotify) и error (асинхронный, приходит «когда-нибудь
потом»). Все объекты — окна, пиксмапы, графические контексты — живут на сервере и адресуются
32-битными resource ID; диапазон выдаётся клиенту при подключении, поэтому ID генерируются
локально.
Ключевое для производительности — round-trip. Запрос без ответа уходит в буфер и стоит
почти ничего; запрос с ответом требует дождаться сервера: локально это десятки микросекунд, по
сети через океан — сотни миллисекунд. Xlib прячет разницу и легко порождает сотни round-trip’ов
на старте; XCB делает их явными — вы отправляете _cookie и забираете _reply тогда, когда
результат нужен.
Обратите внимание на шаг 4: MapWindow от клиента не показывает окно. Если кто-то выбрал на
корневом окне маску SubstructureRedirectMask, сервер вместо выполнения запроса пересылает его
этому клиенту. Эта единственная маска и делает процесс оконным менеджером — а поскольку выбрать
её может лишь один клиент, второй WM падает с «another window manager is already running».
Минимальный клиент на XCB
// gcc win.c -lxcb -o win
#include <xcb/xcb.h>
#include <stdio.h>
int main(void) {
int scr;
xcb_connection_t *c = xcb_connect(NULL, &scr); // читает $DISPLAY
if (xcb_connection_has_error(c)) return 1;
xcb_screen_t *s = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
xcb_window_t win = xcb_generate_id(c); // ID локально, без round-trip
uint32_t vals[2] = { s->white_pixel,
XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS };
xcb_create_window(c, XCB_COPY_FROM_PARENT, win, s->root, 0, 0, 640, 480, 2,
XCB_WINDOW_CLASS_INPUT_OUTPUT, s->root_visual,
XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, vals);
// ICCCM: попросить WM присылать WM_DELETE_WINDOW, а не рвать соединение
xcb_intern_atom_reply_t *proto = xcb_intern_atom_reply(c,
xcb_intern_atom(c, 1, 12, "WM_PROTOCOLS"), NULL); // round-trip #1
xcb_intern_atom_reply_t *del = xcb_intern_atom_reply(c,
xcb_intern_atom(c, 0, 16, "WM_DELETE_WINDOW"), NULL); // round-trip #2
xcb_change_property(c, XCB_PROP_MODE_REPLACE, win, proto->atom, 4, 32, 1, &del->atom);
xcb_map_window(c, win); // это ЗАПРОС: реальное решение примет WM
xcb_flush(c); // без flush ничего не уйдёт в сокет — классическая ошибка
xcb_generic_event_t *e;
while ((e = xcb_wait_for_event(c))) {
switch (e->response_type & ~0x80) { // старший бит = событие послано SendEvent
case XCB_EXPOSE: puts("перерисовать открывшуюся область"); break;
case XCB_KEY_PRESS: puts("это keycode, не символ — трансляция через XKB"); break;
case XCB_CLIENT_MESSAGE:
if (((xcb_client_message_event_t *)e)->data.data32[0] == del->atom) {
free(e); xcb_disconnect(c); return 0;
}
}
free(e);
}
return 0;
}
Три вещи, на которых спотыкаются все: xcb_flush (иначе запросы лежат в буфере), KeyPress
даёт keycode, а не символ, и MapWindow не гарантирует появления окна.
X11 полностью прозрачен — и это же его проблема
$ echo $DISPLAY; ls /tmp/.X11-unix/ # локальный транспорт — unix-сокет, не TCP
:0
X0
$ xdpyinfo | head -12 # версия протокола, экраны, список расширений
$ xrandr --listmonitors # выходы и геометрия
$ xwininfo -root -tree # дерево окон ВСЕЙ сессии — доступно любому клиенту
$ xprop -id 0x2200003 # свойства окна: WM_CLASS, _NET_WM_STATE ...
$ xev # сырые события ввода
$ xlsclients # кто подключён к серверу
Аутентификация — cookie MIT-MAGIC-COOKIE-1 в ~/.Xauthority; TCP (порт 6000+N) обычно
выключен флагом -nolisten tcp, а xhost + буквально означает «пустить кого угодно управлять
моим рабочим столом».
Принципиальные ограничения, которые нельзя починить, не сломав протокол:
- Нет изоляции между клиентами. Любой клиент читает дерево всех окон, снимает с них
картинку и перехватывает ввод. Кейлоггер — цикл вокруг
XQueryKeymap()или одинXGrabKeyboard(); XTEST синтезирует нажатия в любое окно. - Нет атомарного кадра. Новый размер окна и его новое содержимое — разные события, отсюда мигания и «резиновые» рамки при resize.
- Один DPI и одна раскладка на весь дисплей — это свойства сервера, не окна. 4K-ноутбук плюс внешний FullHD корректно не решаются.
- Композиция поверх, а не вместо. Расширение Composite (2004) перенаправляет рендер окна в offscreen-pixmap, композитный WM собирает из них экран — лишний проход и лишняя копия на кадр.
- Развитие остановлено. X.Org получает почти только исправления безопасности; в 2025-м появился форк Xlibre, но дистрибутивы идут в другую сторону.
Обратная сторона: X11 честно работает по сети, отлично автоматизируется (xdotool, wmctrl,
scrot — просто клиенты) и тянет три десятилетия софта. Поэтому XWayland с нами надолго.
Wayland: протокол вместо сервера рисования
Wayland начал Kristian Høgsberg в 2008 году, будучи разработчиком X.Org. Наблюдение было простым: клиенты уже рисовали сами (DRI + OpenGL), а X-сервер выродился в посредника, который передаёт готовые буферы и добавляет к этому копию, задержку и 400 запросов легаси-API.
Цель формулируется радикальнее, чем «сделать быстрее»: «каждый кадр — идеальный» (every frame is perfect). Пользователь не должен увидеть промежуточное состояние никогда — ни разорванного кадра, ни окна старого размера с новым содержимым.
Объектная модель
Wayland — не «протокол рисования», а протокол объектов, описанный в XML; wayland-scanner
генерирует из него C-код обеих сторон. Wire format прост: ID объекта, слово «опкод + длина»,
аргументы; файловые дескрипторы едут вне потока через SCM_RIGHTS.
Принципиально: wl_surface сама по себе не окно. Смысл она получает, только когда ей назначают
роль — xdg_toplevel (окно), xdg_popup (меню), wl_subsurface (встроенная область,
например видео), zwlr_layer_surface (панель или обои в композиторах на wlroots). Роль
назначается один раз и навсегда.
// gcc c.c -lwayland-client — что предлагает композитор
#include <wayland-client.h>
#include <stdio.h>
static void on_global(void *d, struct wl_registry *r, uint32_t name,
const char *iface, uint32_t ver) {
printf("%-40s v%-3u name=%u\n", iface, ver, name);
// обычно здесь: if (!strcmp(iface, "wl_compositor")) comp = wl_registry_bind(...)
}
static void on_remove(void *d, struct wl_registry *r, uint32_t name) {
printf("исчез глобал %u (например, отключили монитор)\n", name);
}
static const struct wl_registry_listener lst = { on_global, on_remove };
int main(void) {
struct wl_display *dpy = wl_display_connect(NULL); // $WAYLAND_DISPLAY в $XDG_RUNTIME_DIR
if (!dpy) return 1;
wl_registry_add_listener(wl_display_get_registry(dpy), &lst, NULL);
wl_display_roundtrip(dpy); // ЕДИНСТВЕННАЯ явная точка синхронизации
wl_display_disconnect(dpy);
}
Готовый аналог — wayland-info из пакета wayland-utils:
$ echo $WAYLAND_DISPLAY $XDG_RUNTIME_DIR
wayland-0 /run/user/1000
$ wayland-info | grep interface | head -6
interface: 'wl_compositor', version: 6, name: 1
interface: 'zwp_linux_dmabuf_v1', version: 5, name: 8
interface: 'xdg_wm_base', version: 6, name: 12
interface: 'wp_fractional_scale_manager_v1', version: 1, name: 21
interface: 'wp_tearing_control_manager_v1', version: 1, name: 23
Этот вывод — контракт вашего приложения с конкретным композитором. Нет
wp_fractional_scale_manager_v1 — не будет честного дробного масштаба; нет
zwlr_layer_shell_v1 — панель не напишется. Отсюда главная боль экосистемы: фрагментация не в
самом Wayland, а в наборе реализованных расширений.
Жизненный цикл окна: configure / ack / commit
Здесь виден весь смысл модели: клиент не выбирает свой размер и позицию — он получает
configure и обязан подтвердить его серийным номером. Композитор знает, что данный кадр
соответствует данному запросу, и потому никогда не покажет несогласованное состояние.
Такт кадра и обратное давление
wl_callback.done — это встроенное обратное давление. Приложение, рисующее «в цикле», на
Wayland автоматически замедляется до частоты монитора и перестаёт жечь батарею на невидимые
кадры; свёрнутое окно вообще не получает callback и корректный клиент просто засыпает. На X11
такого механизма нет — приложения годами компенсировали его таймерами и glXSwapInterval.
Второй важный момент — damage: клиент обязан указать, что именно изменилось, и композитор
может обновить один прямоугольник или пропустить композицию вовсе. damage_buffer() считает в
пикселях буфера, старый damage() — в surface-координатах; путаница даёт классические «полосы»
неперерисованных областей на HiDPI.
Синхронизация. Долго Linux полагался на implicit sync: к dma-buf прикреплены fence’ы,
ядро само упорядочивает доступ. Это плохо ложится на модель Vulkan и на драйвер NVIDIA, поэтому
в 2024-м в протокол вошёл linux-drm-syncobj-v1 — explicit sync, где клиент передаёт точки
таймлайна явно. Практический эффект — исчезновение класса глитчей, которые годами приписывали
«сырому Wayland».
Чего клиент принципиально не может
Узнать свою абсолютную позицию и позиции чужих окон; переместить себя в заданную точку; прочитать содержимое чужого окна или экрана; перехватить ввод без фокуса; синтезировать нажатия в чужое окно; повесить глобальный хоткей напрямую. Это не баги, а ровно то, ради чего всё затевалось.
Всё перечисленное существует, но через порталы — D-Bus сервис xdg-desktop-portal, который
спрашивает пользователя и выдаёт ограниченный доступ:
$ busctl --user introspect org.freedesktop.portal.Desktop \
/org/freedesktop/portal/desktop | grep -E 'ScreenCast|GlobalShortcuts|RemoteDesktop'
Поэтому OBS и браузеры захватывают экран через PipeWire, а не через XGetImage, и диалог
«выберите окно для демонстрации» рисует композитор, а не приложение. Модель ровно та же, что у
разрешений в мобильных ОС.
XWayland позволяет старым клиентам жить дальше: это X-сервер, который сам является Wayland-клиентом. Внутри него действуют старые правила — X-клиенты видят друг друга и могут перехватывать ввод друг у друга, но не у нативных Wayland-окон. Это компромисс, а не изоляция.
$ echo $DISPLAY $WAYLAND_DISPLAY
:0 wayland-0 # оба разом — значит XWayland поднят
$ xlsclients # видно только X-клиентов; нативные окна невидимы
Оконные менеджеры и композиторы: таксономия
Политика размещения делится на три семейства: stacking (окна как листы бумаги — Openbox, Mutter/KWin), tiling (экран делится без перекрытий алгоритмом — i3, sway, dwm, Hyprland, river, xmonad) и dynamic (гибрид с плавающим режимом — dwm, awesome, Hyprland).
| Роль | X11 | Wayland |
|---|---|---|
| Сервер | Xorg | сам композитор |
| Тайлинг «для клавиатуры» | i3, dwm, awesome, xmonad | sway, river, Hyprland, niri |
| Полный DE | GNOME/Mutter, KDE/KWin в X-режиме | GNOME/Mutter, KDE/KWin, COSMIC |
| Headless / тесты | Xvfb, Xephyr | Weston, cage, sway --headless |
| Библиотека для своего WM | xcb-util-wm | wlroots (C), Smithay (Rust) |
wlroots — фактически «libX11 наоборот»: библиотека, реализующая всё скучное (KMS-бэкенд, libinput, рендер, буферы, десяток протоколов) и оставляющая автору только политику. Sway на ней — около 20 тысяч строк. Smithay — то же на Rust, на нём построен COSMIC.
DE (desktop environment) — это не WM, а набор: композитор + оболочка (панель, лаунчер, уведомления) + настройки + бэкенд порталов + агент polkit + менеджер сессии. Отсюда практическое следствие: поставив «просто sway», вы получите пустой экран без панели, без диалога шаринга и без полномочного polkit-агента — всё это придётся собрать самому.
и «просто работает»?} B -- да --> C[GNOME или KDE Plasma
Wayland по умолчанию] B -- нет --> D{Тайлинг с клавиатуры?} D -- нет --> E[Xfce, LXQt, Openbox
пока преимущественно X11] D -- да --> F{Нужны xdotool, старые X-утилиты,
экзотика с NVIDIA?} F -- да --> G[i3 / awesome на X11
цена: нет изоляции, один DPI] F -- нет --> H[sway / Hyprland / niri
цена: панель и порталы вручную] C --> I{Смешанные DPI, сенсор, HDR, VRR?} I -- да --> J[Только Wayland: per-output scale,
атомарный кадр] I -- нет --> K[Рабочий любой вариант]
Ввод: evdev, libinput, XKB и seat
Ввод приходит из /dev/input/event* — интерфейс evdev, поток структур
struct input_event {time, type, code, value}. Поверх него работает libinput (Peter
Hutterer, 2014): акселерация указателя, тапы и жесты тачпада, palm rejection, калибровка
тачскрина. Ею пользуются и Xorg (через xf86-input-libinput), и все Wayland-композиторы —
поэтому поведение тачпада в одном дистрибутиве обычно совпадает в обоих сессиях.
$ sudo libinput list-devices | head -8
Device: Synaptics Touchpad
Kernel: /dev/input/event7
Seat: seat0, default
Tapping: disabled
Accel profile: flat *adaptive
$ sudo libinput debug-events --verbose # что видит стек ДО тулкита
$ sudo evtest /dev/input/event7 # сырые коды ядра
XKB переводит keycode в символ (правила, модель, раскладка, вариант, опции). Несмотря на «X»
в названии, libxkbcommon используется и в Wayland — но конфигурируется иначе:
# X11: состояние сервера, меняется на лету любым клиентом
$ setxkbmap -layout us,ru -option grp:alt_shift_toggle
# Wayland: keymap приходит клиенту ОТ композитора, настраивается в композиторе
# sway: input * xkb_layout "us,ru"
# GNOME: gsettings set org.gnome.desktop.input-sources sources "[('xkb','us'),('xkb','ru')]"
# для приложений вне сессии — переменные libxkbcommon:
$ XKB_DEFAULT_LAYOUT=us,ru XKB_DEFAULT_OPTIONS=grp:alt_shift_toggle myapp
Seat — набор устройств ввода/вывода одного рабочего места. Кто-то должен выдать композитору
права на /dev/dri/card0 и /dev/input/*, не делая его root: в Linux это обычно
systemd-logind (см. системы инициализации), без systemd
и на FreeBSD — seatd.
$ loginctl show-session "$XDG_SESSION_ID" -p Type -p Active -p Seat
Type=wayland
Active=yes
Seat=seat0
Отсюда типичная ошибка Failed to open /dev/dri/card0: Permission denied при запуске
композитора по ssh: нет активной сессии на seat0 — права не выданы.
Практика: что делать прикладному разработчику
Выбор бэкенда. Если приложение выглядит размыто на HiDPI под Wayland — почти наверняка оно
идёт через XWayland, который масштабирует растягиванием. Проверка: xlsclients | grep myapp —
если видно, значит X-клиент.
$ GDK_BACKEND=wayland,x11 ./app # GTK
$ QT_QPA_PLATFORM="wayland;xcb" ./app # Qt
$ SDL_VIDEODRIVER=wayland ./game # SDL
$ ELECTRON_OZONE_PLATFORM_HINT=auto ./app # Electron
$ chromium --ozone-platform=wayland
Отладка протокола. WAYLAND_DEBUG=1 — недооценённый инструмент: показывает ровно то, что
приложение просит и получает, включая версии интерфейсов. Половина вопросов «почему не работает
дробное масштабирование» закрывается взглядом на то, забиндился ли клиент к
wp_fractional_scale_manager_v1.
$ WAYLAND_DEBUG=1 foot 2>&1 | head -5
-> wl_display@1.get_registry(new id wl_registry@2)
{Default Queue} wl_registry@2.global(1, "wl_compositor", 6)
-> wl_surface@7.attach(wl_buffer@12, 0, 0)
-> wl_surface@7.commit()
{Default Queue} wl_callback@14.done(2847113)
$ xtrace -D:9 -- xterm # X11-аналог
$ sudo lsof /dev/dri/card0 # кто держит DRM master
$ sudo cat /sys/kernel/debug/dri/0/state # текущее atomic-состояние
$ sudo intel_gpu_top # нагрузка GPU (Intel)
$ cat /sys/class/drm/card0/device/gpu_busy_percent # amdgpu
Headless и CI. Монитор не нужен ни в одном из миров:
$ xvfb-run -a --server-args="-screen 0 1920x1080x24" pytest tests/gui/
$ Xephyr :2 -screen 1280x800 & # вложенный сервер: отладка своего WM
$ WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1 sway &
$ WAYLAND_DISPLAY=wayland-1 ./integration-tests
$ weston --backend=headless --width=1920 --height=1080 &
$ cage ./kiosk-app # один клиент на весь экран
Скриншот в headless-сессии на wlroots снимает grim, в GNOME — портал. Универсального
import -window root больше нет, и это первое, что ломается при переносе старых CI-пайплайнов.
Удалённый доступ. ssh -X пробрасывает протокол (сервер рисует у вас), waypipe
пробрасывает буферы (приложение рисует у себя, картинка едет сжатой), RDP-бэкенд композитора
(gnome-remote-desktop, weston --backend=rdp) пробрасывает готовый рабочий стол. Для
приложений с активным GPU-рендером первый вариант почти всегда самый медленный; ssh -Y в
отличие от -X даёт клиенту полный доступ к вашему дисплею — не используйте по привычке.
Плавность. Пять рычагов, если картинка «дёргается»: убедиться, что работает direct
scanout (полноэкранный буфер, совпавший по формату и modifier, идёт мимо GPU-композиции);
включить VRR (adaptive_sync on в sway) — монитор ждёт кадр, а не наоборот; для
соревновательных игр разрешить tearing-control (wp_tearing_control_v1); проверить
динамический дедлайн рендера (Mutter будит клиентов ближе к vblank, в sway это
max_render_time); не смешивать dGPU и iGPU без нужды — буфер с дискретной карты, показываемый
встроенной, копируется через системную память.
Как это выглядит в macOS и Windows
Модель Wayland — не эксперимент Linux, а то, к чему индустрия пришла независимо (подробнее в статье о других ОС).
macOS. WindowServer — единственный процесс, владеющий экраном и вводом. Приложение строит
дерево слоёв Core Animation; сами слои — IOSurface, разделяемые GPU-буферы (прямой аналог
dma-buf), композицию делает Quartz Compositor внутри WindowServer. Сменяемого оконного менеджера
не существует вовсе: сторонние тайлеры (yabai, Amethyst) работают через Accessibility API и
упираются в System Integrity Protection. Скриншот и запись экрана требуют явного разрешения в
настройках приватности — ровно логика порталов.
Windows NT. Здесь наоборот: оконный менеджер живёт в ядре, в win32k.sys (USER и GDI
переехали в кольцо 0 ещё в NT 4.0 ради скорости — и породили целый класс уязвимостей повышения
привилегий). Поверх с Vista работает DWM: каждое окно рисует в свою redirection surface, DWM
собирает кадр через Direct3D; с Windows 8 композицию нельзя выключить. Драйверы — модель
WDDM с планировщиком GPU в dxgkrnl.sys, вывод — DXGI flip model (аналог page flip) плюс
multi-plane overlay (аналог KMS-плоскостей). Сеть решается отдельным протоколом RDP, а не
свойством оконной системы.
Вывод: «один доверенный композитор + разделяемые буферы + обязательная композиция» — мейнстрим 2020-х. X11 с сетевым протоколом и взаимно-прозрачными клиентами оказался исторической аномалией, а не нормой.
Типичные заблуждения
«Wayland — это программа/сервер». Wayland — протокол и библиотека libwayland. Установить
его нельзя; вы устанавливаете композитор, который его реализует. Weston — эталон для тестов, не
десктоп.
«Wayland быстрее X11». На синтетике часто нет. Выигрыш не в FPS, а в отсутствии тиринга, корректном обратном давлении (меньше расход батареи), атомарном ресайзе и per-output масштабе. «Быстрее» здесь означает «предсказуемее».
«Оконный менеджер рисует окна». Ни в одном из миров: содержимое рисует клиент, WM/композитор
управляет геометрией и собирает кадр. В X11 рамку рисует WM в своём окне-родителе (reparenting)
— отсюда многолетний спор о серверных и клиентских декорациях (xdg-decoration поддержан не
всеми).
«В X11 приложение рисует через сервер». С конца 1990-х — нет: рендер идёт локально в GPU-буфер через DRI/OpenGL, а серверу передаётся готовый буфер (DRI3 + Present). Core drawing API живёт только как легаси.
«xdotool/wmctrl просто сломали». Их не сломали — их лишили привилегии, которой у обычного
приложения быть не должно. Замена — wtype/ydotool (через /dev/uinput, требует прав) и
портал GlobalShortcuts.
«Композитор добавляет кадр задержки, надо выключать». В X11 его действительно можно выключить и получить тиринг вместо задержки. В современных стеках правильный ответ — direct scanout, VRR и tearing-control: задержка убирается точечно, без потери корректности везде остальном.
Мини-итог
- Внизу у всех один фундамент: DRM/KMS (framebuffer → plane → CRTC → encoder → connector), dma-buf для передачи буферов без копии, evdev + libinput для ввода. Знание этих объектов делает диагностику возможной.
- X11 — сетевой протокол с сервером-посредником и тремя независимыми процессами. Даёт прозрачность, автоматизацию и совместимость ценой полного отсутствия изоляции, атомарности кадра и per-output настроек.
- Wayland — протокол объектов, где композитор совмещает роли сервера, WM и композитора.
Атомарный кадр, изоляция, обратное давление через
wl_callback.done— ценой того, что всё «системное» (скриншот, запись, хоткеи, автоматизация) переехало в порталы. - Разработчику важно уметь: понять, идёт ли приложение через XWayland; прочитать
wayland-infoкак контракт возможностей; включитьWAYLAND_DEBUG=1; поднять headless-сессию для CI. - Модель «единственный доверенный композитор» — та же, что в macOS (WindowServer) и Windows (DWM). Linux пришёл туда позже, но в ту же точку.
Источники
- The X Window System Protocol, X11R7 — первоисточник, читается на удивление легко.
- ICCCM и EWMH — контракт между приложением и оконным менеджером.
- Wayland Protocol Specification — официальная книга по протоколу и объектной модели.
- Drew DeVault, The Wayland Protocol — лучшее практическое введение с кодом.
- wayland-protocols — реестр стабильных и экспериментальных расширений.
- Linux GPU Driver Developer’s Guide и dma-buf — atomic modesetting, плоскости, modifiers.
- libinput documentation — жесты, акселерация, отладка ввода.
- xdg-desktop-portal — API скриншотов, записи экрана, глобальных хоткеев.
- wlroots и Smithay — если захочется написать свой композитор.
- FreeBSD Handbook: X11 / Wayland — различия в настройке на BSD.
- man:
drm(7),drm-kms(7),Xserver(1),xkeyboard-config(7),libinput(4),weston(1),sway(5).
Что дальше
Мы разобрали, как один процесс получает монопольные права на экран и раздаёт их остальным.
Следующая тема — та же задача арбитража, но на уровне целой машины: как один хост изображает
много независимых машин, где проходит граница между виртуализацией и контейнерами и почему
/dev/dri внутри контейнера — отдельный разговор.
Виртуализация и контейнеры: KVM, Xen, QEMU, jails, zones, OCI-рантаймы