Compare commits

...

28 Commits

Author SHA1 Message Date
92c69f3e57 Отчёт 2025-11-18 14:12:03 +03:00
62be1dc854 Скорость поворота отдельно от velocity 2025-11-18 11:00:54 +03:00
d21460b8e4 Более красивые настройки 2025-11-18 01:24:01 +03:00
51a0a6a2eb Доки 2025-11-18 01:22:01 +03:00
d04c04f028 Подсказка при запуске 2025-11-18 00:21:46 +03:00
33285bbf1c Прекрасное вращение 2025-11-18 00:14:03 +03:00
83a6640b2e Обалденное вращение 2025-11-17 23:51:31 +03:00
7de923d0b6 Почти красота 2025-11-17 23:02:16 +03:00
f48bc757ea Рабочий прототип 2025-11-17 22:46:36 +03:00
494686a203 Расстановка точек на окружности 2025-11-17 20:43:01 +03:00
58eb3695f1 Размер не зависит от ширины 2025-11-17 16:13:47 +03:00
2c383b20e9 первое движение 2025-11-17 15:58:18 +03:00
92f4632803 Первый движок анимации 2025-11-17 15:43:33 +03:00
5ac43bb236 Скелет функции анимации 2025-11-17 15:04:05 +03:00
743b8336a3 Структура для анимации 2025-11-17 14:42:42 +03:00
8dd2aa19ad отдельный поток для физики и изменение цвета 2025-11-17 14:38:03 +03:00
de18bd8252 Подготовлены файлы фигуры 2025-11-17 14:05:42 +03:00
c21df94757 Запуск окон wayland на Return 2025-11-17 13:45:23 +03:00
b8ebf31762 Файл для обработки ввода 2025-11-17 13:41:46 +03:00
bc5bf92fee Игнор билд директории 2025-11-17 11:47:01 +03:00
908d539abe Мелкие правки 2025-11-17 01:38:16 +03:00
e0ac79bae1 Превосходное состояние проекта 2025-11-17 01:31:00 +03:00
0d3efeda49 Логи закрытия 2025-11-16 20:57:46 +03:00
6880efafab Поддержка многопоточности 2025-11-16 20:50:02 +03:00
eeb632ed0e Реструктуризация проекта 2025-11-16 17:52:09 +03:00
1f700295ff Проект разнесён по файлам 2025-11-16 17:02:39 +03:00
b11a56ad06 Полная работа с клавитурой 2025-11-16 16:43:21 +03:00
9cdc529918 Переделано окно согласно видеоуроку
https://www.youtube.com/watch?v=iIVIu7YRdY0
2025-11-15 21:10:42 +03:00
38 changed files with 5304 additions and 122 deletions

View File

@@ -14,5 +14,18 @@
"debug.allowBreakpointsEverywhere": true,
"debug.inlineValues": "on",
"editor.codeLens": false,
"files.associations": {
"flake.lock": "json",
"ios": "c",
"cstdint": "c",
"array": "c",
"string": "c",
"string_view": "c",
"ranges": "c",
"span": "c",
"vector": "c",
"regex": "c",
"__node_handle": "c"
},
}
}

2
wayland/.ignore Normal file
View File

@@ -0,0 +1,2 @@
**/build/
**/out/

View File

