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

2565 lines
85 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Лабораторная работа: Анимация фигуры в 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. Общая архитектура
На верхнем уровне приложение состоит из следующих компонент:
- подсистема 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. Диаграмма классов
![Диаграмма классов](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 <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
```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 <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
```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
```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
```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
```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
```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
```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 <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
```c
#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
```c
#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
```c
#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
```c
#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
```