Files
NASM/wayland/docs/wayland-report.md
2025-11-18 14:12:03 +03:00

85 KiB
Raw Blame History

Лабораторная работа: Анимация фигуры в 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.

cd wayland
cmake -B build/debug-gcc -DCMAKE_BUILD_TYPE=Debug
cmake --build build/debug-gcc
./build/debug-gcc/wayland

При запуске создаётся главное окно. Управление выполняется с клавиатуры (см. раздел «Обработка ввода»).

5. Описание архитектуры

5.1. Общая архитектура

На верхнем уровне приложение состоит из следующих компонент:

  • подсистема Waylandruntime (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, а также организует цикл перерисовки по framecallback.
  • модуль 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. Диаграмма классов

Диаграмма классов

Диаграмма показывает связи между основными структурами: wayland_window, window_draw_info, figure_animation_info, а также модулями ввода и runtime.

5.3.2. Компонентная диаграмма

Диаграмма компонентов

Отображает разбиение проекта на подсистемы: runtime, registry, window, input, figure (animate/draw) и их взаимодействие с внешними библиотеками Wayland и xkbcommon.

5.3.3. Диаграммы активности

Диаграмма рендеринга кадра:

Диаграмма активности рендеринга

Диаграмма активности анимации фигуры:

Диаграмма активности анимации

5.3.4. Диаграммы последовательности

Инициализация и запуск окна:

Диаграмма последовательности инициализации

Обработка нажатий клавиш:

Диаграмма последовательности клавиатуры

5.3.5. Диаграмма состояний фигуры

Диаграмма состояний фигуры

Диаграмма отражает возможные состояния фигуры (типы фигуры, направление движения, вращение) и переходы между ними под воздействием столкновений и событий ввода.

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_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

/* generate-offsets.c
 * Генерирует offsets.inc с смещениями полей структур
 */

#include <stdio.h>
#include <stddef.h>
#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

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

; Макрос для локальных переменных
%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

#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#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

#include <stdio.h>
#include <xkbcommon/xkbcommon.h>
#include <wayland-client.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

#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

#include "input-handle.h"
#include "wayland-runtime.h"
#include <math.h>

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

#include "registry.h"
#include "input.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdatomic.h>
#include <poll.h>
#include <errno.h>
#include <wayland-client.h>
#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, &registry_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

#include <errno.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <wayland-client.h>
#include <unistd.h>

#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 <stdio.h>
#include <math.h>
#include <stdint.h>
#include <stdlib.h>

// Прототип 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

#include "window.h"
#include "registry.h"
#include "figure-draw.h"
#include "xdg-shell-client-protocol.h"
#include <wayland-client.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdatomic.h>

#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

#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

#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

#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

#ifndef GEOMETRY_H
#define GEOMETRY_H

struct vec2 {
  float x;
  float y;
};

#endif

A.15. include/input.h

#ifndef WAYLAND_INPUT_H
#define WAYLAND_INPUT_H

#include <wayland-client.h>

/* Вызывается при привязке 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

#ifndef INPUT_HANDLE_H
#define INPUT_HANDLE_H

#include <xkbcommon/xkbcommon.h>
#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

#ifndef WAYLAND_REGISTRY_H
#define WAYLAND_REGISTRY_H

#include <wayland-client.h>

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

#ifndef WAYLAND_RUNTIME_H
#define WAYLAND_RUNTIME_H

#include <stdint.h>

/* Инициализация общего соединения 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

#ifndef WAYLAND_WINDOW_H
#define WAYLAND_WINDOW_H

#include <pthread.h>
#include <wayland-client.h>
#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