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