2565 lines
85 KiB
Markdown
2565 lines
85 KiB
Markdown
# Лабораторная работа: Анимация фигуры в Wayland
|
||
|
||
## 1. Цель работы
|
||
|
||
Исследование программного интерфейса Wayland, освоение работы с протоколом xdg-shell, буферами shm и обработкой ввода с клавиатуры. Реализация оконного приложения, в котором анимированная геометрическая фигура (круг, треугольник, квадрат) движется и вращается внутри окна с обработкой столкновений с границами.
|
||
|
||
## 2. Постановка задачи
|
||
|
||
Разработать приложение на языке C/ASM, использующее Wayland как графическую подсистему, которое:
|
||
- инициализирует соединение с композитором Wayland и глобальные объекты (compositor, shm, xdg_wm_base, seat);
|
||
- создает одно или несколько окон, каждое в отдельном потоке с собственной очередью событий;
|
||
- отображает во всех окнах одну геометрическую фигуру, которая:
|
||
- непрерывно движется по области окна;
|
||
- отражается от границ окна, не выходя за пределы;
|
||
- может менять форму (круг / треугольник / квадрат);
|
||
- обладает радиусом и скоростью, управляемыми с клавиатуры;
|
||
- может вращаться, причём угловая скорость зависит от столкновений с границами;
|
||
- обрабатывает ввод с клавиатуры через xkbcommon;
|
||
- реализует часть логики анимации на языке ассемблера NASM.
|
||
|
||
## 3. Структура проекта
|
||
|
||
Проект `wayland` организован следующим образом:
|
||
|
||
- `CMakeLists.txt` — конфигурация сборки, генерация кода протокола xdg-shell и файла смещений `offsets.inc`.
|
||
- `generate-offsets.c` — вспомогательная утилита, генерирующая NASM‑файл `offsets.inc` со смещениями полей структур C.
|
||
- `src/` — исходный код C и ASM:
|
||
- `asm.asm` — точка входа `main`, запуск и завершение инфраструктуры Wayland.
|
||
- `figure-animate.asm` — ассемблерная логика шага анимации: обновление позиции, угла, обработка столкновений.
|
||
- `figure-draw.c` — растеризация фигур (круг, треугольник, квадрат) в буфер окна.
|
||
- `input.c` — работа с устройством ввода `wl_seat` и клавиатурой Wayland + xkbcommon.
|
||
- `input-handle.c` — интерпретация нажатий клавиш и изменение параметров анимации.
|
||
- `registry.c` — привязка глобальных объектов Wayland и поток обработки глобальной очереди.
|
||
- `wayland-runtime.c` — высокоуровневое управление жизненным циклом: соединение, окна, потоки.
|
||
- `window.c` — создание окон, буфера shm, цикл перерисовки.
|
||
- `include/` — заголовочные файлы с описанием структур и интерфейсов:
|
||
- `figure.h`, `figure-animate.h`, `figure-draw.h` — описание фигуры и интерфейсы анимации/отрисовки.
|
||
- `geomerty.h` — векторная структура `vec2`.
|
||
- `input.h`, `input-handle.h` — интерфейсы обработки ввода.
|
||
- `registry.h` — интерфейсы доступа к глобальным объектам Wayland.
|
||
- `wayland-runtime.h` — интерфейс запуска/завершения инфраструктуры.
|
||
- `window.h` — описание окна и холста для рисования.
|
||
- `docs/uml/*.png` — диаграммы архитектуры и поведения.
|
||
|
||
## 4. Сборка и запуск
|
||
|
||
Сборка проекта выполняется через CMake.
|
||
|
||
```bash
|
||
cd wayland
|
||
cmake -B build/debug-gcc -DCMAKE_BUILD_TYPE=Debug
|
||
cmake --build build/debug-gcc
|
||
./build/debug-gcc/wayland
|
||
```
|
||
|
||
При запуске создаётся главное окно. Управление выполняется с клавиатуры (см. раздел «Обработка ввода»).
|
||
|
||
## 5. Описание архитектуры
|
||
|
||
### 5.1. Общая архитектура
|
||
|
||
На верхнем уровне приложение состоит из следующих компонент:
|
||
|
||
- подсистема Wayland‑runtime (`wayland-runtime.c`):
|
||
- устанавливает соединение с композитором (`wl_display_connect`);
|
||
- через `registry.c` привязывает глобальные объекты (`wl_compositor`, `wl_shm`, `xdg_wm_base`, `wl_seat`);
|
||
- управляет пулом окон и их потоков;
|
||
- выводит подсказки по горячим клавишам.
|
||
- модуль `registry.c`: создаёт `wl_registry`, получает список глобалов и запускает отдельный поток обработки дефолтной очереди.
|
||
- модуль `window.c`: создаёт Wayland‑окно (surface + xdg_surface + xdg_toplevel), буфер shm, а также организует цикл перерисовки по frame‑callback.
|
||
- модуль `input.c` и `input-handle.c`: отвечают за привязку `wl_seat`, создание `wl_keyboard`, сопоставление scancode → keysym через xkbcommon и реакцию на нажатия.
|
||
- модуль `figure-animate.asm` и `figure-draw.c`: реализуют соответственно обновление состояния фигуры и её отрисовку.
|
||
|
||
Совокупно это даёт многопоточное Wayland‑приложение, где глобальные объекты обслуживаются в одном потоке, а окна — в отдельных потоках с собственными очередями событий.
|
||
|
||
### 5.2. Структуры данных
|
||
|
||
Основные структуры описаны в заголовке `figure.h` и `window.h`:
|
||
|
||
- `struct vec2` (`geomerty.h`) — 2D‑вектор с компонентами `x` и `y` (тип `float`).
|
||
- `enum figure_type` — тип фигуры: `FIGURE_CIRCLE`, `FIGURE_TRIANGLE`, `FIGURE_SQUARE`.
|
||
- `struct figure_animation_info` — состояние анимации фигуры:
|
||
- `type` — тип фигуры;
|
||
- `position` — позиция центра в нормализованных координатах;
|
||
- `velocity` — вектор скорости;
|
||
- `angle` — текущий угол поворота (в радианах);
|
||
- `angular_velocity` — угловая скорость;
|
||
- `speed` — скалярный множитель скорости движения;
|
||
- `radius` — радиус (в пикселях).
|
||
- `struct window_draw_info` (`window.h`) описывает холст окна:
|
||
- `data` — указатель на буфер пикселей ARGB;
|
||
- `width`, `height` — размер в пикселях;
|
||
- `figure` — состояние анимируемой фигуры;
|
||
- `figure_mutex` — мьютекс для синхронизации доступа к состоянию фигуры.
|
||
- `struct wayland_window` — обёртка над Wayland‑поверхностью и сопутствующими объектами.
|
||
|
||
### 5.3. Диаграммы UML
|
||
|
||
Для иллюстрации архитектуры используются диаграммы PlantUML, экспортированные в PNG и расположенные в `docs/uml`.
|
||
|
||
#### 5.3.1. Диаграмма классов
|
||
|
||

