X11 это тот механизм на чем работает весь графический интерфейс Unix подобных ОС.
Но мало кто знает как он работает на самом деле. Потому что с годами он оброс слоями и слоями библиотек, которые стремятся скрыть саму сущность протокола.
А протокол в своей сути прекрасен. Он лаконичен и почти совершенен.
В Интернете есть полная документация по протоколу. Но дело в том, что эта документация большая, написана не совсем ясным языком и, по сути, является просто спецификацией. Важные моменты никак не обозначены, а как использовать – тоже оставлено на фантазию читателя.
А все книги и статьи по использованию X11 описывают это через библиотеки прокладки типа XLib и XCB, и даже, что хуже, GTK или Qt.
Так что документацию приходится читать всю и самому выделять что важно, а что не очень. Придумывать сценарии использования и писать хотя бы короткие программы чтобы испробовать как все работает на самом деле.
Как бы то ни было, если кому-то интересно как все работает на самом деле, пожалуйста под кат.
Суть
Суть X11 в том, что есть программа сервер (X server) которая ожидает подключения и выполняет те команды которые получает от клиента. Например, создать графическое окно. Нарисовать что-то и так далее.
Клиенты подключаются к серверу через обычный сокет. Посылают команды и получают обратно ответы, ошибки, если что-то пошло не так, а также события (например перемещения мыши, нажимания на кнопки и т.п.)
Клиент, по сути это консольная программа, которая с графикой не имеет ничего общего, кроме этого сетевого соединения.
Протокол
Весь основной протокол описан в документе X Window System Protocol
Самое полезное в этом документе, это приложение «B», где описано побайтно, что и куда присылается и принимается.
Я буду цитировать отрывки, чтобы иллюстрировать текст.
Идентификаторы
Все объекты в X имеют идентификатор. Это 32 битовое число, которое генерирует клиент и передает серверу, чтобы обозначить создаваемый объект. Например, окно, курсор, картинка и т.д.
Другой тип идентификаторов это ATOM. Атомы это тоже 32 битовые числа, но их генерирует сервер. Клиент передает серверу какую-то символьную строку, а сервер в ответ дает число. Одинаковым строкам всегда соответствует одинаковое число. Это похоже на хеширование, но сделано по другому – сервер просто хранит список строк и присваивает им номера. Если какой-то клиент запросит атом для строки которая уже находится в списке, ему возвращают номер строки в списке.
Атомы используются прежде всего, чтобы разные клиенты могли обмениваться информацией друг с другом, используя стандартные текстовые идентификаторы.
А чтобы не грузить сетевой обмен длинными текстовыми идентификаторами, передаются собственно числа.
Чтобы снизить нагрузку на сервер, самые важные атомы определены в стандарте и всегда имеют одни и те же значения. Если кому-то интересно, список здесь:
То что написано большими буквами, является той строкой из которой генерирован атом:
atomPRIMARY = 1 atomSECONDARY = 2 atomARC = 3 atomATOM = 4 atomBITMAP = 5 atomCARDINAL = 6 atomCOLORMAP = 7 atomCURSOR = 8 atomCUT_BUFFER0 = 9 atomCUT_BUFFER1 = 10 atomCUT_BUFFER2 = 11 atomCUT_BUFFER3 = 12 atomCUT_BUFFER4 = 13 atomCUT_BUFFER5 = 14 atomCUT_BUFFER6 = 15 atomCUT_BUFFER7 = 16 atomDRAWABLE = 17 atomFONT = 18 atomINTEGER = 19 atomPIXMAP = 20 atomPOINT = 21 atomRECTANGLE = 22 atomRESOURCE_MANAGER = 23 atomRGB_COLOR_MAP = 24 atomRGB_BEST_MAP = 25 atomRGB_BLUE_MAP = 26 atomRGB_DEFAULT_MAP = 27 atomRGB_GRAY_MAP = 28 atomRGB_GREEN_MAP = 29 atomRGB_RED_MAP = 30 atomSTRING = 31 atomVISUALID = 32 atomWINDOW = 33 atomWM_COMMAND = 34 atomWM_HINTS = 35 atomWM_CLIENT_MACHINE = 36 atomWM_ICON_NAME = 37 atomWM_ICON_SIZE = 38 atomWM_NAME = 39 atomWM_NORMAL_HINTS = 40 atomWM_SIZE_HINTS = 41 atomWM_ZOOM_HINTS = 42 atomMIN_SPACE = 43 atomNORM_SPACE = 44 atomMAX_SPACE = 45 atomEND_SPACE = 46 atomSUPERSCRIPT_X = 47 atomSUPERSCRIPT_Y = 48 atomSUBSCRIPT_X = 49 atomSUBSCRIPT_Y = 50 atomUNDERLINE_POSITION = 51 atomUNDERLINE_THICKNESS= 52 atomSTRIKEOUT_ASCENT = 53 atomSTRIKEOUT_DESCENT = 54 atomITALIC_ANGLE = 55 atomX_HEIGHT = 56 atomQUAD_WIDTH = 57 atomWEIGHT = 58 atomPOINT_SIZE = 59 atomRESOLUTION = 60 atomCOPYRIGHT = 61 atomNOTICE = 62 atomFONT_NAME = 63 atomFAMILY_NAME = 64 atomFULL_NAME = 65 atomCAP_HEIGHT = 66 atomWM_CLASS = 67 atomWM_TRANSIENT_FOR = 68
Запросы
Все запросы в X11 бинарные, с полями разной длины. По сути, здесь есть поля длиной в 1 байт, 2 байта и 4 байта.
Первые 4 байта запроса всегда присутствуют и всегда содержат одинаковую информацию:
| Смещение | Длина | Содержание |
|---|---|---|
| 0 | 1 | Код команды. Основной протокол использует только значения от 1 до 127, а значения больше 127 выделены расширениям. |
| 1 | 1 | Подкоманда или какой-то параметр запроса длиной в 1 байт или не используется. |
| 2 | 2 | Длина всего запроса в двойных словах (4 байта). |
Прочтя этот заголовок, сервер уже знает сколько байт (а точнее двойных слов) еще надо прочесть, чтобы забрать весь запрос.
Чтобы не быть слишком голословным покажу простой пример:
Запрос «DestroyWindow» кодируется вот так (допустим хотим закрыть окно с ID 0x12345678):
| Смещение | Длина | Значение | Заметки |
|---|---|---|---|
| 0 | 1 | 0x03 | 3 это код операции DestroyWindow |
| 1 | 1 | 0x00 | Не используется. Значение может быть любое. Сервер все-равно его не смотрит. |
| 2 | 2 | 0x0002 | Длина запроса 2 двойных слова или 8 байт. |
| 4 | 4 | 0x12345678 | Идентификатор окна. |
Или в итоге, по сокету уходит вот что: 03 00 02 00 78 56 34 12
Получив этот запрос, X сервер закроет окно с идентификатором 0x12345678
В документации протокола (а точнее в приложении), вот это запрос DestroyWindow описан следующим синтаксисом:
1 4 opcode 1 unused 2 2 request length 4 WINDOW window
А сейчас что-то посложнее: «CreateWindow».
Предварительно надо выбрать идентификатор окна. Выберем опять 0x12345678 чтобы было попроще.
Еще понадобиться идентификатор коренного окна (это служебное окно, которое занимает весь дисплей и является родительским для всех окон верхнего уровня). Допустим его идентификатор 0x9abcdef0 (а откуда взять реальные значен��я, я расскажу немножко позже).
| Смещение | Длина | Имя поля | Значение | Заметки |
|---|---|---|---|---|
| 0 | 1 | opcode | 0x01 | Операция CreateWindow == 1 |
| 1 | 1 | depth | 0x00 | Глубина цвета окна. 0 значит CopyFromParent |
| 2 | 2 | length | 0x0008 | |
| 4 | 4 | wid | 0x12345678 | Идентификатор, который выбрали. |
| 8 | 4 | parent | 0x9abcdef0 | |
| 12 | 2 | x | 0x64 | Это X координата верхнего левого угла нашего окна. |
| 14 | 2 | y | 0x65 | Это Y координата окна. |
| 16 | 2 | width | 0xc8 | Ширина окна. |
| 18 | 2 | height | 0x66 | Высота окна. |
| 20 | 2 | border | 0x0000 | Ширина рамки окна. |
| 22 | 2 | class | 0x0001 | 1 это окно InputOutput. Есть и InputOnly, но они слишком специфические (да и не окна по сути) и их не будем рассматривать здесь. |
| 24 | 4 | visual | 0x00000000 | 0 значит скопировать из родителя. Visual это какое-то абстрактное представление экрана в котором я так и не разобрался. Но CopyFromParent работает всегда. ;) |
| 28 | 4 | value_mask | 0x00000000 | Здесь кончается фиксированная часть запроса. (длиной в 8 двойных словах). Если нужно, можно задать дополнительные параметры окна. Для этого нужно в value_mask поставить единицы в некоторые биты, поставить необходимые параметры после 32го байта запроса и соответственно увеличить длины запроса в поле length на нужное число двойных слов. |
И так, итоговый запрос который отправляем на сокет: 01 00 08 00 78 65 43 21 f0 de bc 9a 64 65 c8 66 00 00 01 00 00 00 00 00 00 00 00 00
Вот и полное описание запроса в приложении протокола:
1 1 opcode 1 CARD8 depth 2 8+n request length 4 WINDOW wid 4 WINDOW parent 2 INT16 x 2 INT16 y 2 CARD16 width 2 CARD16 height 2 CARD16 border-width 2 class 0 CopyFromParent 1 InputOutput 2 InputOnly 4 VISUALID visual 0 CopyFromParent 4 BITMASK value-mask (has n bits set to 1) #x00000001 background-pixmap #x00000002 background-pixel #x00000004 border-pixmap #x00000008 border-pixel #x00000010 bit-gravity #x00000020 win-gravity #x00000040 backing-store #x00000080 backing-planes #x00000100 backing-pixel #x00000200 override-redirect #x00000400 save-under #x00000800 event-mask #x00001000 do-not-propagate-mask #x00002000 colormap #x00004000 cursor 4n LISTofVALUE value-list VALUEs 4 PIXMAP background-pixmap 0 None 1 ParentRelative 4 CARD32 background-pixel 4 PIXMAP border-pixmap 0 CopyFromParent 4 CARD32 border-pixel 1 BITGRAVITY bit-gravity 1 WINGRAVITY win-gravity 1 backing-store 0 NotUseful 1 WhenMapped 2 Always 4 CARD32 backing-planes 4 CARD32 backing-pixel 1 BOOL override-redirect 1 BOOL save-under 4 SETofEVENT event-mask 4 SETofDEVICEEVENT do-not-propagate-mask 4 COLORMAP colormap 0 CopyFromParent 4 CURSOR cursor 0 None
Немножко сложнее, но надеюсь более-менее понятно… Сложность здесь из-за того, что в запросе можно передать кучу параметров окна разного вида и формата. Но по сути все идет последовательно и более-менее логично.
После получения этого запроса, сервер создает окно с заданными параметрами. Но это окно не появится, так как все еще не показано на экране. Делаем это через запрос «MapWindow». На фоне прежнего, он совсем простенький:
| Смещение | Длина | Имя поля | Значение | Заметки |
|---|---|---|---|---|
| 0 | 1 | opcode | 0x08 | Операция MapWindow == 8 |
| 1 | 1 | 0x00 | Не используется | |
| 2 | 2 | length | 0x0002 | Длина 8 байт. |
| 4 | 4 | wid | 0x12345678 | Это ID нашего окна. |
На сокет уходит: 08 00 02 00 78 56 34 12 а окно становится видным.
Ответы
Сервер тоже присылает нам по сокету информацию. Она бывает 3 вида: Ответы (Reply), События (Events) и Ошибки (Errors).
Все три вида имеют длину минимум 32 байта. (А события и ошибки всегда точно 32 байта). Так что чтение из сервера происходит всегда порциями в 32 байта и если это Reply из тела ответа берем длину дополнительной части и читаем ее тоже.
Вся информация с сервера приходит асинхронно, но ответы и ошибки всегда приходят в порядке запросов чьими результатами они являются.
- Ответы на запросы (Reply). Если запрос предполагает ответ от сервера, то сервер его присылает по сокету, как только обработает запрос. Если ответ содержит информацию, которая помещается в 32 байта, то это все что нужно принять. Если ответ длиннее, то в его теле содержится длина дополнительной части ответа.
Общий формат ответа такой:
| Смещение | Длина | Имя поля | Значение | Заметки |
|---|---|---|---|---|
| 0 | 1 | code | 1 | 1 == Reply |
| 1 | 1 | ? | ? | Или не используется, или используется для какой-то части ответа, длиной в 1 байт. |
| 2 | 2 | sequence | s | Это номер запроса, на котором ответ. |
| 4 | 4 | length | n | Длина ответа сверх первых 32 байта в двойных словах. Если не 0, то надо прочитать с сокета еще 4*n байта, чтобы взять весь ответ. |
- События (Events). Содержат те же 32 байта и генерируются в ответ на какие-то события в GUI. Чтобы получать некоторые события, клиент должен подписаться на них, когда создает окно, например.
Некоторые события общесистемного характера присылаются всегда и всем.
Формат событий такой:
| Смещение | Длина | Имя поля | Значение | Заметки |
|---|---|---|---|---|
| 0 | 1 | code | 2..127 [+128] | > 1 для событий. Если событие прислано от другого клиента через SendEvent, то к номеру события прибавляется 128. (Старший бит устанавливается в 1). |
| 1 | 1 | detail | ? | Деталь о событии если помещается в 1 байт. |
| 2 | 2 | sequence | ? | Номер запроса, после которого случилось событие. |
| 4 | 4 | timestamp | time | Время возникновения события |
| 8 | 24 | ? | Зависят от события. |
- Ошибки. Присылаются если какой-то запрос клиента нельзя было исполнить потому что содержит какую-то ошибку в данных или параметрах. Формат ошибок такой:
| Смещение | Длина | Имя поля | Значение | Заметки |
|---|---|---|---|---|
| 0 | 1 | code | 0 | 0 == Error |
| 1 | 1 | error code | 1..255 | Код ошибки. |
| 2 | 2 | sequence | ? | Номер ошибочного запроса. |
| 4 | 28 | data | ? | Подробности об ошибке. Зависит от кода ошибки. |
Подключение.
А сейчас сделаем шаг назад и рассмотрим наверное самое сложное в X11 – подключение к серверу. К сожалению процедура сложная и запутанная и является камнем преткновения для прямого использования X11.
Именно подключение поднимает уровень вхождения в технологию.
Как мы увидели само использование протокола достаточно просто. Но подключение – это что-то с чем-то!
Само подключение по сути простое – создаем сокет и выполняем connect на него. Но сперва надо узнать адрес сервера. Для этого есть алгоритм:
Смотрим на содержание переменной окружения DISPLAY. Если существует, она содержит адрес X11 сервера в формате: [host]:D.S.
host – это хост сервера. Это может быть имя домейна, может быть строкой "/unix" или просто отсутствовать. Отсутствующий host равен "/unix" и означает что сервер слушает на unix domain сокете на локальной машине.
Кстати, это самый частый случай. Если host присутствует, это значит что подключаться надо к этому хосту, по TCP, через IPv6 адрес.
D это номер дисплея, а S это номер экрана. В большинстве случаев на современных конфигурациях номер экрана будет 0, даже если мониторов больше одного. Все они виртуально объединены в один экран.
От номера дисплея зависит порт подключения к серверу. Если по TCP, то сервер слушает на порт 6000+D. Если подключаемся через unix domain сокет, он находится по адресу /tmp/.X11-unix/X{D} – то есть, нулевой дисплей на /tmp/.X11-unix/X0, первый на /tmp/.X11-unix/X1 и т.д.
И вот, мы подключились к сокету. После подключения, нельзя просто так посылать запросы. Надо сперва отправить на сервер информацию о себе и авторизоваться на сервере.
Все это содержится в первом (а точнее нулевом) запросе, который нестандартный и содержит:
| Смещение | Длина | Имя поля | Значение | Заметки |
|---|---|---|---|---|
| 0 | 1 | byte_order | "B" или "l" | B (0x42) означает BIG-ENDIAN, a "l"(0x6c) – little-endian. |
| 1 | 1 | 0x00 | Не используется | |
| 2 | 2 | major_ver | 11 | Мажорная версия протокола |
| 4 | 2 | minor_ver | 0 | Минорная версия протокола |
| 6 | 2 | auth_proto_len | n | Длина им�� протокола авторизации |
| 8 | 2 | auth_data_len | d | Длина данных авторизации |
| 10 | 2 | not_used | ? | Выравнивание |
| 12 | n | auth_proto | string | Протокол авторизации |
| 12+n | pad(n) | Выравнивание к двойному слову. | ||
| 12+n+pad(n) | d | auth_data | string | Данные авторизации |
| 12+n+pad(n)+d | pad(d) | Выравнивание к двойному слову. |
Первый байт определяет в каком формате наша программа понимает числа. Сервер будет присылать нам все числа длиннее одного байта в этом формате и будет понимать числа которые мы присылаем в этом формате.
Потом следует минимальная версия протокола, которая подошла бы программе. Если сервер поддерживает версию ниже этой, то подключение будет отклонено.
Потом следует имя протокола авторизации и собственно данные авторизации. Это типа доказательство, что эта программа имеет право подключаться к серверу X11.
Откуда берем имя протокола и данные об авторизации? Они находятся в файле, путь к которому находится в переменной окружения $XAUTHORITY. Если эта переменная не существует можно поискать в файле $HOME/.Xauthority – это самый распространенный вариант. Если у вашего приложения нет прав доступа к этому файлу или файл не существует, то значит у вас нет доступа к этому X11 серверу.
Файл бинарный и его формат не слишком хорошо задокументирован. Мне пришлось спрашивать на stackoverflow чтобы разобраться, да и то получилось лишь частично.
Так, структура файла, это последовательность записей вот таких структур:
typedef struct xauth { unsigned short family; unsigned short address_length; char *address; unsigned short number_length; char *number; unsigned short name_length; char *name; unsigned short data_length; char *data; } Xauth;
Но во первых, в файле, конечно указателей нет. Все строки вписаны просто последовательно, символ за символом в файле. Во вторых – все двухбайтовые числа всегда являются big-endian. Вне зависимости от архитектуры компьютера.
address – это HOST адрес сервера.
number – это номер дисплея, который мы уже определили из переменной $DISPLAY, записанный в виде текстовой строки!
name – это имя протокола. В настоящем времени и насколько я знаю, используется только MIT-MAGIC-COOKIE-1 протокол.
data – это массив байтов, примерно вот такой: 07 bd 70 26 1а ab 4c 7c 35 3c c1 b2 cc 25 a2 29. который мы должны переслать серверу в знак, что у нас доступ позволен.
Перебираем этот файл пока не найдем запись, у которой HOST совпадает с хостом из $DISPLAY и номер дисплея с номером дисплея из $DISPLAY. Из этой записи достаем имя протокола и данные авторизации.
И так мы собрали все необходимые данные о нулевом запросе и формируем его:
| Смещение | Длина | Имя поля | Байты | Пояснение |
|---|---|---|---|---|
| 0 | 1 | byte_order | 0x6c | l |
| 1 | 1 | 0x00 | ||
| 2 | 2 | major_ver | 0x0b 0x00 | 0x000b |
| 4 | 2 | minor_ver | 0x00 0x00 | 0x0000 |
| 6 | 2 | auth_proto_len | 0x12 | length("MIT-MAGIC-COOKIE-1") = 18 |
| 8 | 2 | auth_data_len | 0x10 | length(cookie) = 16 |
| 10 | 2 | 0x00 0x00 | не используется. Просто выравнивает. | |
| 12 | 18 | auth_proto | 4d 49 54 2d 4d 41 47 49 43 2d 43 4f 4f 4b 49 45 2d 31 | "MIT-MAGIC-COOKIE-1" |
| 30 | 2 | pad(0x12) | 0x00 0x00 | Выравниваем до 20. |
| 32 | 16 | auth_data | 07 bd 70 26 1а ab 4c 7c 35 3c c1 b2 cc 25 a2 29 |
К серверу уходит: 6c 00 0b 00 00 00 12 00 10 00 00 00 4d 49 54 2d 4d 41 47 49 43 2d 43 4f 4f 4b 49 45 2d 31 00 00 07 bd 70 26 1а ab 4c 7c 35 3c c1 b2 cc 25 a2 29 – всего 48 байтов.
На что сервер может ответить тремя возможными ответами. Вариант ответа определяется по первому байту. Он может быть:
0: Подключение отклонено. Весь ответ содержит:
| Смещение | Длина | Имя поля | Байты | Пояснение |
|---|---|---|---|---|
| 0 | 1 | reply | 0x00 | Failed |
| 1 | 1 | n | n | Длина текстового ответа. |
| 2 | 2 | major_ver | 0x0b 0x00 | Мажорная версия протокола. |
| 4 | 2 | minor_ver | 0x00 0x00 | Минорная версия протокола. |
| 6 | 2 | data_len | (n+p)/4 | Длина дополнительной информации в двойных словах. |
| 8 | n | data | Какое-то текстовое сообщение об ошибке. | |
| 8+n | p | pad(n) | Выравнивание до двойного слова. |
2: Нужна дополнительная аутентификация. Я этого варианта не изучал потому что так и не успел найти систему, которая так бы отвечала…
1: Подключение принято.
Самый хороший для нас вариант. Ответ очень длинный и сложный, содержит главные параметры системы, которые мы должны запомнить и использовать позже в наших запросах.
Я так и не смог нарисовать такую сложную табличку, чтобы все разложить по полочкам. Поэтому вот вам описания ответа из документации протокола:
1 1 Success 1 unused 2 CARD16 protocol-major-version 2 CARD16 protocol-minor-version 2 8+2n+(v+p+m)/4 length in 4-byte units of "additional data" 4 CARD32 release-number 4 CARD32 resource-id-base 4 CARD32 resource-id-mask 4 CARD32 motion-buffer-size 2 v length of vendor 2 CARD16 maximum-request-length 1 CARD8 number of SCREENs in roots 1 n number for FORMATs in pixmap-formats 1 image-byte-order 0 LSBFirst 1 MSBFirst 1 bitmap-format-bit-order 0 LeastSignificant 1 MostSignificant 1 CARD8 bitmap-format-scanline-unit 1 CARD8 bitmap-format-scanline-pad 1 KEYCODE min-keycode 1 KEYCODE max-keycode 4 unused v STRING8 vendor p unused, p=pad(v) 8n LISTofFORMAT pixmap-formats m LISTofSCREEN roots (m is always a multiple of 4) FORMAT 1 CARD8 depth 1 CARD8 bits-per-pixel 1 CARD8 scanline-pad 5 unused SCREEN 4 WINDOW root 4 COLORMAP default-colormap 4 CARD32 white-pixel 4 CARD32 black-pixel 4 SETofEVENT current-input-masks 2 CARD16 width-in-pixels 2 CARD16 height-in-pixels 2 CARD16 width-in-millimeters 2 CARD16 height-in-millimeters 2 CARD16 min-installed-maps 2 CARD16 max-installed-maps 4 VISUALID root-visual 1 backing-stores 0 Never 1 WhenMapped 2 Always 1 BOOL save-unders 1 CARD8 root-depth 1 CARD8 number of DEPTHs in allowed-depths n LISTofDEPTH allowed-depths (n is always a multiple of 4) DEPTH 1 CARD8 depth 1 unused 2 n number of VISUALTYPES in visuals 4 unused 24n LISTofVISUALTYPE visuals VISUALTYPE 4 VISUALID visual-id 1 class 0 StaticGray 1 GrayScale 2 StaticColor 3 PseudoColor 4 TrueColor 5 DirectColor 1 CARD8 bits-per-rgb-value 2 CARD16 colormap-entries 4 CARD32 red-mask 4 CARD32 green-mask 4 CARD32 blue-mask 4 unused
Но как бы и сложным это не выглядело бы, всю информацию не надо запоминать или даже анализировать.
Мы от этого ответа возьмем только то, что важно для нас. И это во первых два числа из полей: resource-id-base и resource-id-mask. Они дают нам диапазон в котором надо генерировать ID константы для всех объектов GUI. (Не забывайте, что в X11 все идентификаторы объектов генерируются на стороне клиента, а серверу именно клиент говорит какой будет ID окна или других объектов.)
Так, у сервера есть только одно ограничение – каждой программе он выделяет диапазон в котором идентификаторы должны помещаться. Так идентификатор должен содержать только те биты которые в resource-id-mask установлены в единицу. И идентификатор должен начинать с resource-id_base.
Еще надо запомнить для будущего использования диапазон клавиатурных кодов (min-keycode/max-keycode), найти в ответе те форматы изображений, которые программа может использовать и которые ей удобны.
Еще обязательно надо найти подходящий SCREEN из списка и оттуда взять идентификатор коренного окна. Он нам нужен, в качестве родительского окна для всех окон верхнего уровня, которые мы будем создавать.
Все остальное более или менее можно проигнорировать.
Я обычно ищу во всем этом многообразии тот SCREEN, который меня устраивает (32 бит TrueColor) и использую только его. А если сервер такое не поддерживает, просто заканчиваю работу. Это сильно упрощает работу и код.
Заключение
Ну это все для первого раза. Надеюсь сумел все объяснить яснее чем в документации и дать то понимание, которое позволит дальше свободно читать документацию (А она и правда хороша, если человек умеет ее понимать).
В качестве упражнения предлагаю конкурс-челендж: Написать программу на bash которая устанавливает соединение с X сервером и создает и показывает окно с заголовком «X11 rules».
Если никто не справится или не захочет, я попробую написать ее в качестве примера для следующей статьи цикла.
Спрашивайте в комментариях если что не ясно. Если что не нравиться тоже пишите. Статья может и будет редактироваться по мере обсуждения.
