3#ifdef MAYAFLUX_PLATFORM_LINUX
5#include "KeyMapping.hpp"
8#include <linux/input-event-codes.h>
11#include <sys/timerfd.h>
20WaylandWindow::WaylandWindow(
const WindowCreateInfo& create_info,
21 const GlobalGraphicsConfig& graphics_config)
22 : m_display(wl_display_connect(nullptr))
23 , m_create_info(create_info)
24 , m_key_repeat_config(graphics_config.key_repeat_config)
28 MF_ERROR(Journal::Component::Core, Journal::Context::WindowingSubsystem,
29 "wl_display_connect failed");
33 m_xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
35 m_registry = wl_display_get_registry(m_display);
36 wl_registry_add_listener(m_registry, &s_registry_listener,
this);
37 wl_display_roundtrip(m_display);
39 if (!m_compositor || !m_xdg_wm_base) {
40 MF_ERROR(Journal::Component::Core, Journal::Context::WindowingSubsystem,
41 "Required Wayland globals not found (compositor={}, xdg_wm_base={})",
42 static_cast<void*
>(m_compositor),
static_cast<void*
>(m_xdg_wm_base));
46 m_surface = wl_compositor_create_surface(m_compositor);
47 m_xdg_surface = xdg_wm_base_get_xdg_surface(m_xdg_wm_base, m_surface);
48 xdg_surface_add_listener(m_xdg_surface, &s_xdg_surface_listener,
this);
50 m_xdg_toplevel = xdg_surface_get_toplevel(m_xdg_surface);
51 xdg_toplevel_add_listener(m_xdg_toplevel, &s_toplevel_listener,
this);
52 xdg_toplevel_set_title(m_xdg_toplevel, create_info.title.c_str());
53 xdg_toplevel_set_app_id(m_xdg_toplevel,
"mayaflux");
55 if (m_decoration_manager) {
56 m_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
57 m_decoration_manager, m_xdg_toplevel);
58 zxdg_toplevel_decoration_v1_set_mode(m_decoration,
59 ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
62 wl_surface_commit(m_surface);
63 wl_display_roundtrip(m_display);
65 m_state.current_width =
static_cast<int>(create_info.width);
66 m_state.current_height =
static_cast<int>(create_info.height);
69WaylandWindow::~WaylandWindow()
73 wl_display_disconnect(m_display);
82void WaylandWindow::show()
85 wl_surface_commit(m_surface);
88void WaylandWindow::hide()
91 wl_surface_attach(m_surface,
nullptr, 0, 0);
92 wl_surface_commit(m_surface);
96void WaylandWindow::destroy()
98 if (m_repeat_fd >= 0) {
103 if (m_text_repeat_fd >= 0) {
104 close(m_text_repeat_fd);
105 m_text_repeat_fd = -1;
109 wl_keyboard_destroy(m_keyboard);
110 m_keyboard =
nullptr;
113 wl_pointer_destroy(m_pointer);
118 xkb_state_unref(m_xkb_state);
119 m_xkb_state =
nullptr;
122 xkb_keymap_unref(m_xkb_keymap);
123 m_xkb_keymap =
nullptr;
126 xkb_context_unref(m_xkb_context);
127 m_xkb_context =
nullptr;
131 zxdg_toplevel_decoration_v1_destroy(m_decoration);
132 m_decoration =
nullptr;
134 if (m_decoration_manager) {
135 zxdg_decoration_manager_v1_destroy(m_decoration_manager);
136 m_decoration_manager =
nullptr;
138 if (m_xdg_toplevel) {
139 xdg_toplevel_destroy(m_xdg_toplevel);
140 m_xdg_toplevel =
nullptr;
143 xdg_surface_destroy(m_xdg_surface);
144 m_xdg_surface =
nullptr;
147 wl_surface_destroy(m_surface);
151 xdg_wm_base_destroy(m_xdg_wm_base);
152 m_xdg_wm_base =
nullptr;
155 wl_seat_destroy(m_seat);
159 wl_compositor_destroy(m_compositor);
160 m_compositor =
nullptr;
163 wl_registry_destroy(m_registry);
164 m_registry =
nullptr;
168bool WaylandWindow::should_close()
const
170 return m_should_close.load();
173void WaylandWindow::request_close()
175 m_event_source.defer([
this]() {
176 if (!m_should_close.exchange(
true)) {
178 ev.type = WindowEventType::WINDOW_CLOSED;
184void WaylandWindow::poll()
189 while (wl_display_prepare_read(m_display) != 0) {
190 if (wl_display_dispatch_pending(m_display) < 0) {
191 handle_display_error();
196 if (wl_display_flush(m_display) < 0 && errno != EAGAIN) {
197 wl_display_cancel_read(m_display);
198 handle_display_error();
203 pfd.fd = wl_display_get_fd(m_display);
206 if (::poll(&pfd, 1, 0) > 0 && (pfd.revents & (POLLIN | POLLHUP | POLLERR))) {
207 if (wl_display_read_events(m_display) < 0) {
208 handle_display_error();
212 wl_display_cancel_read(m_display);
215 if (wl_display_dispatch_pending(m_display) < 0) {
216 handle_display_error();
220 if (m_repeat_fd >= 0 && !m_held_keys.empty()) {
221 uint64_t expirations = 0;
222 if (read(m_repeat_fd, &expirations,
sizeof(expirations)) ==
sizeof(expirations)
223 && expirations > 0) {
224 for (
const auto& [k, kd] : m_held_keys) {
226 rev.type = WindowEventType::KEY_REPEAT;
233 if (m_text_repeat_fd >= 0 && m_active_text_key) {
234 uint64_t expirations = 0;
235 if (read(m_text_repeat_fd, &expirations,
sizeof(expirations)) ==
sizeof(expirations)
236 && expirations > 0) {
238 tev.type = WindowEventType::TEXT_INPUT;
239 tev.data = WindowEvent::TextData { .codepoint = m_active_text_key->second };
244 if (m_pending_configure.exchange(
false) && m_xdg_surface && m_surface) {
245 xdg_surface_set_window_geometry(m_xdg_surface, 0, 0,
246 static_cast<int32_t
>(m_state.current_width),
247 static_cast<int32_t
>(m_state.current_height));
248 wl_surface_commit(m_surface);
249 wl_display_flush(m_display);
253void WaylandWindow::handle_display_error()
255 const int err = wl_display_get_error(m_display);
258 const wl_interface* iface =
nullptr;
260 const uint32_t code = wl_display_get_protocol_error(m_display, &iface, &
id);
261 MF_ERROR(Journal::Component::Core, Journal::Context::WindowingSubsystem,
262 "Wayland protocol error: interface={}, id={}, code={}",
263 iface ? iface->name :
"unknown", id, code);
265 MF_ERROR(Journal::Component::Core, Journal::Context::WindowingSubsystem,
266 "Wayland display error: {}", err);
269 if (!m_should_close.exchange(
true)) {
271 ev.type = WindowEventType::WINDOW_CLOSED;
276void WaylandWindow::set_event_callback(WindowEventCallback
callback)
278 m_event_callback = std::move(
callback);
281void* WaylandWindow::get_native_handle()
const
286void* WaylandWindow::get_native_display()
const
291void WaylandWindow::set_title(
const std::string& title)
293 m_create_info.title = title;
295 xdg_toplevel_set_title(m_xdg_toplevel, title.c_str());
298void WaylandWindow::set_size(uint32_t w, uint32_t
h)
300 m_create_info.width = w;
301 m_create_info.height =
h;
302 if (m_xdg_toplevel && m_surface) {
303 xdg_toplevel_set_min_size(m_xdg_toplevel,
304 static_cast<int32_t
>(w),
static_cast<int32_t
>(
h));
305 xdg_toplevel_set_max_size(m_xdg_toplevel,
306 static_cast<int32_t
>(w),
static_cast<int32_t
>(
h));
307 wl_surface_commit(m_surface);
311void WaylandWindow::set_position(uint32_t, uint32_t)
316void WaylandWindow::set_color(
const std::array<float, 4>&
color)
318 m_create_info.clear_color =
color;
325void WaylandWindow::register_rendering_buffer(std::shared_ptr<Buffers::VKBuffer> buf)
327 std::lock_guard lock(m_render_tracking_mutex);
328 m_rendering_buffers.push_back(buf);
331void WaylandWindow::unregister_rendering_buffer(std::shared_ptr<Buffers::VKBuffer> buf)
333 std::lock_guard lock(m_render_tracking_mutex);
334 std::erase_if(m_rendering_buffers, [&buf](
const auto& wp) {
336 return !sp || sp == buf;
340void WaylandWindow::track_frame_command(uint64_t
id)
342 m_frame_commands.push_back(
id);
345const std::vector<uint64_t>& WaylandWindow::get_frame_commands()
const
347 return m_frame_commands;
350void WaylandWindow::clear_frame_commands()
352 m_frame_commands.clear();
355std::vector<std::shared_ptr<Buffers::VKBuffer>> WaylandWindow::get_rendering_buffers()
const
357 std::lock_guard lock(m_render_tracking_mutex);
358 std::vector<std::shared_ptr<Buffers::VKBuffer>>
out;
359 out.reserve(m_rendering_buffers.size());
360 for (
auto& wp : m_rendering_buffers) {
361 if (
auto sp = wp.lock())
371void WaylandWindow::emit(
const WindowEvent& ev)
373 m_event_source.signal(ev);
374 if (m_event_callback)
375 m_event_callback(ev);
382void WaylandWindow::on_registry_global(
void* data, wl_registry* reg,
383 uint32_t
name,
const char* iface, uint32_t version)
385 auto* self =
static_cast<WaylandWindow*
>(data);
387 if (std::string_view(iface) == wl_compositor_interface.name) {
388 self->m_compositor =
static_cast<wl_compositor*
>(
389 wl_registry_bind(reg,
name, &wl_compositor_interface,
390 std::min(version, 4U)));
391 }
else if (std::string_view(iface) == xdg_wm_base_interface.name) {
392 self->m_xdg_wm_base =
static_cast<xdg_wm_base*
>(
393 wl_registry_bind(reg,
name, &xdg_wm_base_interface,
394 std::min(version, 6U)));
395 xdg_wm_base_add_listener(self->m_xdg_wm_base, &s_wm_base_listener, self);
396 }
else if (std::string_view(iface) == wl_seat_interface.name) {
397 self->m_seat =
static_cast<wl_seat*
>(
398 wl_registry_bind(reg,
name, &wl_seat_interface,
399 std::min(version, 7U)));
400 wl_seat_add_listener(self->m_seat, &s_seat_listener, self);
401 }
else if (std::string_view(iface) == zxdg_decoration_manager_v1_interface.name) {
402 self->m_decoration_manager =
static_cast<zxdg_decoration_manager_v1*
>(
403 wl_registry_bind(reg,
name, &zxdg_decoration_manager_v1_interface, 1U));
407void WaylandWindow::on_registry_global_remove(
void*, wl_registry*, uint32_t) { }
413void WaylandWindow::on_wm_base_ping(
void*, xdg_wm_base* base, uint32_t serial)
415 xdg_wm_base_pong(base, serial);
422void WaylandWindow::on_xdg_surface_configure(
void* data, xdg_surface* surf, uint32_t serial)
424 auto* self =
static_cast<WaylandWindow*
>(data);
425 xdg_surface_ack_configure(surf, serial);
426 self->m_pending_configure.store(
true);
433void WaylandWindow::on_toplevel_configure(
void* data, xdg_toplevel*,
434 int32_t w, int32_t
h, wl_array* states)
436 auto* self =
static_cast<WaylandWindow*
>(data);
438 bool now_resizing =
false;
439 const auto* state_begin =
static_cast<const uint32_t*
>(states->data);
440 const auto* state_end =
reinterpret_cast<const uint32_t*
>(
441 static_cast<const uint8_t*
>(states->data) + states->size);
442 for (
const auto* state = state_begin; state != state_end; ++state) {
443 if (*state == XDG_TOPLEVEL_STATE_RESIZING) {
448 const bool gesture_ended = self->m_resizing && !now_resizing;
449 self->m_resizing = now_resizing;
451 if (w <= 0 ||
h <= 0)
454 const bool size_changed =
static_cast<uint32_t
>(w) != self->m_state.current_width
455 ||
static_cast<uint32_t
>(
h) != self->m_state.current_height;
457 if (!size_changed && !gesture_ended)
460 self->m_state.current_width =
static_cast<uint32_t
>(w);
461 self->m_state.current_height =
static_cast<uint32_t
>(
h);
462 self->m_create_info.width =
static_cast<uint32_t
>(w);
463 self->m_create_info.height =
static_cast<uint32_t
>(
h);
466 ev.type = WindowEventType::WINDOW_RESIZED;
467 ev.data = WindowEvent::ResizeData {
468 .width =
static_cast<uint32_t
>(w),
469 .
height =
static_cast<uint32_t
>(
h)
472 if (self->m_event_callback)
473 self->m_event_callback(ev);
476 self->m_event_source.signal(ev);
479void WaylandWindow::on_toplevel_close(
void* data, xdg_toplevel*)
481 auto* self =
static_cast<WaylandWindow*
>(data);
482 self->m_should_close.store(
true);
484 ev.type = WindowEventType::WINDOW_CLOSED;
488void WaylandWindow::on_toplevel_configure_bounds(
void*, xdg_toplevel*,
489 int32_t, int32_t) { }
491void WaylandWindow::on_toplevel_wm_capabilities(
void*, xdg_toplevel*,
498void WaylandWindow::on_seat_capabilities(
void* data, wl_seat* seat, uint32_t caps)
500 auto* self =
static_cast<WaylandWindow*
>(data);
502 const bool has_kb = caps & WL_SEAT_CAPABILITY_KEYBOARD;
503 const bool has_ptr = caps & WL_SEAT_CAPABILITY_POINTER;
505 if (has_kb && !self->m_keyboard) {
506 self->m_keyboard = wl_seat_get_keyboard(seat);
507 wl_keyboard_add_listener(self->m_keyboard, &s_keyboard_listener, self);
508 }
else if (!has_kb && self->m_keyboard) {
509 wl_keyboard_destroy(self->m_keyboard);
510 self->m_keyboard =
nullptr;
513 if (has_ptr && !self->m_pointer) {
514 self->m_pointer = wl_seat_get_pointer(seat);
515 wl_pointer_add_listener(self->m_pointer, &s_pointer_listener, self);
516 }
else if (!has_ptr && self->m_pointer) {
517 wl_pointer_destroy(self->m_pointer);
518 self->m_pointer =
nullptr;
522void WaylandWindow::on_seat_name(
void*, wl_seat*,
const char*) { }
528void WaylandWindow::on_keyboard_keymap(
void* data, wl_keyboard*,
529 uint32_t fmt,
int fd, uint32_t
size)
531 auto* self =
static_cast<WaylandWindow*
>(data);
533 if (fmt != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
538 void* buf = mmap(
nullptr,
size, PROT_READ, MAP_PRIVATE, fd, 0);
540 if (buf == MAP_FAILED)
543 if (self->m_xkb_state) {
544 xkb_state_unref(self->m_xkb_state);
545 self->m_xkb_state =
nullptr;
547 if (self->m_xkb_keymap) {
548 xkb_keymap_unref(self->m_xkb_keymap);
549 self->m_xkb_keymap =
nullptr;
552 self->m_xkb_keymap = xkb_keymap_new_from_string(self->m_xkb_context,
553 static_cast<const char*
>(buf),
554 XKB_KEYMAP_FORMAT_TEXT_V1,
555 XKB_KEYMAP_COMPILE_NO_FLAGS);
559 if (self->m_xkb_keymap)
560 self->m_xkb_state = xkb_state_new(self->m_xkb_keymap);
563void WaylandWindow::on_keyboard_enter(
void* data, wl_keyboard*,
564 uint32_t, wl_surface*, wl_array*)
566 auto* self =
static_cast<WaylandWindow*
>(data);
567 self->m_state.is_focused =
true;
569 ev.type = WindowEventType::WINDOW_FOCUS_GAINED;
573void WaylandWindow::on_keyboard_leave(
void* data, wl_keyboard*,
574 uint32_t, wl_surface*)
576 auto* self =
static_cast<WaylandWindow*
>(data);
577 self->m_state.is_focused =
false;
579 ev.type = WindowEventType::WINDOW_FOCUS_LOST;
583void WaylandWindow::on_keyboard_key(
void* data, wl_keyboard*,
584 uint32_t, uint32_t, uint32_t key, uint32_t state)
586 auto* self =
static_cast<WaylandWindow*
>(data);
587 if (!self->m_xkb_state)
590 const xkb_keycode_t xkb_key = key + 8;
592 IO::Keys mf_key = from_evdev_scancode(key);
593 if (mf_key == IO::Keys::Unknown) {
594 xkb_keysym_t sym = xkb_state_key_get_one_sym(self->m_xkb_state, xkb_key);
595 mf_key = from_xkb_keysym(sym);
598 const WindowEvent::KeyData kd {
599 .key =
static_cast<int16_t
>(mf_key),
600 .scancode =
static_cast<int32_t
>(key),
607 if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
608 ev.type = WindowEventType::KEY_PRESSED;
611 if (
const uint32_t codepoint = xkb_state_key_get_utf32(self->m_xkb_state, xkb_key);
614 tev.type = WindowEventType::TEXT_INPUT;
615 tev.data = WindowEvent::TextData { .codepoint = codepoint };
618 self->m_active_text_key = { kd.key, codepoint };
620 if (self->m_text_repeat_fd < 0)
621 self->m_text_repeat_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
623 const long t_delay_ns =
static_cast<long>(self->m_key_repeat_config.text_initial_delay_ms) * 1'000'000L;
624 const long t_repeat_ns = self->m_key_repeat_config.text_interval_ms > 0
625 ?
static_cast<long>(self->m_key_repeat_config.text_interval_ms) * 1'000'000L
629 ts.it_value.tv_sec = 0;
630 ts.it_value.tv_nsec = t_delay_ns;
631 ts.it_interval.tv_sec = 0;
632 ts.it_interval.tv_nsec = t_repeat_ns;
633 timerfd_settime(self->m_text_repeat_fd, 0, &ts,
nullptr);
636 self->m_held_keys[kd.key] = kd;
638 if (self->m_repeat_fd < 0)
639 self->m_repeat_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
641 const long delay_ns =
static_cast<long>(self->m_key_repeat_config.initial_delay_ms) * 1'000'000L;
642 const long repeat_ns = self->m_key_repeat_config.interval_ms > 0
643 ?
static_cast<long>(self->m_key_repeat_config.interval_ms) * 1'000'000L
647 ts.it_value.tv_sec = 0;
648 ts.it_value.tv_nsec = delay_ns;
649 ts.it_interval.tv_sec = 0;
650 ts.it_interval.tv_nsec = repeat_ns;
651 timerfd_settime(self->m_repeat_fd, 0, &ts,
nullptr);
654 if (self->m_active_text_key && self->m_active_text_key->first == kd.key) {
655 self->m_active_text_key = std::nullopt;
656 if (self->m_text_repeat_fd >= 0) {
658 timerfd_settime(self->m_text_repeat_fd, 0, &ts,
nullptr);
662 self->m_held_keys.erase(kd.key);
664 if (self->m_held_keys.empty() && self->m_repeat_fd >= 0) {
666 timerfd_settime(self->m_repeat_fd, 0, &ts,
nullptr);
669 ev.type = WindowEventType::KEY_RELEASED;
674void WaylandWindow::on_keyboard_modifiers(
void* data, wl_keyboard*,
675 uint32_t, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group)
677 auto* self =
static_cast<WaylandWindow*
>(data);
678 if (self->m_xkb_state) {
679 xkb_state_update_mask(self->m_xkb_state,
680 depressed, latched, locked, 0, 0, group);
684void WaylandWindow::on_keyboard_repeat_info(
void* data, wl_keyboard*,
685 int32_t
rate, int32_t delay)
687 auto* self =
static_cast<WaylandWindow*
>(data);
688 if (!self->m_key_repeat_config.allow_compositor_override)
692 self->m_key_repeat_config.interval_ms = 1000U /
static_cast<uint32_t
>(
rate);
693 self->m_key_repeat_config.text_interval_ms = 1000U /
static_cast<uint32_t
>(
rate);
696 self->m_key_repeat_config.initial_delay_ms =
static_cast<uint32_t
>(
delay);
697 self->m_key_repeat_config.text_initial_delay_ms =
static_cast<uint32_t
>(
delay);
705void WaylandWindow::on_pointer_enter(
void* data, wl_pointer*,
706 uint32_t, wl_surface*, wl_fixed_t sx, wl_fixed_t sy)
708 auto* self =
static_cast<WaylandWindow*
>(data);
709 self->m_pointer_x = wl_fixed_to_double(sx);
710 self->m_pointer_y = wl_fixed_to_double(sy);
712 ev.type = WindowEventType::MOUSE_ENTERED;
716void WaylandWindow::on_pointer_leave(
void* data, wl_pointer*, uint32_t, wl_surface*)
718 auto* self =
static_cast<WaylandWindow*
>(data);
720 ev.type = WindowEventType::MOUSE_EXITED;
724void WaylandWindow::on_pointer_motion(
void* data, wl_pointer*,
725 uint32_t, wl_fixed_t sx, wl_fixed_t sy)
727 auto* self =
static_cast<WaylandWindow*
>(data);
728 self->m_pointer_x = wl_fixed_to_double(sx);
729 self->m_pointer_y = wl_fixed_to_double(sy);
731 ev.type = WindowEventType::MOUSE_MOTION;
732 ev.data = WindowEvent::MousePosData {
733 .x = self->m_pointer_x,
734 .y = self->m_pointer_y
739void WaylandWindow::on_pointer_button(
void* data, wl_pointer*,
740 uint32_t, uint32_t, uint32_t button, uint32_t state)
742 auto* self =
static_cast<WaylandWindow*
>(data);
744 IO::MouseButtons btn = IO::MouseButtons::Unknown;
747 btn = IO::MouseButtons::Left;
750 btn = IO::MouseButtons::Right;
753 btn = IO::MouseButtons::Middle;
756 btn = IO::MouseButtons::Button4;
759 btn = IO::MouseButtons::Button5;
766 ev.type = (state == WL_POINTER_BUTTON_STATE_PRESSED)
767 ? WindowEventType::MOUSE_BUTTON_PRESSED
769 ev.data = WindowEvent::MouseButtonData {
770 .button =
static_cast<int8_t
>(btn),
776void WaylandWindow::on_pointer_axis(
void* data, wl_pointer*,
777 uint32_t, uint32_t axis, wl_fixed_t
value)
779 auto* self =
static_cast<WaylandWindow*
>(data);
780 const double v = wl_fixed_to_double(
value) / 10.0;
782 ev.type = WindowEventType::MOUSE_SCROLLED;
783 ev.data = WindowEvent::ScrollData {
784 .x_offset = (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) ? v : 0.0,
785 .y_offset = (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) ? -v : 0.0
790void WaylandWindow::on_pointer_frame(
void*, wl_pointer*) { }
791void WaylandWindow::on_pointer_axis_source(
void*, wl_pointer*, uint32_t) { }
792void WaylandWindow::on_pointer_axis_stop(
void*, wl_pointer*, uint32_t, uint32_t) { }
793void WaylandWindow::on_pointer_axis_discrete(
void*, wl_pointer*, uint32_t, int32_t) { }
#define MF_ERROR(comp, ctx,...)
std::vector< float > * out
WindowEventType
Types of window and input events.
constexpr bool is_text_input_codepoint(uint32_t codepoint) noexcept
Check if a Unicode codepoint is valid for text input.
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.
void close(std::span< const float > mask, std::span< float > tmp, std::span< float > dst, uint32_t w, uint32_t h, uint32_t radius)
Morphological closing writing into dst.