@@ -4,7 +4,7 @@ project(wayland)
enable_language(ASM_NASM)
set(CMAKE_ASM_NASM_FLAGS "-f elf64")
set(CMAKE_ASM_NASM_FLAGS "-f elf64 -I${CMAKE_CURRENT_BINARY_DIR}")
set(CMAKE_ASM_NASM_FLAGS_DEBUG "-gdwarf")
find_package(PkgConfig REQUIRED)
@@ -24,27 +24,68 @@ set(PROTOCOL_XML ${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml)
# Генерируем заголовочный файл протокола
add_custom_command(
OUTPUT xdg-shell-client-protocol.h
COMMAND ${WAYLAND_SCANNER} client-header ${PROTOCOL_XML} xdg-shell-client-protocol.h
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 xdg-shell-client-protocol.c
COMMAND ${WAYLAND_SCANNER} private-code ${PROTOCOL_XML} xdg-shell-client-protocol.c
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 xdg-shell-client-protocol.h xdg-shell-client-protocol.c)
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 и сгенерированного кода
add_executable(wayland asm.asm c.c xdg-shell-client-protocol.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"
)
# Зависимость от генерации протокола
add_dependencies(wayland generate-xdg-shell)
# 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)
target_link_libraries(wayland ${WAYLAND_CLIENT_LIBRARIES})
target_include_directories(wayland PRIVATE ${WAYLAND_CLIENT_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
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)

View File

@@ -1,11 +0,0 @@
global main
extern run_wayland
section .text
main:
enter 0, 0
call run_wayland
leave
mov rax, 0
ret

View File

@@ -1,100 +0,0 @@
#include <wayland-client.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include "xdg-shell-client-protocol.h"
static struct wl_display *display;
static struct wl_registry *registry;
static struct wl_compositor *compositor;
static struct wl_shm *shm;
static struct xdg_wm_base *xdg_wm_base;
static struct wl_surface *surface;
static struct xdg_surface *xdg_surface;
static struct xdg_toplevel *xdg_toplevel;
static volatile int running = 1;
static int width = 800, height = 600;
static void registry_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
if (strcmp(interface, "wl_compositor") == 0) {
compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1);
} else if (strcmp(interface, "wl_shm") == 0) {
shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
} else if (strcmp(interface, "xdg_wm_base") == 0) {
xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
}
}
static void registry_global_remove(void *data, struct wl_registry *registry, uint32_t name) {}
static const struct wl_registry_listener registry_listener = {
.global = registry_global,
.global_remove = registry_global_remove,
};
static void create_buffer();
static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) {
xdg_surface_ack_configure(xdg_surface, serial);
create_buffer();
}
static const struct xdg_surface_listener xdg_surface_listener = {
.configure = xdg_surface_configure,
};
static void xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t w, int32_t h, struct wl_array *states) {
if (w > 0) width = w;
if (h > 0) height = h;
}
static void xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) {
running = 0;
}
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
.configure = xdg_toplevel_configure,
.close = xdg_toplevel_close,
};
static void create_buffer() {
char name[32];
sprintf(name, "/wayland-shm-%d", getpid());
int stride = width * 4;
int size = stride * height;
int fd = shm_open(name, O_RDWR | O_CREAT, 0600);
if (fd == -1) return;
ftruncate(fd, size);
void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
memset(data, 0xFF, size); // white
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0, width, height);
wl_surface_commit(surface);
wl_buffer_destroy(buffer);
wl_shm_pool_destroy(pool);
close(fd);
shm_unlink(name);
munmap(data, size);
}
void run_wayland() {
display = wl_display_connect(NULL);
if (!display) return;
registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, NULL);
wl_display_dispatch(display);
wl_display_roundtrip(display);
if (!compositor || !shm || !xdg_wm_base) return;
surface = wl_compositor_create_surface(compositor);
xdg_surface = xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);
wl_surface_commit(surface);
while (running && wl_display_dispatch(display) != -1) {}
wl_display_disconnect(display);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,51 @@
@startuml
title Цикл анимации фигуры (aux_thread)
start
:aux_thread запущен;
while (окно не закрыто?) is (да)
:Залочить figure_mutex;
:figure_animation_step(draw_info);
partition "figure_animation_step (ASM)" {
:Обработка коллизий;
note right
figure_handle_collision():
- Расчёт точек фигуры на окружности
- Проверка выхода за границы
- Инверсия velocity при коллизии
- Обновление angular_velocity
end note
:Обновить позицию;
note right
position += (velocity * speed) / height
end note
:Обновить угол поворота;
note right
angle += angular_velocity * speed
end note
:Применить трение к вращению;
note right
angular_velocity *= 0.95
end note
}
:Разлочить figure_mutex;
:usleep(33 ms);
note right: ~30 FPS обновление физики
endwhile (нет)
:Завершение потока;
stop
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -0,0 +1,76 @@
@startuml
title Цикл отрисовки окна (window_thread)
start
:window_thread запущен;
:Создать event queue;
:window_init();
:Запустить aux_thread для анимации;
while (окно не закрыто?) is (да)
:wl_display_dispatch_queue();
note right: Ожидание событий Wayland
if (событие произошло?) then (да)
if (xdg_surface_configure?) then (да)
:xdg_surface_ack_configure();
if (буфер не создан?) then (да)
:resize_canvas();
endif
:draw();
endif
if (xdg_toplevel_configure?) then (да)
if (размер изменился?) then (да)
:resize_new(w, h);
note right
- Освободить старый буфер
- Создать новый буфер
- Обновить размеры draw_info
end note
endif
endif
if (xdg_toplevel_close?) then (да)
:Установить need_close = 1;
endif
if (frame_callback?) then (да)
:Уничтожить старый callback;
:Создать новый callback;
partition "draw()" {
:Залочить figure_mutex;
:Очистить фон (черный);
:figure_draw();
note right
- Получить текущую фигуру
- Нарисовать границу и заливку
- Цвет зависит от типа фигуры
end note
:Разлочить figure_mutex;
:wl_surface_attach(buffer);
:wl_surface_damage_buffer();
:wl_surface_commit();
}
endif
endif
endwhile (нет)
:window_destroy();
:Уничтожить event queue;
:signal_thread_exit();
stop
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -0,0 +1,127 @@
@startuml
title Структура классов проекта Wayland
class wayland_runtime {
+ init_wayland() : int32_t
+ run_window() : int32_t
+ wait_for_windows() : void
+ destroy_wayland() : void
+ get_window_by_surface(surf) : wayland_window*
--
- g_display : wl_display*
- g_slots[MAX_WINDOW_THREADS] : window_thread_slot
- g_thread_lock : pthread_mutex_t
- g_active_threads : atomic_int
- g_shutdown : atomic_bool
}
class window_thread_slot {
+ thread : pthread_t
+ aux_thread : pthread_t
+ active : int
+ queue : wl_event_queue*
+ window : wayland_window
}
class wayland_window {
+ id : int
+ wl_surface : wl_surface*
+ xdg_surface : xdg_surface*
+ xdg_toplevel : xdg_toplevel*
+ buffer : wl_buffer*
+ frame_callback : wl_callback*
+ queue : wl_event_queue*
+ need_close : int
+ draw_info : window_draw_info
--
+ window_init(display, queue) : int
+ window_destroy() : void
+ window_should_close() : int
}
class window_draw_info {
+ width : uint32_t
+ height : uint32_t
+ data : void*
+ figure : figure_animation_info
+ figure_mutex : pthread_mutex_t
}
class figure_animation_info {
+ type : figure_type
+ position : vec2
+ velocity : vec2
+ angle : float
+ angular_velocity : float
+ speed : float
+ radius : float
}
enum figure_type {
FIGURE_CIRCLE = 0
FIGURE_TRIANGLE = 1
FIGURE_SQUARE = 2
}
class registry {
+ registry_global_bind(display) : int
+ registry_global_unbind() : void
+ registry_get_compositor() : wl_compositor*
+ registry_get_shm() : wl_shm*
+ registry_get_xdg_wm_base() : xdg_wm_base*
--
- global_compositor : wl_compositor*
- global_shm : wl_shm*
- global_wm_base : xdg_wm_base*
- global_registry : wl_registry*
- global_thread : pthread_t
- global_thread_running : atomic_int
}
class input {
+ input_register_seat(seat) : void
+ input_cleanup() : void
--
- seat : wl_seat*
- keyboard : wl_keyboard*
- xkb_ctx : xkb_context*
- xkb_keymap : xkb_keymap*
- xkb_state : xkb_state*
- focused_window : wayland_window*
}
class input_handle {
+ keyboard_key_handle(kc, ks, state, window) : void
}
class figure_animate <<ASM>> {
+ figure_animation_step(draw_info) : void
+ place_points_on_circle(...) : void
--
- figure_handle_collision(draw_info) : void
- check_collision_mask(...) : uint64_t
- sincos_f32_rbp(angle) : (sin, cos)
}
class figure_draw {
+ figure_draw(draw_info, thickness, border_color, fill_color) : void
}
wayland_runtime "1" *-- "0..128" window_thread_slot
window_thread_slot "1" *-- "1" wayland_window
wayland_window "1" *-- "1" window_draw_info
window_draw_info "1" *-- "1" figure_animation_info
figure_animation_info --> figure_type
wayland_runtime ..> registry : uses
wayland_runtime ..> input : uses
wayland_window ..> registry : uses
wayland_window ..> figure_draw : uses
input ..> input_handle : uses
input --> wayland_window : focuses
window_thread_slot ..> figure_animate : uses (aux_thread)
figure_draw ..> figure_animate : uses
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -0,0 +1,74 @@
@startuml
title Компоненты проекта Wayland
package "Wayland Application" {
[wayland-runtime] as Runtime
[registry] as Registry
[input] as Input
[input-handle] as InputHandle
[window] as Window
package "Rendering & Animation" {
[figure-draw] as FigureDraw
[figure-animate (ASM)] as FigureAnimate
}
Runtime --> Registry : использует глобальные объекты
Runtime --> Input : инициализирует
Runtime --> Window : создаёт и управляет
Input --> InputHandle : делегирует обработку
InputHandle --> Runtime : вызывает run_window()
InputHandle --> Window : изменяет параметры фигуры
Window --> Registry : получает compositor, shm, wm_base
Window --> FigureDraw : отрисовка фигуры
Window --> FigureAnimate : aux_thread для анимации
FigureDraw --> FigureAnimate : использует place_points_on_circle
}
package "System Libraries" {
[wayland-client] as WaylandLib
[xdg-shell-protocol] as XDGShell
[xkbcommon] as XKB
}
Registry --> WaylandLib
Registry --> XDGShell
Window --> WaylandLib
Window --> XDGShell
Input --> WaylandLib
Input --> XKB
cloud "Wayland Compositor" as Compositor
Runtime --> Compositor : wl_display_connect()
Registry --> Compositor : получение глобальных объектов
Window --> Compositor : создание окон и буферов
Input --> Compositor : получение событий клавиатуры
note right of Runtime
Центральный компонент:
- Управление жизненным циклом
- Многопоточность (окна + анимация)
- Синхронизация через mutex
end note
note right of FigureAnimate
Реализация на NASM:
- Физика движения
- Обработка коллизий
- Математика (sin/cos)
- SSE инструкции
end note
note right of Window
Каждое окно в отдельном потоке:
- window_thread: отрисовка
- aux_thread: анимация
- Event queue для изоляции событий
end note
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -0,0 +1,78 @@
@startuml
title Последовательность инициализации Wayland приложения
actor Main
participant "wayland-runtime" as Runtime
participant "registry" as Registry
participant "input" as Input
participant "window" as Window
Main -> Runtime: init_wayland()
activate Runtime
Runtime -> Runtime: wl_display_connect()
note right: Подключение к Wayland серверу
Runtime -> Registry: registry_global_bind(display)
activate Registry
Registry -> Registry: wl_display_get_registry()
Registry -> Registry: wl_registry_add_listener()
Registry -> Registry: wl_display_roundtrip()
note right: Получение глобальных объектов:\n- wl_compositor\n- wl_shm\n- xdg_wm_base\n- wl_seat
Registry -> Input: input_register_seat(seat)
activate Input
Input -> Input: wl_seat_add_listener()
Input -> Input: wl_seat_get_keyboard()
Input -> Input: xkb_context_new()
note right: Инициализация обработки клавиатуры
deactivate Input
Registry -> Registry: pthread_create(global_thread)
note right: Запуск потока обработки\nглобальных событий
deactivate Registry
Runtime -> Runtime: Инициализация слотов окон
Runtime -> Runtime: atomic_store(g_shutdown, 0)
deactivate Runtime
Main -> Runtime: run_window()
activate Runtime
Runtime -> Runtime: Найти свободный слот
Runtime -> Runtime: pthread_create(window_thread)
note right: Создание потока окна
Runtime --> Main: slot_index
== Поток окна ==
Runtime -> Runtime: wl_display_create_queue()
Runtime -> Window: window_init(display, queue)
activate Window
Window -> Registry: registry_get_compositor()
Window -> Registry: registry_get_shm()
Window -> Registry: registry_get_xdg_wm_base()
Window -> Window: wl_compositor_create_surface()
Window -> Window: xdg_wm_base_get_xdg_surface()
Window -> Window: xdg_surface_get_toplevel()
Window -> Window: Инициализация listeners
Window -> Window: Инициализация figure_animation_info
note right: Параметры анимации:\n- position, velocity\n- angle, angular_velocity\n- speed, radius
deactivate Window
Runtime -> Runtime: pthread_create(aux_thread)
note right: Вспомогательный поток\nдля анимации
Runtime -> Runtime: wl_display_dispatch_queue()
note right: Цикл обработки событий окна
deactivate Runtime
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,61 @@
@startuml
title Обработка клавиатурного ввода
actor User
participant "Wayland Server" as Server
participant "input" as Input
participant "input-handle" as Handler
participant "wayland-runtime" as Runtime
participant "window" as Window
User -> Server: Нажатие клавиши
Server -> Input: keyboard_key(key, state)
activate Input
Input -> Input: xkb_state_key_get_one_sym()
note right: Преобразование keycode в keysym
Input -> Handler: keyboard_key_handle(kc, ks, state, window)
activate Handler
alt Клавиша Enter
Handler -> Runtime: run_window()
note right: Создание нового окна
else Клавиша '1'
Handler -> Window: figure.type = CIRCLE
else Клавиша '2'
Handler -> Window: figure.type = TRIANGLE
else Клавиша '3'
Handler -> Window: figure.type = SQUARE
else Клавиша '-'
Handler -> Handler: pthread_mutex_lock()
Handler -> Window: figure.speed -= 0.5
Handler -> Handler: Ограничить [1, 30]
Handler -> Handler: pthread_mutex_unlock()
else Клавиша '+' или '='
Handler -> Handler: pthread_mutex_lock()
Handler -> Window: figure.speed += 0.5
Handler -> Handler: Ограничить [1, 30]
Handler -> Handler: pthread_mutex_unlock()
else Клавиша Up
Handler -> Handler: pthread_mutex_lock()
Handler -> Window: figure.radius += 2.0
Handler -> Handler: Ограничить [1, max]
Handler -> Handler: pthread_mutex_unlock()
else Клавиша Down
Handler -> Handler: pthread_mutex_lock()
Handler -> Window: figure.radius -= 2.0
Handler -> Handler: Ограничить [1, max]
Handler -> Handler: pthread_mutex_unlock()
end
deactivate Handler
deactivate Input
note over Window
Изменения параметров фигуры
влияют на следующий frame
анимации и отрисовки
end note
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -0,0 +1,62 @@
@startuml
title Диаграмма состояний фигуры
[*] --> Circle : init (по умолчанию)
Circle : Красный цвет (0xFFFF0000)
Circle : radius точек = 16
Triangle : Зелёный цвет (0xFF00FF00)
Triangle : radius точек = 3
Square : Синий цвет (0xFF0000FF)
Square : radius точек = 4
Circle --> Triangle : Клавиша '2'
Circle --> Square : Клавиша '3'
Triangle --> Circle : Клавиша '1'
Triangle --> Square : Клавиша '3'
Square --> Circle : Клавиша '1'
Square --> Triangle : Клавиша '2'
Circle : velocity движет позицию
Circle : angular_velocity вращает
Triangle : velocity движет позицию
Triangle : angular_velocity вращает
Square : velocity движет позицию
Square : angular_velocity вращает
note right of Circle
Общие свойства для всех состояний:
- position (x, y)
- velocity (vx, vy)
- angle (угол поворота)
- angular_velocity (скорость вращения)
- speed (множитель скорости: 1-30)
- radius (радиус в пикселях)
Управление:
- '+'/'-': изменение speed
- Up/Down: изменение radius
- Enter: создать новое окно
end note
state "Коллизия с границей" as Collision {
[*] --> CheckCollision
CheckCollision --> InvertVelocity : точка вышла за границу
InvertVelocity --> UpdateAngularVel : инверсия компоненты velocity
UpdateAngularVel --> CheckCollision : обновление angular_velocity
CheckCollision : проверка всех точек фигуры
InvertVelocity : vel = -vel (по оси коллизии)
UpdateAngularVel : зависит от направления движения
}
Circle --> Collision : каждый frame
Triangle --> Collision : каждый frame
Square --> Collision : каждый frame
Collision --> Circle
Collision --> Triangle
Collision --> Square
@enduml

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
/* 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;
}

View File

@@ -0,0 +1,9 @@
#ifndef FIGURE_ANIMATE_H
#define FIGURE_ANIMATE_H
#include "window.h"
/* Провести один шаг анимации на окне */
void figure_animation_step(struct window_draw_info* draw_info);
#endif

