MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
WaylandWindow.cpp
Go to the documentation of this file.
1#include "WaylandWindow.hpp"
2
3#ifdef MAYAFLUX_PLATFORM_LINUX
4
5#include "KeyMapping.hpp"
7
8#include <linux/input-event-codes.h>
9#include <sys/mman.h>
10#include <sys/timerfd.h>
11#include <unistd.h>
12
13namespace MayaFlux::Core {
14
15// ============================================================================
16// Construction / destruction
17// ============================================================================
18
19WaylandWindow::WaylandWindow(const WindowCreateInfo& create_info,
20 const GlobalGraphicsConfig& graphics_config)
21 : m_display(wl_display_connect(nullptr))
22 , m_create_info(create_info)
23 , m_key_repeat_config(graphics_config.key_repeat_config)
24{
25
26 if (!m_display) {
27 MF_ERROR(Journal::Component::Core, Journal::Context::WindowingSubsystem,
28 "wl_display_connect failed");
29 return;
30 }
31
32 m_xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
33
34 m_registry = wl_display_get_registry(m_display);
35 wl_registry_add_listener(m_registry, &s_registry_listener, this);
36 wl_display_roundtrip(m_display);
37
38 if (!m_compositor || !m_xdg_wm_base) {
39 MF_ERROR(Journal::Component::Core, Journal::Context::WindowingSubsystem,
40 "Required Wayland globals not found (compositor={}, xdg_wm_base={})",
41 static_cast<void*>(m_compositor), static_cast<void*>(m_xdg_wm_base));
42 return;
43 }
44
45 m_surface = wl_compositor_create_surface(m_compositor);
46 m_xdg_surface = xdg_wm_base_get_xdg_surface(m_xdg_wm_base, m_surface);
47 xdg_surface_add_listener(m_xdg_surface, &s_xdg_surface_listener, this);
48
49 m_xdg_toplevel = xdg_surface_get_toplevel(m_xdg_surface);
50 xdg_toplevel_add_listener(m_xdg_toplevel, &s_toplevel_listener, this);
51 xdg_toplevel_set_title(m_xdg_toplevel, create_info.title.c_str());
52 xdg_toplevel_set_app_id(m_xdg_toplevel, "mayaflux");
53
54 if (m_decoration_manager) {
55 m_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
56 m_decoration_manager, m_xdg_toplevel);
57 zxdg_toplevel_decoration_v1_set_mode(m_decoration,
58 ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
59 }
60
61 wl_surface_commit(m_surface);
62 wl_display_roundtrip(m_display);
63
64 m_state.current_width = static_cast<int>(create_info.width);
65 m_state.current_height = static_cast<int>(create_info.height);
66}
67
68WaylandWindow::~WaylandWindow()
69{
70 destroy();
71 if (m_display) {
72 wl_display_disconnect(m_display);
73 m_display = nullptr;
74 }
75}
76
77// ============================================================================
78// Window interface
79// ============================================================================
80
81void WaylandWindow::show()
82{
83 if (m_surface)
84 wl_surface_commit(m_surface);
85}
86
87void WaylandWindow::hide()
88{
89 if (m_surface) {
90 wl_surface_attach(m_surface, nullptr, 0, 0);
91 wl_surface_commit(m_surface);
92 }
93}
94
95void WaylandWindow::destroy()
96{
97 if (m_repeat_fd >= 0) {
98 close(m_repeat_fd);
99 m_repeat_fd = -1;
100 }
101
102 if (m_keyboard) {
103 wl_keyboard_destroy(m_keyboard);
104 m_keyboard = nullptr;
105 }
106 if (m_pointer) {
107 wl_pointer_destroy(m_pointer);
108 m_pointer = nullptr;
109 }
110
111 if (m_xkb_state) {
112 xkb_state_unref(m_xkb_state);
113 m_xkb_state = nullptr;
114 }
115 if (m_xkb_keymap) {
116 xkb_keymap_unref(m_xkb_keymap);
117 m_xkb_keymap = nullptr;
118 }
119 if (m_xkb_context) {
120 xkb_context_unref(m_xkb_context);
121 m_xkb_context = nullptr;
122 }
123
124 if (m_decoration) {
125 zxdg_toplevel_decoration_v1_destroy(m_decoration);
126 m_decoration = nullptr;
127 }
128 if (m_decoration_manager) {
129 zxdg_decoration_manager_v1_destroy(m_decoration_manager);
130 m_decoration_manager = nullptr;
131 }
132 if (m_xdg_toplevel) {
133 xdg_toplevel_destroy(m_xdg_toplevel);
134 m_xdg_toplevel = nullptr;
135 }
136 if (m_xdg_surface) {
137 xdg_surface_destroy(m_xdg_surface);
138 m_xdg_surface = nullptr;
139 }
140 if (m_surface) {
141 wl_surface_destroy(m_surface);
142 m_surface = nullptr;
143 }
144 if (m_xdg_wm_base) {
145 xdg_wm_base_destroy(m_xdg_wm_base);
146 m_xdg_wm_base = nullptr;
147 }
148 if (m_seat) {
149 wl_seat_destroy(m_seat);
150 m_seat = nullptr;
151 }
152 if (m_compositor) {
153 wl_compositor_destroy(m_compositor);
154 m_compositor = nullptr;
155 }
156 if (m_registry) {
157 wl_registry_destroy(m_registry);
158 m_registry = nullptr;
159 }
160}
161
162bool WaylandWindow::should_close() const
163{
164 return m_should_close.load();
165}
166
167void WaylandWindow::poll()
168{
169 wl_display_dispatch_pending(m_display);
170 wl_display_flush(m_display);
171
172 if (m_repeat_fd >= 0 && !m_held_keys.empty()) {
173 uint64_t expirations = 0;
174 if (read(m_repeat_fd, &expirations, sizeof(expirations)) == sizeof(expirations)
175 && expirations > 0) {
176 for (const auto& [k, kd] : m_held_keys) {
177 WindowEvent rev;
178 rev.type = WindowEventType::KEY_REPEAT;
179 rev.data = kd;
180 emit(rev);
181 }
182 }
183 }
184
185 if (m_pending_configure.exchange(false)) {
186 xdg_surface_set_window_geometry(m_xdg_surface, 0, 0,
187 static_cast<int32_t>(m_state.current_width),
188 static_cast<int32_t>(m_state.current_height));
189 wl_surface_commit(m_surface);
190 wl_display_flush(m_display);
191 }
192}
193
194void WaylandWindow::set_event_callback(WindowEventCallback callback)
195{
196 m_event_callback = std::move(callback);
197}
198
199void* WaylandWindow::get_native_handle() const
200{
201 return m_surface;
202}
203
204void* WaylandWindow::get_native_display() const
205{
206 return m_display;
207}
208
209void WaylandWindow::set_title(const std::string& title)
210{
211 m_create_info.title = title;
212 if (m_xdg_toplevel)
213 xdg_toplevel_set_title(m_xdg_toplevel, title.c_str());
214}
215
216void WaylandWindow::set_size(uint32_t w, uint32_t h)
217{
218 m_create_info.width = w;
219 m_create_info.height = h;
220 if (m_xdg_toplevel && m_surface) {
221 xdg_toplevel_set_min_size(m_xdg_toplevel,
222 static_cast<int32_t>(w), static_cast<int32_t>(h));
223 xdg_toplevel_set_max_size(m_xdg_toplevel,
224 static_cast<int32_t>(w), static_cast<int32_t>(h));
225 wl_surface_commit(m_surface);
226 }
227}
228
229void WaylandWindow::set_position(uint32_t, uint32_t)
230{
231 // Wayland does not allow client-side window positioning.
232}
233
234void WaylandWindow::set_color(const std::array<float, 4>& color)
235{
236 m_create_info.clear_color = color;
237}
238
239// ============================================================================
240// Render tracking
241// ============================================================================
242
243void WaylandWindow::register_rendering_buffer(std::shared_ptr<Buffers::VKBuffer> buf)
244{
245 std::lock_guard lock(m_render_tracking_mutex);
246 m_rendering_buffers.push_back(buf);
247}
248
249void WaylandWindow::unregister_rendering_buffer(std::shared_ptr<Buffers::VKBuffer> buf)
250{
251 std::lock_guard lock(m_render_tracking_mutex);
252 std::erase_if(m_rendering_buffers, [&buf](const auto& wp) {
253 auto sp = wp.lock();
254 return !sp || sp == buf;
255 });
256}
257
258void WaylandWindow::track_frame_command(uint64_t id)
259{
260 m_frame_commands.push_back(id);
261}
262
263const std::vector<uint64_t>& WaylandWindow::get_frame_commands() const
264{
265 return m_frame_commands;
266}
267
268void WaylandWindow::clear_frame_commands()
269{
270 m_frame_commands.clear();
271}
272
273std::vector<std::shared_ptr<Buffers::VKBuffer>> WaylandWindow::get_rendering_buffers() const
274{
275 std::lock_guard lock(m_render_tracking_mutex);
276 std::vector<std::shared_ptr<Buffers::VKBuffer>> out;
277 out.reserve(m_rendering_buffers.size());
278 for (auto& wp : m_rendering_buffers) {
279 if (auto sp = wp.lock())
280 out.push_back(sp);
281 }
282 return out;
283}
284
285// ============================================================================
286// Internal helpers
287// ============================================================================
288
289void WaylandWindow::emit(const WindowEvent& ev)
290{
291 m_event_source.signal(ev);
292 if (m_event_callback)
293 m_event_callback(ev);
294}
295
296// ============================================================================
297// Registry
298// ============================================================================
299
300void WaylandWindow::on_registry_global(void* data, wl_registry* reg,
301 uint32_t name, const char* iface, uint32_t version)
302{
303 auto* self = static_cast<WaylandWindow*>(data);
304
305 if (std::string_view(iface) == wl_compositor_interface.name) {
306 self->m_compositor = static_cast<wl_compositor*>(
307 wl_registry_bind(reg, name, &wl_compositor_interface,
308 std::min(version, 4U)));
309 } else if (std::string_view(iface) == xdg_wm_base_interface.name) {
310 self->m_xdg_wm_base = static_cast<xdg_wm_base*>(
311 wl_registry_bind(reg, name, &xdg_wm_base_interface,
312 std::min(version, 6U)));
313 xdg_wm_base_add_listener(self->m_xdg_wm_base, &s_wm_base_listener, self);
314 } else if (std::string_view(iface) == wl_seat_interface.name) {
315 self->m_seat = static_cast<wl_seat*>(
316 wl_registry_bind(reg, name, &wl_seat_interface,
317 std::min(version, 7U)));
318 wl_seat_add_listener(self->m_seat, &s_seat_listener, self);
319 } else if (std::string_view(iface) == zxdg_decoration_manager_v1_interface.name) {
320 self->m_decoration_manager = static_cast<zxdg_decoration_manager_v1*>(
321 wl_registry_bind(reg, name, &zxdg_decoration_manager_v1_interface, 1U));
322 }
323}
324
325void WaylandWindow::on_registry_global_remove(void*, wl_registry*, uint32_t) { }
326
327// ============================================================================
328// xdg_wm_base
329// ============================================================================
330
331void WaylandWindow::on_wm_base_ping(void*, xdg_wm_base* base, uint32_t serial)
332{
333 xdg_wm_base_pong(base, serial);
334}
335
336// ============================================================================
337// xdg_surface
338// ============================================================================
339
340void WaylandWindow::on_xdg_surface_configure(void* data, xdg_surface* surf, uint32_t serial)
341{
342 auto* self = static_cast<WaylandWindow*>(data);
343 xdg_surface_ack_configure(surf, serial);
344 self->m_pending_configure.store(true);
345}
346
347// ============================================================================
348// xdg_toplevel
349// ============================================================================
350
351void WaylandWindow::on_toplevel_configure(void* data, xdg_toplevel*,
352 int32_t w, int32_t h, wl_array*)
353{
354 auto* self = static_cast<WaylandWindow*>(data);
355 if (w <= 0 || h <= 0)
356 return;
357
358 if (static_cast<uint32_t>(w) == self->m_state.current_width && static_cast<uint32_t>(h) == self->m_state.current_height)
359 return;
360
361 self->m_state.current_width = static_cast<uint32_t>(w);
362 self->m_state.current_height = static_cast<uint32_t>(h);
363 self->m_create_info.width = static_cast<uint32_t>(w);
364 self->m_create_info.height = static_cast<uint32_t>(h);
365 WindowEvent ev;
366 ev.type = WindowEventType::WINDOW_RESIZED;
367 ev.data = WindowEvent::ResizeData {
368 .width = static_cast<uint32_t>(w),
369 .height = static_cast<uint32_t>(h)
370 };
371 self->emit(ev);
372}
373
374void WaylandWindow::on_toplevel_close(void* data, xdg_toplevel*)
375{
376 auto* self = static_cast<WaylandWindow*>(data);
377 self->m_should_close.store(true);
378 WindowEvent ev;
379 ev.type = WindowEventType::WINDOW_CLOSED;
380 self->emit(ev);
381}
382
383void WaylandWindow::on_toplevel_configure_bounds(void*, xdg_toplevel*,
384 int32_t, int32_t) { }
385
386void WaylandWindow::on_toplevel_wm_capabilities(void*, xdg_toplevel*,
387 wl_array*) { }
388
389// ============================================================================
390// wl_seat
391// ============================================================================
392
393void WaylandWindow::on_seat_capabilities(void* data, wl_seat* seat, uint32_t caps)
394{
395 auto* self = static_cast<WaylandWindow*>(data);
396
397 const bool has_kb = caps & WL_SEAT_CAPABILITY_KEYBOARD;
398 const bool has_ptr = caps & WL_SEAT_CAPABILITY_POINTER;
399
400 if (has_kb && !self->m_keyboard) {
401 self->m_keyboard = wl_seat_get_keyboard(seat);
402 wl_keyboard_add_listener(self->m_keyboard, &s_keyboard_listener, self);
403 } else if (!has_kb && self->m_keyboard) {
404 wl_keyboard_destroy(self->m_keyboard);
405 self->m_keyboard = nullptr;
406 }
407
408 if (has_ptr && !self->m_pointer) {
409 self->m_pointer = wl_seat_get_pointer(seat);
410 wl_pointer_add_listener(self->m_pointer, &s_pointer_listener, self);
411 } else if (!has_ptr && self->m_pointer) {
412 wl_pointer_destroy(self->m_pointer);
413 self->m_pointer = nullptr;
414 }
415}
416
417void WaylandWindow::on_seat_name(void*, wl_seat*, const char*) { }
418
419// ============================================================================
420// wl_keyboard
421// ============================================================================
422
423void WaylandWindow::on_keyboard_keymap(void* data, wl_keyboard*,
424 uint32_t fmt, int fd, uint32_t size)
425{
426 auto* self = static_cast<WaylandWindow*>(data);
427
428 if (fmt != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
429 close(fd);
430 return;
431 }
432
433 void* buf = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
434 close(fd);
435 if (buf == MAP_FAILED)
436 return;
437
438 if (self->m_xkb_state) {
439 xkb_state_unref(self->m_xkb_state);
440 self->m_xkb_state = nullptr;
441 }
442 if (self->m_xkb_keymap) {
443 xkb_keymap_unref(self->m_xkb_keymap);
444 self->m_xkb_keymap = nullptr;
445 }
446
447 self->m_xkb_keymap = xkb_keymap_new_from_string(self->m_xkb_context,
448 static_cast<const char*>(buf),
449 XKB_KEYMAP_FORMAT_TEXT_V1,
450 XKB_KEYMAP_COMPILE_NO_FLAGS);
451
452 munmap(buf, size);
453
454 if (self->m_xkb_keymap)
455 self->m_xkb_state = xkb_state_new(self->m_xkb_keymap);
456}
457
458void WaylandWindow::on_keyboard_enter(void* data, wl_keyboard*,
459 uint32_t, wl_surface*, wl_array*)
460{
461 auto* self = static_cast<WaylandWindow*>(data);
462 self->m_state.is_focused = true;
463 WindowEvent ev;
464 ev.type = WindowEventType::WINDOW_FOCUS_GAINED;
465 self->emit(ev);
466}
467
468void WaylandWindow::on_keyboard_leave(void* data, wl_keyboard*,
469 uint32_t, wl_surface*)
470{
471 auto* self = static_cast<WaylandWindow*>(data);
472 self->m_state.is_focused = false;
473 WindowEvent ev;
474 ev.type = WindowEventType::WINDOW_FOCUS_LOST;
475 self->emit(ev);
476}
477
478void WaylandWindow::on_keyboard_key(void* data, wl_keyboard*,
479 uint32_t, uint32_t, uint32_t key, uint32_t state)
480{
481 auto* self = static_cast<WaylandWindow*>(data);
482 if (!self->m_xkb_state)
483 return;
484
485 IO::Keys mf_key = from_evdev_scancode(key);
486 if (mf_key == IO::Keys::Unknown) {
487 xkb_keycode_t xkb_key = key + 8;
488 xkb_keysym_t sym = xkb_state_key_get_one_sym(self->m_xkb_state, xkb_key);
489 mf_key = from_xkb_keysym(sym);
490 }
491
492 const WindowEvent::KeyData kd {
493 .key = static_cast<int16_t>(mf_key),
494 .scancode = static_cast<int32_t>(key),
495 .mods = 0
496 };
497
498 WindowEvent ev;
499 ev.data = kd;
500
501 if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
502 ev.type = WindowEventType::KEY_PRESSED;
503 self->emit(ev);
504
505 self->m_held_keys[kd.key] = kd;
506
507 if (self->m_repeat_fd < 0)
508 self->m_repeat_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
509
510 const long delay_ns = static_cast<long>(self->m_key_repeat_config.initial_delay_ms) * 1'000'000L;
511 const long repeat_ns = self->m_key_repeat_config.interval_ms > 0
512 ? static_cast<long>(self->m_key_repeat_config.interval_ms) * 1'000'000L
513 : 16'666'667L;
514
515 itimerspec ts {};
516 ts.it_value.tv_sec = 0;
517 ts.it_value.tv_nsec = delay_ns;
518 ts.it_interval.tv_sec = 0;
519 ts.it_interval.tv_nsec = repeat_ns;
520 timerfd_settime(self->m_repeat_fd, 0, &ts, nullptr);
521
522 } else {
523 self->m_held_keys.erase(kd.key);
524
525 if (self->m_held_keys.empty() && self->m_repeat_fd >= 0) {
526 itimerspec ts {};
527 timerfd_settime(self->m_repeat_fd, 0, &ts, nullptr);
528 }
529
530 ev.type = WindowEventType::KEY_RELEASED;
531 self->emit(ev);
532 }
533}
534
535void WaylandWindow::on_keyboard_modifiers(void* data, wl_keyboard*,
536 uint32_t, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group)
537{
538 auto* self = static_cast<WaylandWindow*>(data);
539 if (self->m_xkb_state) {
540 xkb_state_update_mask(self->m_xkb_state,
541 depressed, latched, locked, 0, 0, group);
542 }
543}
544
545void WaylandWindow::on_keyboard_repeat_info(void* data, wl_keyboard*,
546 int32_t rate, int32_t delay)
547{
548 auto* self = static_cast<WaylandWindow*>(data);
549 if (!self->m_key_repeat_config.allow_compositor_override)
550 return;
551
552 if (rate > 0)
553 self->m_key_repeat_config.interval_ms = 1000u / static_cast<uint32_t>(rate);
554 if (delay > 0)
555 self->m_key_repeat_config.initial_delay_ms = static_cast<uint32_t>(delay);
556}
557
558// ============================================================================
559// wl_pointer
560// ============================================================================
561
562void WaylandWindow::on_pointer_enter(void* data, wl_pointer*,
563 uint32_t, wl_surface*, wl_fixed_t sx, wl_fixed_t sy)
564{
565 auto* self = static_cast<WaylandWindow*>(data);
566 self->m_pointer_x = wl_fixed_to_double(sx);
567 self->m_pointer_y = wl_fixed_to_double(sy);
568 WindowEvent ev;
569 ev.type = WindowEventType::MOUSE_ENTERED;
570 self->emit(ev);
571}
572
573void WaylandWindow::on_pointer_leave(void* data, wl_pointer*, uint32_t, wl_surface*)
574{
575 auto* self = static_cast<WaylandWindow*>(data);
576 WindowEvent ev;
577 ev.type = WindowEventType::MOUSE_EXITED;
578 self->emit(ev);
579}
580
581void WaylandWindow::on_pointer_motion(void* data, wl_pointer*,
582 uint32_t, wl_fixed_t sx, wl_fixed_t sy)
583{
584 auto* self = static_cast<WaylandWindow*>(data);
585 self->m_pointer_x = wl_fixed_to_double(sx);
586 self->m_pointer_y = wl_fixed_to_double(sy);
587 WindowEvent ev;
588 ev.type = WindowEventType::MOUSE_MOTION;
589 ev.data = WindowEvent::MousePosData {
590 .x = self->m_pointer_x,
591 .y = self->m_pointer_y
592 };
593 self->emit(ev);
594}
595
596void WaylandWindow::on_pointer_button(void* data, wl_pointer*,
597 uint32_t, uint32_t, uint32_t button, uint32_t state)
598{
599 auto* self = static_cast<WaylandWindow*>(data);
600
601 IO::MouseButtons btn = IO::MouseButtons::Unknown;
602 switch (button) {
603 case BTN_LEFT:
604 btn = IO::MouseButtons::Left;
605 break;
606 case BTN_RIGHT:
607 btn = IO::MouseButtons::Right;
608 break;
609 case BTN_MIDDLE:
610 btn = IO::MouseButtons::Middle;
611 break;
612 case BTN_SIDE:
613 btn = IO::MouseButtons::Button4;
614 break;
615 case BTN_EXTRA:
616 btn = IO::MouseButtons::Button5;
617 break;
618 default:
619 break;
620 }
621
622 WindowEvent ev;
623 ev.type = (state == WL_POINTER_BUTTON_STATE_PRESSED)
624 ? WindowEventType::MOUSE_BUTTON_PRESSED
626 ev.data = WindowEvent::MouseButtonData {
627 .button = static_cast<int8_t>(btn),
628 .mods = 0
629 };
630 self->emit(ev);
631}
632
633void WaylandWindow::on_pointer_axis(void* data, wl_pointer*,
634 uint32_t, uint32_t axis, wl_fixed_t value)
635{
636 auto* self = static_cast<WaylandWindow*>(data);
637 const double v = wl_fixed_to_double(value) / 10.0;
638 WindowEvent ev;
639 ev.type = WindowEventType::MOUSE_SCROLLED;
640 ev.data = WindowEvent::ScrollData {
641 .x_offset = (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) ? v : 0.0,
642 .y_offset = (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) ? -v : 0.0
643 };
644 self->emit(ev);
645}
646
647void WaylandWindow::on_pointer_frame(void*, wl_pointer*) { }
648void WaylandWindow::on_pointer_axis_source(void*, wl_pointer*, uint32_t) { }
649void WaylandWindow::on_pointer_axis_stop(void*, wl_pointer*, uint32_t, uint32_t) { }
650void WaylandWindow::on_pointer_axis_discrete(void*, wl_pointer*, uint32_t, int32_t) { }
651
652} // namespace MayaFlux::Core
653
654#endif // MAYAFLUX_PLATFORM_LINUX
#define MF_ERROR(comp, ctx,...)
uint32_t h
Definition InkPress.cpp:28
WindowEventType
Types of window and input events.
std::vector< double > delay(std::span< const double > data, uint32_t delay_samples, double fill_value)
Prepend delay_samples zero-valued (or fill_value) samples, returning a new vector.