|
||
|
||
Диаграмма показывает связи между основными структурами: `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
|
||
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, ®istry_listener, NULL);
|
||
if (wl_display_roundtrip(display) < 0)
|
||
{
|
||
/* произошла ошибка — уничтожить registry и вернуть ошибку */
|
||
wl_registry_destroy(registry);
|
||
return -1;
|
||
}
|
||
global_registry = registry;
|
||
|
||
if (!global_compositor || !global_shm || !global_wm_base)
|
||
{
|
||
destroy_global_objects();
|
||
return -1;
|
||
}
|
||
|
||
/* Запустить поток обработки событий дефолтной очереди */
|
||
global_thread_running = 1;
|
||
global_display = display;
|
||
int thr_res = pthread_create(&global_thread, NULL, registry_global_thread, display);
|
||
if (thr_res != 0)
|
||
{
|
||
global_thread_running = 0;
|
||
destroy_global_objects();
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
void registry_global_unbind(void)
|
||
{
|
||
if (!global_thread_running)
|
||
return;
|
||
|
||
/* Корректно остановить поток */
|
||
global_thread_running = 0;
|
||
pthread_join(global_thread, NULL);
|
||
global_display = NULL;
|
||
|
||
destroy_global_objects();
|
||
|
||
return;
|
||
}
|
||
|
||
struct wl_compositor *registry_get_compositor(void)
|
||
{
|
||
return global_compositor;
|
||
}
|
||
|
||
struct wl_shm *registry_get_shm(void)
|
||
{
|
||
return global_shm;
|
||
}
|
||
|
||
struct xdg_wm_base *registry_get_xdg_wm_base(void)
|
||
{
|
||
return global_wm_base;
|
||
}
|
||
```
|
||
|
||
### A.9. src/wayland-runtime.c
|
||
|
||
```c
|
||
#include <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
|
||
```
|