View File

@@ -0,0 +1,9 @@
#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

26
wayland/include/figure.h Normal file
View File

@@ -0,0 +1,26 @@
#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

View File

@@ -0,0 +1,9 @@
#ifndef GEOMETRY_H
#define GEOMETRY_H
struct vec2 {
float x;
float y;
};
#endif

View File

@@ -0,0 +1,10 @@
#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

26
wayland/include/input.h Normal file
View File

@@ -0,0 +1,26 @@
#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

View File

@@ -0,0 +1,14 @@
#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

View File

@@ -0,0 +1,21 @@
#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

39
wayland/include/window.h Normal file
View File

@@ -0,0 +1,39 @@
#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

23
wayland/src/asm.asm Normal file
View File

@@ -0,0 +1,23 @@
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

View File

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

239
wayland/src/figure-draw.c Normal file
View File

@@ -0,0 +1,239 @@
#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;
}
}

View File

@@ -0,0 +1,81 @@
#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;
}
}
}
}

200
wayland/src/input.c Normal file
View File

@@ -0,0 +1,200 @@
#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;
}

221
wayland/src/registry.c Normal file
View File

@@ -0,0 +1,221 @@
#include "registry.h"
#include "input.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdatomic.h>
#include <poll.h>
#include <errno.h>
#include <wayland-client.h>
#include "xdg-shell-client-protocol.h"
static void wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial)
{
(void)data;
xdg_wm_base_pong(wm_base, serial);
}
static struct xdg_wm_base_listener wm_base_listener = {
.ping = wm_base_ping
};
/* Глобальные объекты, привязанные к единому registry-потоку (общие для процесса) */
static struct wl_compositor *global_compositor;
static struct wl_shm *global_shm;
static struct xdg_wm_base *global_wm_base;
/* Глобальный реестр живёт на весь срок программы (удаляется при сбросе) */
static struct wl_registry *global_registry = NULL;
/* Поток обработки глобальных событий: использует дефолтную очередь */
static pthread_t global_thread;
static _Atomic int global_thread_running = 0;
static struct wl_display *global_display = NULL;
static uint32_t clamp_version(uint32_t announced, uint32_t supported)
{
return (announced < supported) ? announced : supported;
}
static void registry_global(void *data, struct wl_registry *reg, uint32_t name, const char *intf, uint32_t version)
{
printf("%u\t%s\tv:%u\n", name, intf, version);
if (!strcmp(intf, wl_compositor_interface.name))
{
uint32_t ver = clamp_version(version, wl_compositor_interface.version);
/* Привязать compositor на дефолтной очереди сразу */
if (!global_compositor)
global_compositor = wl_registry_bind(reg, name, &wl_compositor_interface, ver);
}
else if (!strcmp(intf, wl_shm_interface.name))
{
uint32_t ver = clamp_version(version, wl_shm_interface.version);
if (!global_shm)
global_shm = wl_registry_bind(reg, name, &wl_shm_interface, ver);
}
else if (!strcmp(intf, xdg_wm_base_interface.name))
{
uint32_t ver = clamp_version(version, xdg_wm_base_interface.version);
if (!global_wm_base)
global_wm_base = wl_registry_bind(reg, name, &xdg_wm_base_interface, ver);
if (global_wm_base)
xdg_wm_base_add_listener(global_wm_base, &wm_base_listener, NULL);
}
else if (!strcmp(intf, wl_seat_interface.name))
{
uint32_t ver = clamp_version(version, wl_seat_interface.version);
/* Привязать seat на дефолтной очереди */
struct wl_seat *seat = wl_registry_bind(reg, name, &wl_seat_interface, ver);
if (seat)
input_register_seat(seat);
}
}
static void registry_global_remove(void *data, struct wl_registry *reg, uint32_t name)
{
(void)data;
(void)reg;
(void)name;
}
static const struct wl_registry_listener registry_listener = {
.global = registry_global,
.global_remove = registry_global_remove
};
static void destroy_global_objects(void)
{
if (global_compositor)
{
wl_compositor_destroy(global_compositor);
global_compositor = NULL;
}
if (global_shm)
{
wl_shm_destroy(global_shm);
global_shm = NULL;
}
if (global_wm_base)
{
xdg_wm_base_destroy(global_wm_base);
global_wm_base = NULL;
}
if (global_registry)
{
wl_registry_destroy(global_registry);
global_registry = NULL;
}
}
/* Привязка глобального реестра — привязать compositor/shm/xdg_wm_base и
* запустить поток обработки глобальной очереди. Возвращает 0 при успехе,
* -1 при ошибке.
*/
static void *registry_global_thread(void *arg)
{
struct wl_display *display = arg;
int fd = wl_display_get_fd(display);
while (global_thread_running)
{
struct pollfd pfd;
pfd.fd = fd;
pfd.events = POLLIN;
pfd.revents = 0;
int ret = poll(&pfd, 1, 100); /* таймаут 100 ms для проверки флага запуска */
if (ret == -1)
{
if (errno == EINTR)
continue;
break;
}
if (ret == 0)
continue;
if (pfd.revents & POLLIN)
{
int r = wl_display_dispatch(display);
if (r < 0)
break;
}
}
return NULL;
}
int registry_global_bind(struct wl_display *display)
{
if (!display)
return -1;
if (global_thread_running)
return 0; /* уже привязан */
/* Используем дефолтную очередь для глобальных объектов.
* Добавим listener и сделаем roundtrip, чтобы вызывающий код
* мог не делать этого заранее. Реестр привяжет глобалы сразу
* при получении событий. */
struct wl_registry *registry = wl_display_get_registry(display);
if (!registry)
{
return -1;
}
wl_registry_add_listener(registry, &registry_listener, NULL);
if (wl_display_roundtrip(display) < 0)
{
/* произошла ошибка — уничтожить registry и вернуть ошибку */
wl_registry_destroy(registry);
return -1;
}
global_registry = registry;
if (!global_compositor || !global_shm || !global_wm_base)
{
destroy_global_objects();
return -1;
}
/* Запустить поток обработки событий дефолтной очереди */
global_thread_running = 1;
global_display = display;
int thr_res = pthread_create(&global_thread, NULL, registry_global_thread, display);
if (thr_res != 0)
{
global_thread_running = 0;
destroy_global_objects();
return -1;
}
return 0;
}
void registry_global_unbind(void)
{
if (!global_thread_running)
return;
/* Корректно остановить поток */
global_thread_running = 0;
pthread_join(global_thread, NULL);
global_display = NULL;
destroy_global_objects();
return;
}
struct wl_compositor *registry_get_compositor(void)
{
return global_compositor;
}
struct wl_shm *registry_get_shm(void)
{
return global_shm;
}
struct xdg_wm_base *registry_get_xdg_wm_base(void)
{
return global_wm_base;
}

View File

@@ -0,0 +1,272 @@
#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;
}

299
wayland/src/window.c Normal file
View File

@@ -0,0 +1,299 @@
#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);
}