From 92c69f3e57faef8057b4cf921c4e5d6251a7ac7f Mon Sep 17 00:00:00 2001 From: Roman Pytkov Date: Tue, 18 Nov 2025 14:12:03 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D1=82=D1=87=D1=91=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wayland/docs/wayland-report.md | 2564 ++++++++++++++++++++++++++++++++ 1 file changed, 2564 insertions(+) create mode 100644 wayland/docs/wayland-report.md diff --git a/wayland/docs/wayland-report.md b/wayland/docs/wayland-report.md new file mode 100644 index 0000000..b9a27e5 --- /dev/null +++ b/wayland/docs/wayland-report.md @@ -0,0 +1,2564 @@ +# Лабораторная работа: Анимация фигуры в Wayland + +## 1. Цель работы + +Исследование программного интерфейса Wayland, освоение работы с протоколом xdg-shell, буферами shm и обработкой ввода с клавиатуры. Реализация оконного приложения, в котором анимированная геометрическая фигура (круг, треугольник, квадрат) движется и вращается внутри окна с обработкой столкновений с границами. + +## 2. Постановка задачи + +Разработать приложение на языке C/ASM, использующее Wayland как графическую подсистему, которое: +- инициализирует соединение с композитором Wayland и глобальные объекты (compositor, shm, xdg_wm_base, seat); +- создает одно или несколько окон, каждое в отдельном потоке с собственной очередью событий; +- отображает во всех окнах одну геометрическую фигуру, которая: + - непрерывно движется по области окна; + - отражается от границ окна, не выходя за пределы; + - может менять форму (круг / треугольник / квадрат); + - обладает радиусом и скоростью, управляемыми с клавиатуры; + - может вращаться, причём угловая скорость зависит от столкновений с границами; +- обрабатывает ввод с клавиатуры через xkbcommon; +- реализует часть логики анимации на языке ассемблера NASM. + +## 3. Структура проекта + +Проект `wayland` организован следующим образом: + +- `CMakeLists.txt` — конфигурация сборки, генерация кода протокола xdg-shell и файла смещений `offsets.inc`. +- `generate-offsets.c` — вспомогательная утилита, генерирующая NASM‑файл `offsets.inc` со смещениями полей структур C. +- `src/` — исходный код C и ASM: + - `asm.asm` — точка входа `main`, запуск и завершение инфраструктуры Wayland. + - `figure-animate.asm` — ассемблерная логика шага анимации: обновление позиции, угла, обработка столкновений. + - `figure-draw.c` — растеризация фигур (круг, треугольник, квадрат) в буфер окна. + - `input.c` — работа с устройством ввода `wl_seat` и клавиатурой Wayland + xkbcommon. + - `input-handle.c` — интерпретация нажатий клавиш и изменение параметров анимации. + - `registry.c` — привязка глобальных объектов Wayland и поток обработки глобальной очереди. + - `wayland-runtime.c` — высокоуровневое управление жизненным циклом: соединение, окна, потоки. + - `window.c` — создание окон, буфера shm, цикл перерисовки. +- `include/` — заголовочные файлы с описанием структур и интерфейсов: + - `figure.h`, `figure-animate.h`, `figure-draw.h` — описание фигуры и интерфейсы анимации/отрисовки. + - `geomerty.h` — векторная структура `vec2`. + - `input.h`, `input-handle.h` — интерфейсы обработки ввода. + - `registry.h` — интерфейсы доступа к глобальным объектам Wayland. + - `wayland-runtime.h` — интерфейс запуска/завершения инфраструктуры. + - `window.h` — описание окна и холста для рисования. +- `docs/uml/*.png` — диаграммы архитектуры и поведения. + +## 4. Сборка и запуск + +Сборка проекта выполняется через CMake. + +```bash +cd wayland +cmake -B build/debug-gcc -DCMAKE_BUILD_TYPE=Debug +cmake --build build/debug-gcc +./build/debug-gcc/wayland +``` + +При запуске создаётся главное окно. Управление выполняется с клавиатуры (см. раздел «Обработка ввода»). + +## 5. Описание архитектуры + +### 5.1. Общая архитектура + +На верхнем уровне приложение состоит из следующих компонент: + +- подсистема Wayland‑runtime (`wayland-runtime.c`): + - устанавливает соединение с композитором (`wl_display_connect`); + - через `registry.c` привязывает глобальные объекты (`wl_compositor`, `wl_shm`, `xdg_wm_base`, `wl_seat`); + - управляет пулом окон и их потоков; + - выводит подсказки по горячим клавишам. +- модуль `registry.c`: создаёт `wl_registry`, получает список глобалов и запускает отдельный поток обработки дефолтной очереди. +- модуль `window.c`: создаёт Wayland‑окно (surface + xdg_surface + xdg_toplevel), буфер shm, а также организует цикл перерисовки по frame‑callback. +- модуль `input.c` и `input-handle.c`: отвечают за привязку `wl_seat`, создание `wl_keyboard`, сопоставление scancode → keysym через xkbcommon и реакцию на нажатия. +- модуль `figure-animate.asm` и `figure-draw.c`: реализуют соответственно обновление состояния фигуры и её отрисовку. + +Совокупно это даёт многопоточное Wayland‑приложение, где глобальные объекты обслуживаются в одном потоке, а окна — в отдельных потоках с собственными очередями событий. + +### 5.2. Структуры данных + +Основные структуры описаны в заголовке `figure.h` и `window.h`: + +- `struct vec2` (`geomerty.h`) — 2D‑вектор с компонентами `x` и `y` (тип `float`). +- `enum figure_type` — тип фигуры: `FIGURE_CIRCLE`, `FIGURE_TRIANGLE`, `FIGURE_SQUARE`. +- `struct figure_animation_info` — состояние анимации фигуры: + - `type` — тип фигуры; + - `position` — позиция центра в нормализованных координатах; + - `velocity` — вектор скорости; + - `angle` — текущий угол поворота (в радианах); + - `angular_velocity` — угловая скорость; + - `speed` — скалярный множитель скорости движения; + - `radius` — радиус (в пикселях). +- `struct window_draw_info` (`window.h`) описывает холст окна: + - `data` — указатель на буфер пикселей ARGB; + - `width`, `height` — размер в пикселях; + - `figure` — состояние анимируемой фигуры; + - `figure_mutex` — мьютекс для синхронизации доступа к состоянию фигуры. +- `struct wayland_window` — обёртка над Wayland‑поверхностью и сопутствующими объектами. + +### 5.3. Диаграммы UML + +Для иллюстрации архитектуры используются диаграммы PlantUML, экспортированные в PNG и расположенные в `docs/uml`. + +#### 5.3.1. Диаграмма классов + +![Диаграмма классов](uml/diagram-class.png) + +Диаграмма показывает связи между основными структурами: `wayland_window`, `window_draw_info`, `figure_animation_info`, а также модулями ввода и runtime. + +#### 5.3.2. Компонентная диаграмма + +![Диаграмма компонентов](uml/diagram-component.png) + +Отображает разбиение проекта на подсистемы: runtime, registry, window, input, figure (animate/draw) и их взаимодействие с внешними библиотеками Wayland и xkbcommon. + +#### 5.3.3. Диаграммы активности + +Диаграмма рендеринга кадра: + +![Диаграмма активности рендеринга](uml/diagram-activity-render.png) + +Диаграмма активности анимации фигуры: + +![Диаграмма активности анимации](uml/diagram-activity-animation.png) + +#### 5.3.4. Диаграммы последовательности + +Инициализация и запуск окна: + +![Диаграмма последовательности инициализации](uml/diagram-sequence-init.png) + +Обработка нажатий клавиш: + +![Диаграмма последовательности клавиатуры](uml/diagram-sequence-keyboard.png) + +#### 5.3.5. Диаграмма состояний фигуры + +![Диаграмма состояний фигуры](uml/diagram-state-figure.png) + +Диаграмма отражает возможные состояния фигуры (типы фигуры, направление движения, вращение) и переходы между ними под воздействием столкновений и событий ввода. + +## 6. Логика анимации фигуры (ASM) + +Логика анимации реализована в файле `src/figure-animate.asm`. Основные шаги: + +1. Обработка столкновений (`figure_handle_collision`): + - На окружности вокруг фигуры размещаются вспомогательные точки при помощи функции `place_points_on_circle`. + - Для каждой точки выполняется проверка выхода за нормализованный прямоугольник холста (`check_collision_mask`). + - По результатам собирается битовая маска столкновений (левая, правая, верхняя, нижняя границы). + - При столкновении: + - компоненты скорости `velocity.x` и `velocity.y` отражаются (меняют знак) только если фигура реально движется к соответствующей границе; + - обновляется угловая скорость `angular_velocity` — её знак и величина зависят от направления движения и стороны столкновения, также используется трение и ограничение максимальной угловой скорости. +2. Непосредственный шаг анимации (`figure_animation_step`): + - вычисляется коэффициент нормализации по высоте окна (позиция хранится в относительных координатах); + - обновляется позиция: `position += (velocity * speed) / height_pixels` по осям X и Y; + - обновляется угол: `angle += angular_velocity * ANG_SPEED`; + - к `angular_velocity` применяется коэффициент трения для плавного замедления. + +Использование ассемблера позволяет гибко управлять числовой стабильностью, производительностью и использовать SIMD‑инструкции для вычислений. + +## 7. Отрисовка фигуры (C) + +Отрисовка реализована в `src/figure-draw.c`. Функция `figure_draw` принимает `window_draw_info` и параметры рамки/заливки, после чего в зависимости от типа фигуры вызывает: + +- `draw_circle` — классическая растеризация круга по расстоянию до центра; +- `draw_triangle` — построение равностороннего треугольника, проверка попадания точки по барицентрическим координатам и прорисовка контура через расстояние до рёбер; +- `draw_square` — вычисление вершин квадрата, разбиение области на два треугольника для проверки попадания, прорисовка рамки по расстоянию до сторон. + +Координаты фигуры переводятся из нормализованных в пиксельные, при этом по оси X масштабирование также идёт по высоте для сохранения пропорций. + +## 8. Обработка ввода с клавиатуры + +### 8.1. Регистрация ввода + +Модуль `input.c`: + +- получает объект `wl_seat` из реестра в `registry.c` и регистрирует слушатель `seat_listener`; +- при наличии возможности клавиатуры создаёт `wl_keyboard` и добавляет к нему `keyboard_listener`; +- через событие `keyboard_keymap` получает и компилирует keymap xkbcommon; +- в `keyboard_key` из scancode вычисляется keysym, после чего вызывается `keyboard_key_handle` из `input-handle.c`. + +### 8.2. Горячие клавиши и действия + +Модуль `input-handle.c` реализует функцию: + +- `keyboard_key_handle(...)`, которая нажатия клавиш интерпретирует следующим образом: + - `Enter` — открыть новое окно (`run_window()`); + - `1` — переключить фигуру на круг; + - `2` — переключить фигуру на треугольник; + - `3` — переключить фигуру на квадрат; + - `-` — уменьшить скорость анимации (с ограничением снизу); + - `+` или `=` — увеличить скорость анимации (с ограничением сверху); + - `↑` (стрелка вверх) — увеличить радиус фигуры, но не выходя за границы окна; + - `↓` (стрелка вниз) — уменьшить радиус до минимального значения. + +Изменения осуществляются под мьютексом `figure_mutex`, чтобы гарантировать согласованность с ассемблерным кодом при одновременном доступе из вспомогательного потока анимации. + +## 9. Жизненный цикл и многопоточность + +### 9.1. Инициализация + +Функция `init_wayland`: + +- устанавливает соединение с дисплеем (`wl_display_connect`); +- вызывает `registry_global_bind`, который создаёт `wl_registry`, привязывает глобальные объекты и запускает отдельный поток для их обработки; +- подготавливает массив слотов для оконных потоков и выводит подсказку по клавиатуре. + +### 9.2. Оконные потоки + +Функция `run_window`: + +- выбирает свободный слот в массиве `g_slots`; +- создаёт поток `window_thread_main`, которому передаёт указатель на слот; +- поток создаёт отдельную очередь событий `wl_event_queue`, инициализирует окно (`window_init`), после чего входит в цикл `wl_display_dispatch_queue` до закрытия окна или завершения. + +Параллельно для каждого окна создаётся вспомогательный поток `window_aux_loop`, который: + +- периодически (с задержкой ~15 ms) захватывает мьютекс фигуры; +- вызывает ассемблерную функцию `figure_animation_step`, обновляющую позицию и угол; +- отпускает мьютекс. + +### 9.3. Завершение работы + +Точка входа `main` (`src/asm.asm`): + +- вызывает `init_wayland`; +- запускает первое окно `run_window`; +- ожидает завершения всех окон `wait_for_windows`; +- выполняет `destroy_wayland`, который останавливает поток реестра, очищает input и закрывает соединение с Wayland. + +## 10. Выводы + +В ходе выполнения работы был разработан многопоточный Wayland‑клиент, использующий xdg-shell, shm‑буферы и библиотеку xkbcommon для обработки клавиатуры. Часть логики анимации перенесена в ассемблер NASM с использованием автоматически сгенерированных смещений полей структур C, что позволяет на практике отработать взаимодействие C и ASM‑кода на уровне структур и указателей. + +Приложение демонстрирует движение и вращение геометрических фигур с реакцией на столкновения с границами окна и интерактивным управлением параметрами анимации. Использование UML‑диаграмм (классов, компонентов, активности, последовательности и состояний) помогает формально описать архитектуру и поведение системы. + +--- + +## Приложение A. Исходные тексты + +### A.1. CMakeLists.txt + +```cmake +cmake_minimum_required(VERSION 3.14) + +project(wayland) + +enable_language(ASM_NASM) + +set(CMAKE_ASM_NASM_FLAGS "-f elf64 -I${CMAKE_CURRENT_BINARY_DIR}") +set(CMAKE_ASM_NASM_FLAGS_DEBUG "-gdwarf") + +find_package(PkgConfig REQUIRED) + +# Находим программу wayland-scanner +find_program(WAYLAND_SCANNER wayland-scanner REQUIRED) + +# Ищем директорию с протоколами Wayland +find_path(WAYLAND_PROTOCOLS_DIR + NAMES stable/xdg-shell/xdg-shell.xml + PATHS /usr/share/wayland-protocols /usr/local/share/wayland-protocols + REQUIRED +) + +# Путь к протоколу xdg-shell +set(PROTOCOL_XML ${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml) + +# Генерируем заголовочный файл протокола +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client-protocol.h + COMMAND ${WAYLAND_SCANNER} client-header ${PROTOCOL_XML} ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client-protocol.h + DEPENDS ${PROTOCOL_XML} +) + +# Генерируем исходный файл протокола +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client-protocol.c + COMMAND ${WAYLAND_SCANNER} private-code ${PROTOCOL_XML} ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client-protocol.c + DEPENDS ${PROTOCOL_XML} +) + +# Цель для генерации протокола +add_custom_target(generate-xdg-shell DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client-protocol.c) + +# Генерируем offsets.inc +add_executable(generate-offsets ${CMAKE_CURRENT_SOURCE_DIR}/generate-offsets.c) +target_include_directories(generate-offsets PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/offsets.inc + COMMAND generate-offsets > ${CMAKE_CURRENT_BINARY_DIR}/offsets.inc + DEPENDS generate-offsets + COMMENT "Generating offsets.inc" +) + +add_custom_target(generate-offsets-file DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/offsets.inc) + +# Создаем исполняемый файл из ассемблерного, C и сгенерированного кода +file(GLOB_RECURSE WAYLAND_SOURCES CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c" + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.asm" +) + +# Append the generated XDG C source (will be created in the binary dir) +list(APPEND WAYLAND_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client-protocol.c) + +# Create executable from collected sources +add_executable(wayland ${WAYLAND_SOURCES}) + +# Ensure generated files are produced before building the target +add_dependencies(wayland generate-xdg-shell generate-offsets-file) + +# Include headers and binary dir where generated headers are written +target_include_directories(wayland PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_BINARY_DIR} + ${WAYLAND_CLIENT_INCLUDE_DIRS} + ${XKBCOMMON_INCLUDE_DIRS} +) + +pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client) +set(WAYLAND_CLIENT_LIBRARIES ${WAYLAND_CLIENT_LIBRARIES}) +set(WAYLAND_CLIENT_INCLUDE_DIRS ${WAYLAND_CLIENT_INCLUDE_DIRS}) + +# xkbcommon for keyboard layout handling +pkg_check_modules(XKBCOMMON REQUIRED xkbcommon) +set(XKBCOMMON_LIBRARIES ${XKBCOMMON_LIBRARIES}) +set(XKBCOMMON_INCLUDE_DIRS ${XKBCOMMON_INCLUDE_DIRS}) + +find_package(Threads REQUIRED) + +# Link to system libraries +target_link_libraries(wayland PRIVATE ${WAYLAND_CLIENT_LIBRARIES} ${XKBCOMMON_LIBRARIES} Threads::Threads) +``` + +### A.2. generate-offsets.c + +```c +/* generate-offsets.c + * Генерирует offsets.inc с смещениями полей структур + */ + +#include +#include +#include "window.h" + +int main(void) { + printf("; offsets.inc — generated automatically\n"); + + // window_draw_info offsets + printf("WDI_DATA equ %zu\n", offsetof(struct window_draw_info, data)); + printf("WDI_WIDTH equ %zu\n", offsetof(struct window_draw_info, width)); + printf("WDI_HEIGHT equ %zu\n", offsetof(struct window_draw_info, height)); + printf("WDI_FIGURE equ %zu\n", offsetof(struct window_draw_info, figure)); + printf("WDI_FIGURE_MUTEX equ %zu\n", offsetof(struct window_draw_info, figure_mutex)); + printf("\n"); + + // figure_animation_info offsets + printf("FIG_TYPE equ %zu\n", offsetof(struct figure_animation_info, type)); + printf("FIG_POSITION equ %zu\n", offsetof(struct figure_animation_info, position)); + printf("FIG_VELOCITY equ %zu\n", offsetof(struct figure_animation_info, velocity)); + printf("FIG_ANGLE equ %zu\n", offsetof(struct figure_animation_info, angle)); + printf("FIG_ANG_VEL equ %zu\n", offsetof(struct figure_animation_info, angular_velocity)); + printf("FIG_SPEED equ %zu\n", offsetof(struct figure_animation_info, speed)); + printf("FIG_RADIUS equ %zu\n", offsetof(struct figure_animation_info, radius)); + + return 0; +} +``` + +### A.3. src/asm.asm + +```asm +global main +extern init_wayland +extern run_window +extern wait_for_windows +extern destroy_wayland + +section .text +main: + enter 0, 0 + + call init_wayland + cmp eax, 0 + jl .shutdown + + ; Launch the first window thread (duplicate calls for more windows) + call run_window + + call wait_for_windows + +.shutdown: + call destroy_wayland + leave + ret +``` + +### A.4. src/figure-animate.asm + +```asm +; Макрос для локальных переменных +%macro local 2 + %assign __local_offset __local_offset - %2 + %define %1 (__local_offset) +%endmacro + +; Подключаем автоматически сгенерированные offsets из C структур +%include "offsets.inc" + +section .rodata +PI: dd 3.1415926 +TWO_PI: dd 6.2831852 +NEG_ONE_CONST: dd -1.0 +ONE_CONST: dd 1.0 +ZERO_CONST: dd 0.0 +ABS_MASK: dd 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff +ANG_COLLIDE_COEF: dd 0.2 +ANG_BOOST_FACTOR: dd 0.01 + ; Скорость углового обновления (можно регулировать независимо от FIG_SPEED) + ANG_SPEED: dd 15.0 + ANG_MAX: dd 0.03 + ANG_SWITCH_FACTOR: dd 0.2 + ANG_MAX_DELTA: dd 0.006 + ANG_FRICTION: dd 0.94 + +section .text + +; void figure_animation_step(struct window_draw_info* draw_info); +; Параметры: +; rdi - указатель на struct window_draw_info +%assign __local_offset 0 +global figure_animation_step +figure_animation_step: + enter 0, 0 + + ; Отработка коллизий + push rdi + call figure_handle_collision + pop rdi + + ; Вычислить нормализующий коэффициент: 1.0 / height_pixels + cvtsi2ss xmm4, dword [rdi + WDI_HEIGHT] + movss xmm5, [rel ONE_CONST] + divss xmm5, xmm4 ; xmm5 = 1.0 / height + + ; Обновить позицию: position += (velocity * speed) / height_pixels + ; pos_x += (vel_x * speed) / height + movss xmm0, [rdi + WDI_FIGURE + FIG_VELOCITY] + mulss xmm0, [rdi + WDI_FIGURE + FIG_SPEED] + mulss xmm0, xmm5 ; нормализовать скорость + addss xmm0, [rdi + WDI_FIGURE + FIG_POSITION] + movss [rdi + WDI_FIGURE + FIG_POSITION], xmm0 + + ; pos_y += (vel_y * speed) / height + movss xmm0, [rdi + WDI_FIGURE + FIG_VELOCITY + 4] + mulss xmm0, [rdi + WDI_FIGURE + FIG_SPEED] + mulss xmm0, xmm5 ; нормализовать скорость + addss xmm0, [rdi + WDI_FIGURE + FIG_POSITION + 4] + movss [rdi + WDI_FIGURE + FIG_POSITION + 4], xmm0 + + ; Обновить угол: angle += angular_velocity * ANG_SPEED (локальная константа) + movss xmm0, [rdi + WDI_FIGURE + FIG_ANG_VEL] + mulss xmm0, [rel ANG_SPEED] + addss xmm0, [rdi + WDI_FIGURE + FIG_ANGLE] + movss [rdi + WDI_FIGURE + FIG_ANGLE], xmm0 + + ; Apply angular friction to slow down rotation over time + movss xmm0, [rdi + WDI_FIGURE + FIG_ANG_VEL] + mulss xmm0, [rel ANG_FRICTION] + movss [rdi + WDI_FIGURE + FIG_ANG_VEL], xmm0 + + leave + ret + +; Функция для обработки коллизии, изменяет velocity при обнаружении коллизии с границами +; Параметры: +; rdi - указатель на struct window_draw_info +%assign __local_offset 0 +figure_handle_collision: + enter 128,0 + + local point_buffer, 128 + + ; Сохранить регистры + push r12 + push r13 + push r14 + push r15 + + mov r12, rdi + + ; Нормализовать радиус: radius_normalized = radius_pixels / height_pixels + movss xmm3, [r12 + WDI_FIGURE + FIG_RADIUS] + cvtsi2ss xmm4, dword [r12 + WDI_HEIGHT] + divss xmm3, xmm4 + + ; Вызов place_points_on_circle + movss xmm0, [r12 + WDI_FIGURE + FIG_POSITION] + movss xmm1, [r12 + WDI_FIGURE + FIG_POSITION + 4] + movss xmm2, [r12 + WDI_FIGURE + FIG_ANGLE] ; смещение угла = 0 + mulss xmm2, [rel NEG_ONE_CONST] + + ; Установка правильного количества точек + mov eax, dword [r12 + WDI_FIGURE + FIG_TYPE] + cmp eax, 1 ; FIGURE_TRIANGLE + je .figure_triangle + cmp eax, 2 ; FIGURE_SQUARE + je .figure_square + ; default (FIGURE_CIRCLE and others): 16 points + mov rsi, 16 + jmp .figure_points_done +.figure_triangle: + mov rsi, 3 + jmp .figure_points_done +.figure_square: + mov rsi, 4 +.figure_points_done: + + lea rdi, [rbp + point_buffer] + call place_points_on_circle + + ; Вычислить canvas_width = width_pixels / height_pixels + cvtsi2ss xmm0, dword [r12 + WDI_WIDTH] + cvtsi2ss xmm1, dword [r12 + WDI_HEIGHT] + divss xmm0, xmm1 + movss xmm14, xmm0 ; сохраняем canvas_width в xmm14 + movss xmm13, [rel ONE_CONST] ; canvas_height = 1.0 в xmm13 + + ; Инициализация: r14 = маска коллизий (OR всех точек), r15 = указатель на текущую точку + xor r14, r14 + lea r15, [rbp + point_buffer] + mov rcx, rsi + +.point_check: + ; Загрузить координаты точки + movss xmm2, [r15] ; x + movss xmm3, [r15 + 4] ; y + + ; Вызвать check_collision_mask(canvas_width, canvas_height, x, y) + movss xmm0, xmm14 ; width + movss xmm1, xmm13 ; height = 1.0 + push rcx + call check_collision_mask + pop rcx + + ; Объединить маску коллизий + or r14, rax + + ; Перейти к следующей точке + add r15, 8 + loop .point_check + + ; Проверить, были ли коллизии + test r14, r14 + jz .no_collision + + ; ----------------------- + ; Обновить угловую скорость при коллизии + ; Формула: delta = |relevant_velocity| * speed * ANG_COLLIDE_COEF + ; Знак delta зависит от границы и направления движения. + ; Если итоговое направление совпадает с текущим - даём небольшой буст. + ; ----------------------- + + ; Сохраняем старую угловую скорость и обнуляем суммарный эффект + movss xmm6, [r12 + WDI_FIGURE + FIG_ANG_VEL] ; old ang vel + xorps xmm7, xmm7 ; total delta + + ; LEFT (bit 0): use vertical motion (vel_y) + test r14, 0x1 + jz .skip_left_ang + movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY + 4] ; vel_y + movss xmm1, xmm0 + ; Broadcast ABS_MASK (0x7fffffff) into xmm2 and AND to get abs + mov eax, dword [rel ABS_MASK] + movd xmm2, eax + pshufd xmm2, xmm2, 0x0 + andps xmm1, xmm2 ; abs(vel_y) + mulss xmm1, [r12 + WDI_FIGURE + FIG_SPEED] + mulss xmm1, [rel ANG_COLLIDE_COEF] + ucomiss xmm0, [rel ZERO_CONST] + jb .left_up_ang ; vel_y < 0 -> moving UP + ; moving DOWN -> clockwise (+) + addss xmm7, xmm1 + jmp .skip_left_ang +.left_up_ang: + ; moving UP -> anticlockwise (-) + movss xmm2, [rel NEG_ONE_CONST] + mulss xmm1, xmm2 + addss xmm7, xmm1 +.skip_left_ang: + + ; RIGHT (bit 2): use vertical motion (vel_y) + test r14, 0x4 + jz .skip_right_ang + movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY + 4] ; vel_y + movss xmm1, xmm0 + mov eax, dword [rel ABS_MASK] + movd xmm2, eax + pshufd xmm2, xmm2, 0x0 + andps xmm1, xmm2 + mulss xmm1, [r12 + WDI_FIGURE + FIG_SPEED] + mulss xmm1, [rel ANG_COLLIDE_COEF] + ucomiss xmm0, [rel ZERO_CONST] + jb .right_up_ang ; vel_y < 0 -> moving UP + ; moving DOWN -> anticlockwise (-) + movss xmm2, [rel NEG_ONE_CONST] + mulss xmm1, xmm2 + addss xmm7, xmm1 + jmp .skip_right_ang +.right_up_ang: + ; moving UP -> clockwise (+) + addss xmm7, xmm1 +.skip_right_ang: + + ; TOP (bit 1): use horizontal motion (vel_x) + test r14, 0x2 + jz .skip_top_ang + movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY] ; vel_x + movss xmm1, xmm0 + mov eax, dword [rel ABS_MASK] + movd xmm2, eax + pshufd xmm2, xmm2, 0x0 + andps xmm1, xmm2 + mulss xmm1, [r12 + WDI_FIGURE + FIG_SPEED] + mulss xmm1, [rel ANG_COLLIDE_COEF] + ucomiss xmm0, [rel ZERO_CONST] + ja .top_right_ang ; vel_x > 0 -> moving RIGHT + ; moving LEFT -> clockwise (+) + addss xmm7, xmm1 + jmp .skip_top_ang +.top_right_ang: + ; moving RIGHT -> anticlockwise (-) + movss xmm2, [rel NEG_ONE_CONST] + mulss xmm1, xmm2 + addss xmm7, xmm1 +.skip_top_ang: + + ; BOTTOM (bit 3): use horizontal motion (vel_x) + test r14, 0x8 + jz .skip_bottom_ang + movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY] ; vel_x + movss xmm1, xmm0 + mov eax, dword [rel ABS_MASK] + movd xmm2, eax + pshufd xmm2, xmm2, 0x0 + andps xmm1, xmm2 + mulss xmm1, [r12 + WDI_FIGURE + FIG_SPEED] + mulss xmm1, [rel ANG_COLLIDE_COEF] + ucomiss xmm0, [rel ZERO_CONST] + ja .bottom_right_ang ; vel_x > 0 -> moving RIGHT + ; moving LEFT -> anticlockwise (-) + movss xmm2, [rel NEG_ONE_CONST] + mulss xmm1, xmm2 + addss xmm7, xmm1 + jmp .skip_bottom_ang +.bottom_right_ang: + ; moving RIGHT -> clockwise (+) + addss xmm7, xmm1 +.skip_bottom_ang: + + ; Если суммарный эффект нулевой - ничего не делаем + ucomiss xmm7, [rel ZERO_CONST] + je .ang_no_change + + ; Invert direction rules to match drawing coordinate system + ; (User requested flip — so we reverse sign of computed delta) + movss xmm0, [rel NEG_ONE_CONST] + mulss xmm7, xmm0 + + ; Decide: same direction or switch sign + ucomiss xmm6, [rel ZERO_CONST] + jb .old_neg_dir + ; old >= 0 + ucomiss xmm7, [rel ZERO_CONST] + jae .same_dir + jmp .switch_dir +.old_neg_dir: + ; old < 0 + ucomiss xmm7, [rel ZERO_CONST] + jb .same_dir + jmp .switch_dir + +; If same direction -> boost and add +.same_dir: + mulss xmm7, [rel ANG_BOOST_FACTOR] + ; Clamp delta magnitude to ANG_MAX_DELTA + movss xmm0, xmm7 + movss xmm1, xmm0 + mov eax, dword [rel ABS_MASK] + movd xmm2, eax + pshufd xmm2, xmm2, 0x0 + andps xmm1, xmm2 ; xmm1 = abs(delta) + movss xmm3, [rel ANG_MAX_DELTA] + ucomiss xmm1, xmm3 + ja .cap_delta_same + jmp .after_cap_same +.cap_delta_same: + ; Set abs(delta) = ANG_MAX_DELTA, preserve sign + movss xmm1, [rel ANG_MAX_DELTA] + ucomiss xmm0, [rel ZERO_CONST] + jae .cap_delta_same_pos + movss xmm4, [rel NEG_ONE_CONST] + mulss xmm1, xmm4 +.cap_delta_same_pos: + movss xmm7, xmm1 +.after_cap_same: + addss xmm6, xmm7 + jmp .finish_dir_logic + +; Switch sign -> compute new magnitude using old magnitude and delta +.switch_dir: + ; xmm6 = old, xmm7 = delta + ; abs_old = abs(xmm6) + movss xmm0, xmm6 + movss xmm1, xmm0 + mov eax, dword [rel ABS_MASK] + movd xmm2, eax + pshufd xmm2, xmm2, 0x0 + andps xmm1, xmm2 ; xmm1 = abs_old + + ; abs_delta = abs(xmm7) + movss xmm3, xmm7 + movss xmm4, xmm3 + andps xmm4, xmm2 ; xmm4 = abs_delta + ; Clamp abs_delta + movss xmm5, [rel ANG_MAX_DELTA] + ucomiss xmm4, xmm5 + ja .cap_delta_switch + jmp .delta_not_capped +.cap_delta_switch: + movss xmm4, xmm5 +.delta_not_capped: + + ; abs_old *= ANG_SWITCH_FACTOR + movss xmm5, [rel ANG_SWITCH_FACTOR] + mulss xmm1, xmm5 + + ; new_mag = abs_old + abs_delta + addss xmm1, xmm4 + + ; apply sign of delta (xmm7) + ucomiss xmm7, [rel ZERO_CONST] + jae .switch_positive + ; negative + movss xmm5, [rel NEG_ONE_CONST] + mulss xmm1, xmm5 + movss xmm6, xmm1 + jmp .finish_dir_logic +.switch_positive: + movss xmm6, xmm1 + +.finish_dir_logic: + ; Clamp angular velocity: |xmm6| <= ANG_MAX + movss xmm0, xmm6 + movss xmm1, xmm0 + mov eax, dword [rel ABS_MASK] + movd xmm2, eax + pshufd xmm2, xmm2, 0x0 + andps xmm1, xmm2 ; xmm1 = abs(xmm0) + movss xmm2, [rel ANG_MAX] + ucomiss xmm1, xmm2 + ja .ang_clamp_needed + movss [r12 + WDI_FIGURE + FIG_ANG_VEL], xmm6 + jmp .ang_no_change2 +.ang_clamp_needed: + ; If xmm0 >= 0 -> set +ANG_MAX else set -ANG_MAX + ucomiss xmm0, [rel ZERO_CONST] + jae .ang_positive_clamp + ; negative + movss xmm3, [rel ANG_MAX] + movss xmm4, [rel NEG_ONE_CONST] + mulss xmm3, xmm4 + movss xmm6, xmm3 + movss [r12 + WDI_FIGURE + FIG_ANG_VEL], xmm6 + jmp .ang_no_change2 +.ang_positive_clamp: + movss xmm3, [rel ANG_MAX] + movss xmm6, xmm3 + movss [r12 + WDI_FIGURE + FIG_ANG_VEL], xmm6 +.ang_no_change2: +.ang_no_change: + + ; Обработка коллизий: инвертировать velocity только если движемся к границе + +; Обработка velocity + ; Проверка left (bit 0): инвертировать velocity.x только если vel.x < 0 + test r14, 0x1 + jz .check_right + + movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY] + movss xmm1, [rel ZERO_CONST] + ucomiss xmm0, xmm1 + jae .check_right ; если vel.x >= 0, пропускаем + + ; vel.x < 0, инвертируем + movss xmm1, [rel NEG_ONE_CONST] + mulss xmm0, xmm1 + movss [r12 + WDI_FIGURE + FIG_VELOCITY], xmm0 + +.check_right: + ; Проверка right (bit 2): инвертировать velocity.x только если vel.x > 0 + test r14, 0x4 + jz .check_top + + movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY] + movss xmm1, [rel ZERO_CONST] + ucomiss xmm0, xmm1 + jbe .check_top ; если vel.x <= 0, пропускаем + + ; vel.x > 0, инвертируем + movss xmm1, [rel NEG_ONE_CONST] + mulss xmm0, xmm1 + movss [r12 + WDI_FIGURE + FIG_VELOCITY], xmm0 + +.check_top: + ; Проверка top (bit 1): инвертировать velocity.y только если vel.y < 0 + test r14, 0x2 + jz .check_bottom + + movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY + 4] + movss xmm1, [rel ZERO_CONST] + ucomiss xmm0, xmm1 + jae .check_bottom ; если vel.y >= 0, пропускаем + + ; vel.y < 0, инвертируем + movss xmm1, [rel NEG_ONE_CONST] + mulss xmm0, xmm1 + movss [r12 + WDI_FIGURE + FIG_VELOCITY + 4], xmm0 + +.check_bottom: + ; Проверка bottom (bit 3): инвертировать velocity.y только если vel.y > 0 + test r14, 0x8 + jz .no_collision + + movss xmm0, [r12 + WDI_FIGURE + FIG_VELOCITY + 4] + movss xmm1, [rel ZERO_CONST] + ucomiss xmm0, xmm1 + jbe .no_collision ; если vel.y <= 0, пропускаем + + ; vel.y > 0, инвертируем + movss xmm1, [rel NEG_ONE_CONST] + mulss xmm0, xmm1 + movss [r12 + WDI_FIGURE + FIG_VELOCITY + 4], xmm0 + +.no_collision: + ; Костыль: если центр фигуры вышел за границу, вернуть его на границу + + ; Проверка pos_x < 0 + movss xmm0, [r12 + WDI_FIGURE + FIG_POSITION] + movss xmm1, [rel ZERO_CONST] + ucomiss xmm0, xmm1 + jae .check_pos_x_max + movss [r12 + WDI_FIGURE + FIG_POSITION], xmm1 ; pos_x = 0 + +.check_pos_x_max: + ; Проверка pos_x > canvas_width + movss xmm0, [r12 + WDI_FIGURE + FIG_POSITION] + ucomiss xmm0, xmm14 + jbe .check_pos_y_min + movss [r12 + WDI_FIGURE + FIG_POSITION], xmm14 ; pos_x = canvas_width + +.check_pos_y_min: + ; Проверка pos_y < 0 + movss xmm0, [r12 + WDI_FIGURE + FIG_POSITION + 4] + movss xmm1, [rel ZERO_CONST] + ucomiss xmm0, xmm1 + jae .check_pos_y_max + movss [r12 + WDI_FIGURE + FIG_POSITION + 4], xmm1 ; pos_y = 0 + +.check_pos_y_max: + ; Проверка pos_y > canvas_height (1.0) + movss xmm0, [r12 + WDI_FIGURE + FIG_POSITION + 4] + ucomiss xmm0, xmm13 + jbe .position_clamped + movss [r12 + WDI_FIGURE + FIG_POSITION + 4], xmm13 ; pos_y = 1.0 + +.position_clamped: + ; Восстановить регистры + pop r15 + pop r14 + pop r13 + pop r12 + + leave + ret + + +; Функция для расположения точек на окружности +; Вход: +; xmm0 - pos_x +; xmm1 - pos_y +; xmm2 - смещение точек на окружности в радианах +; xmm3 - радиус +; rdi - адрес буфера для точек +; rsi - количество точек +; Уничтожает: rax, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7 +global place_points_on_circle +place_points_on_circle: + enter 0, 0 + + ; Сохранить координаты центра + movss xmm6, xmm0 + movss xmm7, xmm1 + + ; Рассчитать TWO_PI / rsi и сохранить в xmm4 + movss xmm4, [rel TWO_PI] + cvtsi2ss xmm5, rsi + divss xmm4, xmm5 + + movss xmm5, [rel ZERO_CONST] ; счётчик + mov rcx, rsi +.loop: + movss xmm0, xmm5 + + mulss xmm0, xmm4 ; Счётчик*шаг + addss xmm0, xmm2 ; Прибавить смещение + call sincos_f32_rbp ; Посчитать sincos + mulss xmm0, xmm3 ; sin *= radius + mulss xmm1, xmm3 ; cos *= radius + + addss xmm1, xmm6 ; x = center_x + cos*radius + addss xmm0, xmm7 ; y = center_y + sin*radius + + movss [rdi], xmm1 + movss [rdi + 4], xmm0 + add rdi, 8 + + + addss xmm5, [rel ONE_CONST] + loop .loop + + leave + ret + + +; Функция для рассчёта sin и cos +; Вход: +; xmm0 - угол в радианах (float) +; Выход: +; xmm0 - sin(angle) +; xmm1 - cos(angle) +; Уничтожает: rax, flags +sincos_f32_rbp: + enter 24, 0 ; 24 байта локального места: + ; [rbp-8] — временно угол (для fld) + ; [rbp-16] — sin + ; [rbp-24] — cos + ; (выравнивание по 16 будет соблюдено за счёт enter) + + ; Сохраняем входной угол как float32 в стек для загрузки в x87 + movss [rbp-8], xmm0 + fld dword [rbp-8] ; ST(0) = angle (в extended precision) + + fsincos ; ST(0) = cos, ST(1) = sin + + ; Сохраняем результаты обратно в память как float32 + fstp dword [rbp-24] ; pop cos → [rbp-24] + fstp dword [rbp-16] ; pop sin → [rbp-16] + + ; Загружаем результаты в xmm0 и xmm1 + movss xmm0, [rbp-16] ; xmm0 = sin + movss xmm1, [rbp-24] ; xmm1 = cos + + leave ; эквивалент: mov rsp, rbp / pop rbp + ret + + +; Функция проверки выхода за границы с маской +; Вход: +; xmm0 - width +; xmm1 - height +; xmm2 - x +; xmm3 - y +; Выход: +; rax - битовая маска границ (left=1, top=2, right=4, bottom=8) +check_collision_mask: + xor rax, rax ; очистим rax (маска) + + movss xmm4, [rel ZERO_CONST] + + ; left: x < 0 + ucomiss xmm2, xmm4 + jb .set_left + +.next_left: + ; right: x > width + ucomiss xmm2, xmm0 + ja .set_right + +.next_right: + ; top: y < 0 + ucomiss xmm3, xmm4 + jb .set_top + +.next_top: + ; bottom: y > height + ucomiss xmm3, xmm1 + ja .set_bottom + + ret + +.set_left: + or rax, 1 + jmp .next_left + +.set_top: + or rax, 2 + jmp .next_top + +.set_right: + or rax, 4 + jmp .next_right + +.set_bottom: + or rax, 8 + ret +``` + +### A.5. src/figure-draw.c + +```c +#include +#include +#include +#include +#include +#include "figure-draw.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +/* Вспомогательная функция для установки пикселя */ +static inline void set_pixel(uint8_t *data, int32_t width, int32_t height, int x, int y, uint32_t color) +{ + if (x < 0 || x >= width || y < 0 || y >= height) + return; + + uint32_t *pixel = (uint32_t *)(data + (y * width + x) * 4); + *pixel = color; +} + +/* Проверка, находится ли точка внутри треугольника (барицентрические координаты) */ +static int point_in_triangle(float px, float py, float x1, float y1, float x2, float y2, float x3, float y3) +{ + float d1 = (px - x2) * (y1 - y2) - (x1 - x2) * (py - y2); + float d2 = (px - x3) * (y2 - y3) - (x2 - x3) * (py - y3); + float d3 = (px - x1) * (y3 - y1) - (x3 - x1) * (py - y1); + + int has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0); + int has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0); + + return !(has_neg && has_pos); +} + +/* Расстояние от точки до отрезка */ +static float point_to_segment_distance(float px, float py, float x1, float y1, float x2, float y2) +{ + float dx = x2 - x1; + float dy = y2 - y1; + float len_sq = dx * dx + dy * dy; + + if (len_sq < 0.0001f) { + dx = px - x1; + dy = py - y1; + return sqrtf(dx * dx + dy * dy); + } + + float t = ((px - x1) * dx + (py - y1) * dy) / len_sq; + t = fmaxf(0.0f, fminf(1.0f, t)); + + float proj_x = x1 + t * dx; + float proj_y = y1 + t * dy; + + dx = px - proj_x; + dy = py - proj_y; + + return sqrtf(dx * dx + dy * dy); +} + +/* Рисование круга */ +static void draw_circle(struct window_draw_info* draw_info, float cx, float cy, float radius, + float border_thickness, uint32_t border_color, uint32_t fill_color) +{ + int x_min = (int)fmaxf(0, cx - radius - border_thickness); + int x_max = (int)fminf(draw_info->width - 1, cx + radius + border_thickness); + int y_min = (int)fmaxf(0, cy - radius - border_thickness); + int y_max = (int)fminf(draw_info->height - 1, cy + radius + border_thickness); + + for (int y = y_min; y <= y_max; y++) { + for (int x = x_min; x <= x_max; x++) { + float dx = x - cx; + float dy = y - cy; + float dist = sqrtf(dx * dx + dy * dy); + + if (dist <= radius) { + set_pixel(draw_info->data, draw_info->width, draw_info->height, x, y, fill_color); + } else if (dist <= radius + border_thickness) { + set_pixel(draw_info->data, draw_info->width, draw_info->height, x, y, border_color); + } + } + } +} + +/* Рисование треугольника */ +static void draw_triangle(struct window_draw_info* draw_info, float cx, float cy, float radius, float angle, + float border_thickness, uint32_t border_color, uint32_t fill_color) +{ + /* Вычисляем координаты вершин равностороннего треугольника */ + /* Угол 0 означает, что одна вершина справа от центра */ + float vertices[3][2]; + for (int i = 0; i < 3; i++) { + float vertex_angle = angle + i * (2.0f * M_PI / 3.0f); + vertices[i][0] = cx + radius * cosf(vertex_angle); + vertices[i][1] = cy - radius * sinf(vertex_angle); /* Инвертируем Y для экранных координат */ + } + + /* Находим ограничивающий прямоугольник */ + float min_x = fminf(vertices[0][0], fminf(vertices[1][0], vertices[2][0])) - border_thickness; + float max_x = fmaxf(vertices[0][0], fmaxf(vertices[1][0], vertices[2][0])) + border_thickness; + float min_y = fminf(vertices[0][1], fminf(vertices[1][1], vertices[2][1])) - border_thickness; + float max_y = fmaxf(vertices[0][1], fmaxf(vertices[1][1], vertices[2][1])) + border_thickness; + + int x_min = (int)fmaxf(0, min_x); + int x_max = (int)fminf(draw_info->width - 1, max_x); + int y_min = (int)fmaxf(0, min_y); + int y_max = (int)fminf(draw_info->height - 1, max_y); + + /* Рисуем треугольник */ + for (int y = y_min; y <= y_max; y++) { + for (int x = x_min; x <= x_max; x++) { + int inside = point_in_triangle((float)x, (float)y, + vertices[0][0], vertices[0][1], + vertices[1][0], vertices[1][1], + vertices[2][0], vertices[2][1]); + + if (inside) { + set_pixel(draw_info->data, draw_info->width, draw_info->height, x, y, fill_color); + } else { + /* Проверяем расстояние до границ */ + float dist1 = point_to_segment_distance((float)x, (float)y, + vertices[0][0], vertices[0][1], + vertices[1][0], vertices[1][1]); + float dist2 = point_to_segment_distance((float)x, (float)y, + vertices[1][0], vertices[1][1], + vertices[2][0], vertices[2][1]); + float dist3 = point_to_segment_distance((float)x, (float)y, + vertices[2][0], vertices[2][1], + vertices[0][0], vertices[0][1]); + + float min_dist = fminf(dist1, fminf(dist2, dist3)); + if (min_dist <= border_thickness) { + set_pixel(draw_info->data, draw_info->width, draw_info->height, x, y, border_color); + } + } + } + } +} + +/* Рисование квадрата */ +static void draw_square(struct window_draw_info* draw_info, float cx, float cy, float radius, float angle, + float border_thickness, uint32_t border_color, uint32_t fill_color) +{ + /* Вычисляем координаты вершин квадрата */ + /* Угол 0 означает, что одна вершина справа от центра */ + float vertices[4][2]; + for (int i = 0; i < 4; i++) { + float vertex_angle = angle + i * (M_PI / 2.0f); + vertices[i][0] = cx + radius * cosf(vertex_angle); + vertices[i][1] = cy - radius * sinf(vertex_angle); /* Инвертируем Y для экранных координат */ + } + + /* Находим ограничивающий прямоугольник */ + float min_x = vertices[0][0], max_x = vertices[0][0]; + float min_y = vertices[0][1], max_y = vertices[0][1]; + for (int i = 1; i < 4; i++) { + min_x = fminf(min_x, vertices[i][0]); + max_x = fmaxf(max_x, vertices[i][0]); + min_y = fminf(min_y, vertices[i][1]); + max_y = fmaxf(max_y, vertices[i][1]); + } + + min_x -= border_thickness; + max_x += border_thickness; + min_y -= border_thickness; + max_y += border_thickness; + + int x_min = (int)fmaxf(0, min_x); + int x_max = (int)fminf(draw_info->width - 1, max_x); + int y_min = (int)fmaxf(0, min_y); + int y_max = (int)fminf(draw_info->height - 1, max_y); + + /* Рисуем квадрат */ + for (int y = y_min; y <= y_max; y++) { + for (int x = x_min; x <= x_max; x++) { + float px = (float)x; + float py = (float)y; + + /* Проверяем, находится ли точка внутри квадрата */ + /* Используем два треугольника */ + int inside = point_in_triangle(px, py, + vertices[0][0], vertices[0][1], + vertices[1][0], vertices[1][1], + vertices[2][0], vertices[2][1]) || + point_in_triangle(px, py, + vertices[0][0], vertices[0][1], + vertices[2][0], vertices[2][1], + vertices[3][0], vertices[3][1]); + + if (inside) { + set_pixel(draw_info->data, draw_info->width, draw_info->height, x, y, fill_color); + } else { + /* Проверяем расстояние до границ */ + float min_dist = INFINITY; + for (int i = 0; i < 4; i++) { + int next = (i + 1) % 4; + float dist = point_to_segment_distance(px, py, + vertices[i][0], vertices[i][1], + vertices[next][0], vertices[next][1]); + min_dist = fminf(min_dist, dist); + } + + if (min_dist <= border_thickness) { + set_pixel(draw_info->data, draw_info->width, draw_info->height, x, y, border_color); + } + } + } + } +} + +void figure_draw(struct window_draw_info* draw_info, float border_thickness, uint32_t border_color, uint32_t fill_color) +{ + if (!draw_info || !draw_info->data) + return; + + /* Координаты приходят в относительных единицах + * Y: [0..1] -> умножаем на высоту + * X: нормализован относительно высоты -> умножаем на высоту же + */ + float cx = draw_info->figure.position.x * draw_info->height; + float cy = draw_info->figure.position.y * draw_info->height; + float radius = draw_info->figure.radius; + float angle = draw_info->figure.angle; + enum figure_type type = draw_info->figure.type; + + switch (type) { + case FIGURE_CIRCLE: + draw_circle(draw_info, cx, cy, radius, border_thickness, border_color, fill_color); + break; + + case FIGURE_TRIANGLE: + draw_triangle(draw_info, cx, cy, radius, angle, border_thickness, border_color, fill_color); + break; + + case FIGURE_SQUARE: + draw_square(draw_info, cx, cy, radius, angle, border_thickness, border_color, fill_color); + break; + } +} +``` + +### A.6. src/input.c + +```c +#include +#include +#include +#include +#include +#include +#include + +#include "input.h" +#include "input-handle.h" +#include "wayland-runtime.h" +#include "window.h" + +static struct wl_seat *seat = NULL; +static struct wl_keyboard *keyboard = NULL; +static struct xkb_context *xkb_ctx = NULL; +static struct xkb_keymap *xkb_keymap = NULL; +static struct xkb_state *xkb_state = NULL; +static struct wayland_window* focused_window = NULL; + +/* Обработчики клавиатуры */ +static void keyboard_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) +{ + printf("keyboard: keymap format=%u size=%u\n", format, size); + if (fd < 0) + return; + + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) + { + close(fd); + return; + } + + char *map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) + { + perror("mmap"); + close(fd); + return; + } + + if (!xkb_ctx) + xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + if (xkb_state) + { + xkb_state_unref(xkb_state); + xkb_state = NULL; + } + if (xkb_keymap) + { + xkb_keymap_unref(xkb_keymap); + xkb_keymap = NULL; + } + + xkb_keymap = xkb_keymap_new_from_string(xkb_ctx, map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!xkb_keymap) + { + fprintf(stderr, "Unable to compile xkb keymap\n"); + munmap(map, size); + close(fd); + return; + } + + xkb_state = xkb_state_new(xkb_keymap); + + munmap(map, size); + close(fd); +} + +static void keyboard_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) +{ + /* Сохраняем поверхность, которая получила фокус клавиатуры */ + focused_window = get_window_by_surface(surface); +} + +static void keyboard_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) +{ + /* Если уходим с фокусной поверхности — сбросить фокус */ + if (focused_window && focused_window->wl_surface == surface) + focused_window = NULL; +} + +static void keyboard_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) +{ + enum keyboard_key_state key_state = (state == WL_KEYBOARD_KEY_STATE_PRESSED) ? KEYBOARD_KEY_STATE_PRESSED + : (state == WL_KEYBOARD_KEY_STATE_REPEATED) ? KEYBOARD_KEY_STATE_REPEATED + : KEYBOARD_KEY_STATE_RELEASED; + + if (xkb_state && focused_window) + { + xkb_keycode_t kc = (xkb_keycode_t)key + 8; + xkb_keysym_t ks = xkb_state_key_get_one_sym(xkb_state, kc); + + keyboard_key_handle(kc, ks, key_state, focused_window); + } +} + +static void keyboard_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) +{ + if (xkb_state) + { + xkb_state_update_mask(xkb_state, mods_depressed, mods_latched, mods_locked, group, 0, 0); + } +} + +static void keyboard_repeat_info(void *data, struct wl_keyboard *keyboard, int32_t rate, int32_t delay) +{ +} + +static const struct wl_keyboard_listener keyboard_listener = { + .keymap = keyboard_keymap, + .enter = keyboard_enter, + .leave = keyboard_leave, + .key = keyboard_key, + .modifiers = keyboard_modifiers, + .repeat_info = keyboard_repeat_info}; + +/* Обработчики wl_seat */ +static void seat_capabilities(void *data, struct wl_seat *seat_local, uint32_t caps) +{ + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !keyboard) + { + keyboard = wl_seat_get_keyboard(seat_local); + wl_keyboard_add_listener(keyboard, &keyboard_listener, NULL); + printf("Seat reports keyboard capability - keyboard bound\n"); + } + else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && keyboard) + { + wl_keyboard_destroy(keyboard); + keyboard = NULL; + if (xkb_state) + { + xkb_state_unref(xkb_state); + xkb_state = NULL; + } + if (xkb_keymap) + { + xkb_keymap_unref(xkb_keymap); + xkb_keymap = NULL; + } + } +} + +static void seat_name(void *data, struct wl_seat *seat, const char *name) +{ + printf("Seat name: %s\n", name); +} + +static const struct wl_seat_listener seat_listener = { + .capabilities = seat_capabilities, + .name = seat_name}; + +void input_register_seat(struct wl_seat *s) +{ + if (!s) + return; + if (seat) + { + if (keyboard) + { + wl_keyboard_destroy(keyboard); + keyboard = NULL; + } + wl_seat_destroy(seat); + seat = NULL; + } + seat = s; + wl_seat_add_listener(seat, &seat_listener, NULL); +} + +void input_cleanup(void) +{ + if (keyboard) + { + wl_keyboard_destroy(keyboard); + keyboard = NULL; + } + if (seat) + { + wl_seat_destroy(seat); + seat = NULL; + } + if (xkb_state) + { + xkb_state_unref(xkb_state); + xkb_state = NULL; + } + if (xkb_keymap) + { + xkb_keymap_unref(xkb_keymap); + xkb_keymap = NULL; + } + if (xkb_ctx) + { + xkb_context_unref(xkb_ctx); + xkb_ctx = NULL; + } + focused_window = NULL; +} +``` + +### A.7. src/input-handle.c + +```c +#include "input-handle.h" +#include "wayland-runtime.h" +#include + +void keyboard_key_handle(xkb_keycode_t kc, xkb_keysym_t ks, enum keyboard_key_state state, struct wayland_window *window) +{ + if (ks != XKB_KEY_NoSymbol) + { + char buf[64]; + int n = xkb_keysym_to_utf8(ks, buf, sizeof(buf)); + + // if (n > 0) + // printf("keyboard: symbol '%s' (keysym 0x%x) on surface:%p\n", buf, ks, window); + // else + // printf("keyboard: keysym 0x%x (no UTF-8 representation)\n", ks); + + if(state != KEYBOARD_KEY_STATE_RELEASED) + { + switch (ks) + { + case XKB_KEY_Return: + run_window(); + break; + + case '1': + window->draw_info.figure.type = FIGURE_CIRCLE; + break; + case '2': + window->draw_info.figure.type = FIGURE_TRIANGLE; + break; + case '3': + window->draw_info.figure.type = FIGURE_SQUARE; + break; + case '-': + /* decrease animation speed (multiplicative delta, clamped) */ + pthread_mutex_lock(&window->draw_info.figure_mutex); + window->draw_info.figure.speed -= 1; + if (window->draw_info.figure.speed < 1) + window->draw_info.figure.speed = 1; + printf("keyboard: speed decreased to %.2f\n", window->draw_info.figure.speed); + pthread_mutex_unlock(&window->draw_info.figure_mutex); + break; + case '+': + case '=': /* some layouts may emit '=' for unshifted, handle + via keysym */ + /* increase animation speed (multiplicative delta, clamped) */ + pthread_mutex_lock(&window->draw_info.figure_mutex); + window->draw_info.figure.speed += 1; + if (window->draw_info.figure.speed > 50.0f) + window->draw_info.figure.speed = 50.0f; + printf("keyboard: speed increased to %.2f\n", window->draw_info.figure.speed); + pthread_mutex_unlock(&window->draw_info.figure_mutex); + break; + case XKB_KEY_Up: + /* increase figure radius */ + pthread_mutex_lock(&window->draw_info.figure_mutex); + { + float maxr = fminf(window->draw_info.width, window->draw_info.height) / 2.0f - 1.0f; + window->draw_info.figure.radius += 2.0f; + if (window->draw_info.figure.radius > maxr) + window->draw_info.figure.radius = maxr; + } + printf("keyboard: radius increased to %.2f\n", window->draw_info.figure.radius); + pthread_mutex_unlock(&window->draw_info.figure_mutex); + break; + case XKB_KEY_Down: + /* decrease figure radius */ + pthread_mutex_lock(&window->draw_info.figure_mutex); + { + window->draw_info.figure.radius -= 2.0f; + if (window->draw_info.figure.radius < 1.0f) + window->draw_info.figure.radius = 1.0f; + } + printf("keyboard: radius decreased to %.2f\n", window->draw_info.figure.radius); + pthread_mutex_unlock(&window->draw_info.figure_mutex); + break; + default: + break; + } + } + } +} +``` + +### A.8. src/registry.c + +```c +#include "registry.h" +#include "input.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "xdg-shell-client-protocol.h" + +static void wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial) +{ + (void)data; + xdg_wm_base_pong(wm_base, serial); +} + +static struct xdg_wm_base_listener wm_base_listener = { + .ping = wm_base_ping +}; + + +/* Глобальные объекты, привязанные к единому registry-потоку (общие для процесса) */ +static struct wl_compositor *global_compositor; +static struct wl_shm *global_shm; +static struct xdg_wm_base *global_wm_base; +/* Глобальный реестр живёт на весь срок программы (удаляется при сбросе) */ +static struct wl_registry *global_registry = NULL; + +/* Поток обработки глобальных событий: использует дефолтную очередь */ +static pthread_t global_thread; +static _Atomic int global_thread_running = 0; +static struct wl_display *global_display = NULL; + +static uint32_t clamp_version(uint32_t announced, uint32_t supported) +{ + return (announced < supported) ? announced : supported; +} + +static void registry_global(void *data, struct wl_registry *reg, uint32_t name, const char *intf, uint32_t version) +{ + printf("%u\t%s\tv:%u\n", name, intf, version); + if (!strcmp(intf, wl_compositor_interface.name)) + { + uint32_t ver = clamp_version(version, wl_compositor_interface.version); + /* Привязать compositor на дефолтной очереди сразу */ + if (!global_compositor) + global_compositor = wl_registry_bind(reg, name, &wl_compositor_interface, ver); + } + else if (!strcmp(intf, wl_shm_interface.name)) + { + uint32_t ver = clamp_version(version, wl_shm_interface.version); + if (!global_shm) + global_shm = wl_registry_bind(reg, name, &wl_shm_interface, ver); + } + else if (!strcmp(intf, xdg_wm_base_interface.name)) + { + uint32_t ver = clamp_version(version, xdg_wm_base_interface.version); + if (!global_wm_base) + global_wm_base = wl_registry_bind(reg, name, &xdg_wm_base_interface, ver); + if (global_wm_base) + xdg_wm_base_add_listener(global_wm_base, &wm_base_listener, NULL); + } + else if (!strcmp(intf, wl_seat_interface.name)) + { + uint32_t ver = clamp_version(version, wl_seat_interface.version); + /* Привязать seat на дефолтной очереди */ + struct wl_seat *seat = wl_registry_bind(reg, name, &wl_seat_interface, ver); + if (seat) + input_register_seat(seat); + } +} + +static void registry_global_remove(void *data, struct wl_registry *reg, uint32_t name) +{ + (void)data; + (void)reg; + (void)name; +} + +static const struct wl_registry_listener registry_listener = { + .global = registry_global, + .global_remove = registry_global_remove +}; + + +static void destroy_global_objects(void) +{ + if (global_compositor) + { + wl_compositor_destroy(global_compositor); + global_compositor = NULL; + } + if (global_shm) + { + wl_shm_destroy(global_shm); + global_shm = NULL; + } + if (global_wm_base) + { + xdg_wm_base_destroy(global_wm_base); + global_wm_base = NULL; + } + if (global_registry) + { + wl_registry_destroy(global_registry); + global_registry = NULL; + } +} + + +/* Привязка глобального реестра — привязать compositor/shm/xdg_wm_base и + * запустить поток обработки глобальной очереди. Возвращает 0 при успехе, + * -1 при ошибке. + */ +static void *registry_global_thread(void *arg) +{ + struct wl_display *display = arg; + int fd = wl_display_get_fd(display); + while (global_thread_running) + { + struct pollfd pfd; + pfd.fd = fd; + pfd.events = POLLIN; + pfd.revents = 0; + int ret = poll(&pfd, 1, 100); /* таймаут 100 ms для проверки флага запуска */ + if (ret == -1) + { + if (errno == EINTR) + continue; + break; + } + if (ret == 0) + continue; + if (pfd.revents & POLLIN) + { + int r = wl_display_dispatch(display); + if (r < 0) + break; + } + } + return NULL; +} + +int registry_global_bind(struct wl_display *display) +{ + if (!display) + return -1; + if (global_thread_running) + return 0; /* уже привязан */ + /* Используем дефолтную очередь для глобальных объектов. + * Добавим listener и сделаем roundtrip, чтобы вызывающий код + * мог не делать этого заранее. Реестр привяжет глобалы сразу + * при получении событий. */ + + struct wl_registry *registry = wl_display_get_registry(display); + if (!registry) + { + return -1; + } + + wl_registry_add_listener(registry, ®istry_listener, NULL); + if (wl_display_roundtrip(display) < 0) + { + /* произошла ошибка — уничтожить registry и вернуть ошибку */ + wl_registry_destroy(registry); + return -1; + } + global_registry = registry; + + if (!global_compositor || !global_shm || !global_wm_base) + { + destroy_global_objects(); + return -1; + } + + /* Запустить поток обработки событий дефолтной очереди */ + global_thread_running = 1; + global_display = display; + int thr_res = pthread_create(&global_thread, NULL, registry_global_thread, display); + if (thr_res != 0) + { + global_thread_running = 0; + destroy_global_objects(); + return -1; + } + + return 0; +} + +void registry_global_unbind(void) +{ + if (!global_thread_running) + return; + + /* Корректно остановить поток */ + global_thread_running = 0; + pthread_join(global_thread, NULL); + global_display = NULL; + + destroy_global_objects(); + + return; +} + +struct wl_compositor *registry_get_compositor(void) +{ + return global_compositor; +} + +struct wl_shm *registry_get_shm(void) +{ + return global_shm; +} + +struct xdg_wm_base *registry_get_xdg_wm_base(void) +{ + return global_wm_base; +} +``` + +### A.9. src/wayland-runtime.c + +```c +#include +#include +#include +#include +#include +#include +#include +#include + +#include "input.h" +#include "registry.h" +#include "wayland-runtime.h" +#include "window.h" +#include "figure-animate.h" + +#define MAX_WINDOW_THREADS 128 + +struct window_thread_slot +{ + pthread_t thread; + pthread_t aux_thread; + int active; + struct wl_event_queue *queue; + struct wayland_window window; +}; + +static struct wl_display *g_display = NULL; +static struct window_thread_slot g_slots[MAX_WINDOW_THREADS]; +static pthread_mutex_t g_thread_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t g_thread_cond = PTHREAD_COND_INITIALIZER; +static atomic_int g_active_threads = 0; +static atomic_bool g_shutdown = 0; +static int g_initialized = 0; + +static void signal_thread_exit(struct window_thread_slot *slot) +{ + pthread_mutex_lock(&g_thread_lock); + slot->active = 0; + atomic_fetch_sub(&g_active_threads, 1); + pthread_cond_broadcast(&g_thread_cond); + pthread_mutex_unlock(&g_thread_lock); +} + +#include +#include +#include +#include + +// Прототип ASM-функции +void place_points_on_circle( + float pos_x, + float pos_y, + float offset_rad, + float radius, + float *points, + size_t count); + +struct Point +{ + float x; + float y; +}; + +static void *window_aux_loop(void *arg) +{ + struct window_thread_slot *slot = arg; + struct window_draw_info *draw_info = &slot->window.draw_info; + while (!atomic_load(&g_shutdown) && !window_should_close(&slot->window)) + { + /* На время обновления позиции фигуры локаем мутекс */ + pthread_mutex_lock(&draw_info->figure_mutex); + figure_animation_step(draw_info); + + // const size_t n = 8; + // struct vec2 *pts = malloc(sizeof(struct vec2) * n); + // if (!pts) + // { + // printf("malloc failed\n"); + // } + + // float center_x = 0.0f; + // float center_y = 0.0f; + // float offset = 0.78f; + // float radius = 1.0f; + + // // Вызов ASM-функции + // place_points_on_circle( + // center_x, + // center_y, + // offset, + // radius, + // (float *)pts, // адрес выходного массива + // n); + + // // Вывод для проверки (и удобной точки останова) + // for (size_t i = 0; i < n; i++) + // { + // printf("%zu: x = %f, y = %f\n", i, pts[i].x, pts[i].y); + // } + + // free(pts); + pthread_mutex_unlock(&draw_info->figure_mutex); + + usleep(15 * 1000); + } + return NULL; +} + +static void *window_thread_main(void *arg) +{ + struct window_thread_slot *slot = arg; + memset(&slot->window, 0, sizeof(slot->window)); + + slot->queue = wl_display_create_queue(g_display); + if (!slot->queue) + { + signal_thread_exit(slot); + return NULL; + } + + if (window_init(g_display, slot->queue, &slot->window) < 0) + { + wl_event_queue_destroy(slot->queue); + slot->queue = NULL; + signal_thread_exit(slot); + return NULL; + } + + /* Запуск вспомогательного потока, который пока просто крутится с паузой 100 ms. + * Поток завершится, как только окно будет помечено как закрыто. */ + int aux_res = pthread_create(&slot->aux_thread, NULL, window_aux_loop, slot); + if (aux_res == 0) + pthread_detach(slot->aux_thread); + + while (!atomic_load(&g_shutdown) && !window_should_close(&slot->window)) + { + int dispatch = wl_display_dispatch_queue(g_display, slot->queue); + if (dispatch < 0) + { + if (errno == EINTR) + continue; + atomic_store(&g_shutdown, 1); + break; + } + } + printf("Window #%d closed\n", slot->window.id); + + window_destroy(&slot->window); + + if (slot->queue) + { + wl_event_queue_destroy(slot->queue); + slot->queue = NULL; + } + + signal_thread_exit(slot); + return NULL; +} + +int32_t init_wayland(void) +{ + if (g_initialized) + return 0; + + g_display = wl_display_connect(NULL); + if (!g_display) + return -1; + + /* Привязать глобальные объекты и запустить поток обработки (дефолтная очередь). + * `registry_global_bind` добавляет listener и делает roundtrip. */ + if (registry_global_bind(g_display) < 0) + { + wl_display_disconnect(g_display); + g_display = NULL; + return -1; + } + + atomic_store(&g_shutdown, 0); + atomic_store(&g_active_threads, 0); + g_initialized = 1; + + /* Print keyboard help to the console so users know available shortcuts and + * behaviors. This mirrors the handling implemented in + * `input-handle.c` and acts as a quick usage hint. */ + printf("\nKeyboard shortcuts and behavior:\n"); + printf(" - Enter: open a new window\n"); + printf(" - 1: switch to circle figure\n"); + printf(" - 2: switch to triangle figure\n"); + printf(" - 3: switch to square figure\n"); + printf(" - - : decrease animation speed (clamped, multiplicative)\n"); + printf(" - + or = : increase animation speed (clamped, multiplicative)\n"); + printf(" - Up arrow: increase figure radius (clamped to window bounds)\n"); + printf(" - Down arrow: decrease figure radius (minimum 1.0)\n"); + printf(" Note: actions are triggered on key press (not on release).\n\n"); + return 0; +} + +int32_t run_window(void) +{ + if (!g_initialized || !g_display) + return -1; + + int slot_index = -1; + + pthread_mutex_lock(&g_thread_lock); + for (int i = 0; i < MAX_WINDOW_THREADS; ++i) + { + if (!g_slots[i].active) + { + slot_index = i; + g_slots[i].active = 1; + atomic_fetch_add(&g_active_threads, 1); + break; + } + } + pthread_mutex_unlock(&g_thread_lock); + + if (slot_index < 0) + return -1; + + int create_res = pthread_create(&g_slots[slot_index].thread, NULL, window_thread_main, &g_slots[slot_index]); + if (create_res != 0) + { + pthread_mutex_lock(&g_thread_lock); + g_slots[slot_index].active = 0; + atomic_fetch_sub(&g_active_threads, 1); + pthread_mutex_unlock(&g_thread_lock); + return -1; + } + + pthread_detach(g_slots[slot_index].thread); + return slot_index; +} + +void wait_for_windows(void) +{ + pthread_mutex_lock(&g_thread_lock); + while (atomic_load(&g_active_threads) > 0) + pthread_cond_wait(&g_thread_cond, &g_thread_lock); + pthread_mutex_unlock(&g_thread_lock); +} + +void destroy_wayland(void) +{ + if (!g_initialized) + return; + + wait_for_windows(); + + input_cleanup(); + /* Остановить поток глобального реестра и сбросить глобальные данные */ + registry_global_unbind(); + + if (g_display) + { + wl_display_disconnect(g_display); + g_display = NULL; + } + + memset(g_slots, 0, sizeof(g_slots)); + + g_initialized = 0; + atomic_store(&g_shutdown, 0); +} + +struct wayland_window *get_window_by_surface(struct wl_surface *surf) +{ + for (int i = 0; i < MAX_WINDOW_THREADS; i++) + if (g_slots[i].window.wl_surface == surf) + return &g_slots[i].window; + return NULL; +} +``` + +### A.10. src/window.c + +```c +#include "window.h" +#include "registry.h" +#include "figure-draw.h" +#include "xdg-shell-client-protocol.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "registry.h" + +static atomic_uint_fast64_t shm_counter = 0; + +static int32_t alloc_shm(size_t size) +{ + char name[64]; + uint64_t id = atomic_fetch_add(&shm_counter, 1); + snprintf(name, sizeof(name), "/wayland-shm-%d-%llu", getpid(), (unsigned long long)id); + + int32_t fd = shm_open(name, O_RDWR | O_CREAT, 0600); + if (fd == -1) + return fd; + shm_unlink(name); + if (ftruncate(fd, size) == -1) + { + close(fd); + return -1; + } + return fd; +} + +static void destroy_frame_callback(struct wayland_window *win) +{ + if (win->frame_callback) + { + wl_callback_destroy(win->frame_callback); + win->frame_callback = NULL; + } +} + +static void resize_canvas(struct wayland_window *win) +{ + struct window_draw_info *draw_info = &win->draw_info; + size_t stride = draw_info->width * 4; + size_t size = stride * draw_info->height; + int32_t fd = alloc_shm(size); + if (fd == -1) + return; + + if (win->buffer) + { + wl_buffer_destroy(win->buffer); + win->buffer = NULL; + } + + void *map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) + { + close(fd); + return; + } + struct wl_shm *shm = registry_get_shm(); + struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); + win->buffer = wl_shm_pool_create_buffer(pool, 0, draw_info->width, draw_info->height, stride, WL_SHM_FORMAT_ARGB8888); + wl_shm_pool_destroy(pool); + close(fd); + + draw_info->data = map; +} + +static void resize_new(struct wayland_window *win, int32_t w, int32_t h) +{ + if (!w || !h) + return; + struct window_draw_info *draw_info = &win->draw_info; + + if (draw_info->width != w || draw_info->height != h) + { + if (draw_info->data) + { + munmap(draw_info->data, draw_info->width * draw_info->height * 4); + draw_info->data = NULL; + } + draw_info->width = w; + draw_info->height = h; + resize_canvas(win); + } +} + +static void draw(struct wayland_window *win) +{ + struct window_draw_info *draw_info = &win->draw_info; + + size_t stride = draw_info->width * 4; + size_t size = stride * draw_info->height; + + if (!draw_info->data || !win->buffer) + return; + + // Залочиться, чтобы операции обновления позиции фигуры происходили атомарно + pthread_mutex_lock(&draw_info->figure_mutex); + struct figure_animation_info figure = draw_info->figure; + + uint32_t color; + switch (figure.type) + { + case FIGURE_CIRCLE: + color = 0xFFFF0000; + break; + case FIGURE_TRIANGLE: + color = 0xFF00FF00; + break; + case FIGURE_SQUARE: + color = 0xFF0000FF; + break; + + default: + break; + } + + uint8_t *bytes = (uint8_t *)draw_info->data; + for (int32_t y = 0; y < (int32_t)draw_info->height; ++y) + { + uint32_t *row = (uint32_t *)(bytes + y * stride); + for (int32_t x = 0; x < (int32_t)draw_info->width; ++x) + row[x] = 0xAA5F5F5F; /* background black */ + } + + /* Draw figure into buffer. border thickness in pixels = 3.0f */ + figure_draw(&win->draw_info, 3.0f, 0xFF000000, color); + pthread_mutex_unlock(&draw_info->figure_mutex); + + + wl_surface_attach(win->wl_surface, win->buffer, 0, 0); + wl_surface_damage_buffer(win->wl_surface, 0, 0, draw_info->width, draw_info->height); + wl_surface_commit(win->wl_surface); +} + +static void xdg_surface_conf(void *data, struct xdg_surface *xdg_surface_local, uint32_t serial) +{ + xdg_surface_ack_configure(xdg_surface_local, serial); + struct wayland_window *win = data; + if (!win->draw_info.data) + resize_canvas(win); + + draw(win); +} + +static void xdg_toplevel_conf(void *data, struct xdg_toplevel *xdg_top, int32_t w, int32_t h, struct wl_array *states) +{ + (void)states; + struct wayland_window *win = data; + resize_new(win, w, h); +} + +static void xdg_toplevel_cls(void *data, struct xdg_toplevel *xdg_top) +{ + struct wayland_window *win = data; + (void)xdg_top; + win->need_close = 1; +} + +static void xdg_toplevel_bounds(void *data, struct xdg_toplevel *xdg_top, int32_t w, int32_t h) +{ + (void)data; + (void)xdg_top; + (void)w; + (void)h; +} + +static void xdg_toplevel_wm_caps(void *data, struct xdg_toplevel *xdg_top, struct wl_array *caps) +{ + (void)data; + (void)xdg_top; + (void)caps; +} + +static struct wl_callback_listener callback_listener; +static void setup_frame_callback(struct wayland_window *win, struct wl_event_queue *queue) +{ + struct wl_callback *cb = wl_surface_frame(win->wl_surface); + win->frame_callback = cb; + if (cb && queue) + wl_proxy_set_queue((struct wl_proxy *)cb, queue); + wl_callback_add_listener(cb, &callback_listener, win); +} + +static void frame_new(void *data, struct wl_callback *cb, uint32_t _) +{ + struct wayland_window *win = data; + if (win->frame_callback == cb) + win->frame_callback = NULL; + wl_callback_destroy(cb); + setup_frame_callback(win, win->queue); + draw(win); +} + +static struct wl_callback_listener callback_listener = { + .done = frame_new}; +static struct xdg_surface_listener surface_listener = { + .configure = xdg_surface_conf}; +static struct xdg_toplevel_listener top_listener = { + .configure = xdg_toplevel_conf, + .close = xdg_toplevel_cls, + .configure_bounds = xdg_toplevel_bounds, + .wm_capabilities = xdg_toplevel_wm_caps}; + +int window_init(struct wl_display *display, struct wl_event_queue *queue, struct wayland_window *win) +{ + static int id = 0; + (void)display; + struct wl_compositor *compositor = registry_get_compositor(); + struct wl_shm *shm = registry_get_shm(); + struct xdg_wm_base *wm = registry_get_xdg_wm_base(); + if (!compositor || !shm || !wm) + return -1; + + win->wl_surface = wl_compositor_create_surface(compositor); + win->queue = queue; + if (win->wl_surface && queue) + wl_proxy_set_queue((struct wl_proxy *)win->wl_surface, queue); + + setup_frame_callback(win, queue); + + win->xdg_surface = xdg_wm_base_get_xdg_surface(wm, win->wl_surface); + if (win->xdg_surface && queue) + wl_proxy_set_queue((struct wl_proxy *)win->xdg_surface, queue); + xdg_surface_add_listener(win->xdg_surface, &surface_listener, win); + + win->xdg_toplevel = xdg_surface_get_toplevel(win->xdg_surface); + if (win->xdg_toplevel && queue) + wl_proxy_set_queue((struct wl_proxy *)win->xdg_toplevel, queue); + + xdg_toplevel_add_listener(win->xdg_toplevel, &top_listener, win); + + wl_surface_commit(win->wl_surface); + + struct window_draw_info *draw_info = &win->draw_info; + + /* Инициализация состояния */ + win->id = id++; + draw_info->width = 400; + draw_info->height = 250; + draw_info->data = NULL; + + /* Default animation state */ + draw_info->figure.type = FIGURE_CIRCLE; + draw_info->figure.position.x = 0.5f; + draw_info->figure.position.y = 0.5f; + draw_info->figure.velocity.x = 0.5f; + draw_info->figure.velocity.y = 0.5f; + draw_info->figure.angle = 0.0f; + draw_info->figure.angular_velocity = 0.01; /* radians per frame */ + draw_info->figure.speed = 10; /* speed multiplier */ + draw_info->figure.radius = 25.0f; /* radius in pixels */ + + win->buffer = NULL; + win->frame_callback = NULL; + win->need_close = 0; + /* Мьютекс нужен для атомарного обращения к структуре фигуры */ + if (pthread_mutex_init(&win->draw_info.figure_mutex, NULL) != 0) + { + /* if mutex init failed — cleanup and return error */ + window_destroy(win); + return -1; + } + + xdg_toplevel_set_app_id(win->xdg_toplevel, "my-wayland-window"); + xdg_toplevel_set_title(win->xdg_toplevel, "Custom Title"); + + return 0; +} + +int window_should_close(struct wayland_window *win) +{ + return win->need_close; +} + +void window_destroy(struct wayland_window *win) +{ + destroy_frame_callback(win); + if (win->buffer) + wl_buffer_destroy(win->buffer); + if (win->xdg_toplevel) + xdg_toplevel_destroy(win->xdg_toplevel); + if (win->xdg_surface) + xdg_surface_destroy(win->xdg_surface); + if (win->wl_surface) + wl_surface_destroy(win->wl_surface); + if (win->draw_info.data) + munmap(win->draw_info.data, win->draw_info.width * win->draw_info.height * 4); + /* destroy mutex if initialized */ + pthread_mutex_destroy(&win->draw_info.figure_mutex); +} +``` + +### A.11. include/figure.h + +```c +#ifndef FIGURE_H +#define FIGURE_H + +#include "geomerty.h" + +enum figure_type +{ + FIGURE_CIRCLE = 0, + FIGURE_TRIANGLE = 1, + FIGURE_SQUARE = 2 +}; + +struct figure_animation_info { + enum figure_type type; + struct vec2 position; + struct vec2 velocity; + + float angle; + float angular_velocity; + + + float speed; + float radius; +}; + +#endif +``` + +### A.12. include/figure-animate.h + +```c +#ifndef FIGURE_ANIMATE_H +#define FIGURE_ANIMATE_H + +#include "window.h" + +/* Провести один шаг анимации на окне */ +void figure_animation_step(struct window_draw_info* draw_info); + +#endif +``` + +### A.13. include/figure-draw.h + +```c +#ifndef FIGURE_DRAW_H +#define FIGURE_DRAW_H + +#include "window.h" + +/* Нарисовать фигуру на холсте */ +void figure_draw(struct window_draw_info* draw_info, float border_thikness, uint32_t border_color, uint32_t fill_color); + +#endif +``` + +### A.14. include/geomerty.h + +```c +#ifndef GEOMETRY_H +#define GEOMETRY_H + +struct vec2 { + float x; + float y; +}; + +#endif +``` + +### A.15. include/input.h + +```c +#ifndef WAYLAND_INPUT_H +#define WAYLAND_INPUT_H + +#include + +/* Вызывается при привязке wl_seat регистром */ +void input_register_seat(struct wl_seat *seat); +/* Очистка input (разрушить keyboard и xkb state) */ +void input_cleanup(void); + +enum keyboard_key_state { + /** + * key is not pressed + */ + KEYBOARD_KEY_STATE_RELEASED = 0, + /** + * key is pressed + */ + KEYBOARD_KEY_STATE_PRESSED = 1, + /** + * key was repeated + */ + KEYBOARD_KEY_STATE_REPEATED = 2, +}; + +#endif +``` + +### A.16. include/input-handle.h + +```c +#ifndef INPUT_HANDLE_H +#define INPUT_HANDLE_H + +#include +#include "window.h" +#include "input.h" + +void keyboard_key_handle(xkb_keycode_t kc, xkb_keysym_t ks, enum keyboard_key_state state, struct wayland_window* window); + +#endif +``` + +### A.17. include/registry.h + +```c +#ifndef WAYLAND_REGISTRY_H +#define WAYLAND_REGISTRY_H + +#include + +int registry_global_bind(struct wl_display *display); +void registry_global_unbind(void); + +/* Доступ к привязанным глобальным объектам */ +struct wl_compositor *registry_get_compositor(void); +struct wl_shm *registry_get_shm(void); +struct xdg_wm_base *registry_get_xdg_wm_base(void); + +#endif +``` + +### A.18. include/wayland-runtime.h + +```c +#ifndef WAYLAND_RUNTIME_H +#define WAYLAND_RUNTIME_H + +#include + +/* Инициализация общего соединения Wayland и подготовка глобальных данных */ +int32_t init_wayland(void); + +/* Создать поток для окна; вернуть индекс слота или отрицательное при ошибке */ +int32_t run_window(void); + +/* Блокировать до завершения всех оконных потоков */ +void wait_for_windows(void); + +/* Остановить оконные потоки, очистить input и закрыть соединение Wayland */ +void destroy_wayland(void); + +/* Поиск окна по wl_surface */ +struct wayland_window* get_window_by_surface(struct wl_surface* surf); + +#endif +``` + +### A.19. include/window.h + +```c +#ifndef WAYLAND_WINDOW_H +#define WAYLAND_WINDOW_H + +#include +#include +#include "figure.h" + +struct window_draw_info { + uint8_t *data; + int32_t width; + int32_t height; + struct figure_animation_info figure; + pthread_mutex_t figure_mutex; +}; + +/* Данные одного Wayland-окна (одна поверхность) */ +struct wayland_window { + int id; + struct wl_surface *wl_surface; + struct wl_buffer *buffer; + struct wl_callback *frame_callback; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct wl_event_queue *queue; /* очередь событий для окна */ + struct window_draw_info draw_info; + int need_close; +}; + + +/* Инициализация окна; структура предоставляется вызывающим */ +int window_init(struct wl_display *display, struct wl_event_queue *queue, struct wayland_window *win); + +/* Нечётное значение — окно запросило закрытие */ +int window_should_close(struct wayland_window *win); + +/* Уничтожить окно и связанные ресурсы */ +void window_destroy(struct wayland_window *win); + +#endif +```