Операционные системы Графический стек: X11, Wayland, оконные менеджеры, композиторы и DE
0%

Графический стек: X11, Wayland, оконные менеджеры, композиторы и DE

Графический стек: X11, Wayland, оконные менеджеры, композиторы и DE

Экран один. Приложений — сорок. Клавиатура одна, а претендентов на её ввод — все окна сразу. Видеопамять общая, но браузер не должен читать пиксели вашего банковского клиента. Всё это — классические задачи ОС: арбитраж разделяемого ресурса, изоляция, синхронизация. Разница лишь в том, что в Unix их решают не в ядре, а в пользовательском процессе, который исторически назывался «X-сервер», а сегодня чаще называется «композитор».

Статья — про то, из чего реально собран рабочий стол: какие объекты живут в ядре, какой протокол ходит по сокету, кто решает, где будет окно, почему xdotool не работает в GNOME на Wayland и откуда берутся 16.7 мс задержки.

Проверочный вопрос, отделяющий пользователя от инженера: почему в X11 приложение может само подвинуть своё окно в точку (0,0), а в Wayland — нет, и это не недоработка, а следствие модели безопасности? Ответ объясняет добрую половину «регрессий» при переезде.

Карта: из чего состоит графический стек

Сразу разделим три роли, которые в речи слипаются в слово «графика»:

  • display server — владеет устройствами вывода и ввода, принимает подключения клиентов;
  • window manager (WM) — решает политику: где окно, какого размера, что в фокусе;
  • compositor — собирает из буферов окон финальный кадр и отдаёт его железу.

В X11 это три сменяемых на лету процесса. В Wayland — один. Вся разница между мирами вырастает отсюда.

Ядро: 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

Два узла — не дубликаты, а разные уровни привилегий:

  • card0primary node: modesetting, смена разрешения, page flip. Владеть им может только один процесс — DRM master. Отсюда drmModeSetCrtc failed: Permission denied при попытке поднять второй композитор поверх работающего.
  • renderD128render 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.

Путь кадра через плоскости KMS до развёртки монитора

$ 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 + буквально означает «пустить кого угодно управлять моим рабочим столом».

Принципиальные ограничения, которые нельзя починить, не сломав протокол:

  1. Нет изоляции между клиентами. Любой клиент читает дерево всех окон, снимает с них картинку и перехватывает ввод. Кейлоггер — цикл вокруг XQueryKeymap() или один XGrabKeyboard(); XTEST синтезирует нажатия в любое окно.
  2. Нет атомарного кадра. Новый размер окна и его новое содержимое — разные события, отсюда мигания и «резиновые» рамки при resize.
  3. Один DPI и одна раскладка на весь дисплей — это свойства сервера, не окна. 4K-ноутбук плюс внешний FullHD корректно не решаются.
  4. Композиция поверх, а не вместо. Расширение Composite (2004) перенаправляет рендер окна в offscreen-pixmap, композитный WM собирает из них экран — лишний проход и лишняя копия на кадр.
  5. Развитие остановлено. 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). Пользователь не должен увидеть промежуточное состояние никогда — ни разорванного кадра, ни окна старого размера с новым содержимым.

Сравнение архитектур X11 и Wayland: кто рисует пиксель

Объектная модель

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-v1explicit 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-агента — всё это придётся собрать самому.

Ввод: 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 пришёл туда позже, но в ту же точку.

Источники

Что дальше

Мы разобрали, как один процесс получает монопольные права на экран и раздаёт их остальным. Следующая тема — та же задача арбитража, но на уровне целой машины: как один хост изображает много независимых машин, где проходит граница между виртуализацией и контейнерами и почему /dev/dri внутри контейнера — отдельный разговор.

Виртуализация и контейнеры: KVM, Xen, QEMU, jails, zones, OCI-рантаймы

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов