Path: blob/master/platform/linuxbsd/wayland/wayland_thread.cpp
11353 views
/**************************************************************************/1/* wayland_thread.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "wayland_thread.h"3132#include "core/config/engine.h"3334#ifdef WAYLAND_ENABLED3536#ifdef __FreeBSD__37#include <dev/evdev/input-event-codes.h>38#else39// Assume Linux.40#include <linux/input-event-codes.h>41#endif4243// For the actual polling thread.44#include <poll.h>4546// For shared memory buffer creation.47#include <fcntl.h>48#include <sys/mman.h>49#include <unistd.h>5051// Fix the wl_array_for_each macro to work with C++. This is based on the52// original from `wayland-util.h` in the Wayland client library.53#undef wl_array_for_each54#define wl_array_for_each(pos, array) \55for (pos = (decltype(pos))(array)->data; (const char *)pos < ((const char *)(array)->data + (array)->size); (pos)++)5657#define WAYLAND_THREAD_DEBUG_LOGS_ENABLED58#ifdef WAYLAND_THREAD_DEBUG_LOGS_ENABLED59#define DEBUG_LOG_WAYLAND_THREAD(...) print_verbose(__VA_ARGS__)60#else61#define DEBUG_LOG_WAYLAND_THREAD(...)62#endif6364// Since we're never going to use this interface directly, it's not worth65// generating the whole deal.66#define FIFO_INTERFACE_NAME "wp_fifo_manager_v1"6768// Read the content pointed by fd into a Vector<uint8_t>.69Vector<uint8_t> WaylandThread::_read_fd(int fd) {70// This is pretty much an arbitrary size.71uint32_t chunk_size = 2048;7273LocalVector<uint8_t> data;74data.resize(chunk_size);7576uint32_t bytes_read = 0;7778while (true) {79ssize_t last_bytes_read = read(fd, data.ptr() + bytes_read, chunk_size);80if (last_bytes_read < 0) {81ERR_PRINT(vformat("Read error %d.", errno));8283data.clear();84break;85}8687if (last_bytes_read == 0) {88// We're done, we've reached the EOF.89DEBUG_LOG_WAYLAND_THREAD(vformat("Done reading %d bytes.", bytes_read));9091close(fd);9293data.resize(bytes_read);94break;95}9697DEBUG_LOG_WAYLAND_THREAD(vformat("Read chunk of %d bytes.", last_bytes_read));9899bytes_read += last_bytes_read;100101// Increase the buffer size by one chunk in preparation of the next read.102data.resize(bytes_read + chunk_size);103}104105return Vector<uint8_t>(data);106}107108// Based on the wayland book's shared memory boilerplate (PD/CC0).109// See: https://wayland-book.com/surfaces/shared-memory.html110int WaylandThread::_allocate_shm_file(size_t size) {111int retries = 100;112113do {114// Generate a random name.115char name[] = "/wl_shm-godot-XXXXXX";116for (long unsigned int i = sizeof(name) - 7; i < sizeof(name) - 1; i++) {117name[i] = Math::random('A', 'Z');118}119120// Try to open a shared memory object with that name.121int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);122if (fd >= 0) {123// Success, unlink its name as we just need the file descriptor.124shm_unlink(name);125126// Resize the file to the requested length.127int ret;128do {129ret = ftruncate(fd, size);130} while (ret < 0 && errno == EINTR);131132if (ret < 0) {133close(fd);134return -1;135}136137return fd;138}139140retries--;141} while (retries > 0 && errno == EEXIST);142143return -1;144}145146// Return the content of a wl_data_offer.147Vector<uint8_t> WaylandThread::_wl_data_offer_read(struct wl_display *p_display, const char *p_mime, struct wl_data_offer *p_offer) {148if (!p_offer) {149return Vector<uint8_t>();150}151152int fds[2];153if (pipe(fds) == 0) {154wl_data_offer_receive(p_offer, p_mime, fds[1]);155156// Let the compositor know about the pipe.157// NOTE: It's important to just flush and not roundtrip here as we would risk158// running some cleanup event, like for example `wl_data_device::leave`. We're159// going to wait for the message anyways as the read will probably block if160// the compositor doesn't read from the other end of the pipe.161wl_display_flush(p_display);162163// Close the write end of the pipe, which we don't need and would otherwise164// just stall our next `read`s.165close(fds[1]);166167return _read_fd(fds[0]);168}169170return Vector<uint8_t>();171}172173// Read the content of a wp_primary_selection_offer.174Vector<uint8_t> WaylandThread::_wp_primary_selection_offer_read(struct wl_display *p_display, const char *p_mime, struct zwp_primary_selection_offer_v1 *p_offer) {175if (!p_offer) {176return Vector<uint8_t>();177}178179int fds[2];180if (pipe(fds) == 0) {181zwp_primary_selection_offer_v1_receive(p_offer, p_mime, fds[1]);182183// NOTE: It's important to just flush and not roundtrip here as we would risk184// running some cleanup event, like for example `wl_data_device::leave`. We're185// going to wait for the message anyways as the read will probably block if186// the compositor doesn't read from the other end of the pipe.187wl_display_flush(p_display);188189// Close the write end of the pipe, which we don't need and would otherwise190// just stall our next `read`s.191close(fds[1]);192193return _read_fd(fds[0]);194}195196return Vector<uint8_t>();197}198199Ref<InputEventKey> WaylandThread::_seat_state_get_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed) {200Ref<InputEventKey> event;201202ERR_FAIL_NULL_V(p_ss, event);203204Key shifted_key = KeyMappingXKB::get_keycode(xkb_state_key_get_one_sym(p_ss->xkb_state, p_keycode));205206Key plain_key = Key::NONE;207// NOTE: xkbcommon's API really encourages to apply the modifier state but we208// only want a "plain" symbol so that we can convert it into a godot keycode.209const xkb_keysym_t *syms = nullptr;210int num_sys = xkb_keymap_key_get_syms_by_level(p_ss->xkb_keymap, p_keycode, p_ss->current_layout_index, 0, &syms);211if (num_sys > 0 && syms) {212plain_key = KeyMappingXKB::get_keycode(syms[0]);213}214215Key physical_keycode = KeyMappingXKB::get_scancode(p_keycode);216KeyLocation key_location = KeyMappingXKB::get_location(p_keycode);217uint32_t unicode = xkb_state_key_get_utf32(p_ss->xkb_state, p_keycode);218219Key keycode = Key::NONE;220221if ((shifted_key & Key::SPECIAL) != Key::NONE || (plain_key & Key::SPECIAL) != Key::NONE) {222keycode = shifted_key;223}224225if (keycode == Key::NONE) {226keycode = plain_key;227}228229if (keycode == Key::NONE) {230keycode = physical_keycode;231}232233if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {234keycode -= 'a' - 'A';235}236237if (physical_keycode == Key::NONE && keycode == Key::NONE && unicode == 0) {238return event;239}240241event.instantiate();242243event->set_window_id(p_ss->focused_id);244245// Set all pressed modifiers.246event->set_shift_pressed(p_ss->shift_pressed);247event->set_ctrl_pressed(p_ss->ctrl_pressed);248event->set_alt_pressed(p_ss->alt_pressed);249event->set_meta_pressed(p_ss->meta_pressed);250251event->set_pressed(p_pressed);252event->set_keycode(keycode);253event->set_physical_keycode(physical_keycode);254event->set_location(key_location);255256if (unicode != 0) {257event->set_key_label(fix_key_label(unicode, keycode));258} else {259event->set_key_label(keycode);260}261262if (p_pressed) {263event->set_unicode(fix_unicode(unicode));264}265266// Taken from DisplayServerX11.267if (event->get_keycode() == Key::BACKTAB) {268// Make it consistent across platforms.269event->set_keycode(Key::TAB);270event->set_physical_keycode(Key::TAB);271event->set_shift_pressed(true);272}273274return event;275}276277// NOTE: Due to the nature of the way keys are encoded, there's an ambiguity278// regarding "special" keys. In other words: there's no reliable way of279// switching between a special key and a character key if not marking a280// different Godot keycode, even if we're actually using the same XKB raw281// keycode. This means that, during this switch, the old key will get "stuck",282// as it will never receive a release event. This method returns the necessary283// event to fix this if needed.284Ref<InputEventKey> WaylandThread::_seat_state_get_unstuck_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed, Key p_key) {285Ref<InputEventKey> event;286287if (p_pressed) {288Key *old_key = p_ss->pressed_keycodes.getptr(p_keycode);289if (old_key != nullptr && *old_key != p_key) {290print_verbose(vformat("%s and %s have same keycode. Generating release event for %s", keycode_get_string(*old_key), keycode_get_string(p_key), keycode_get_string(*old_key)));291event = _seat_state_get_key_event(p_ss, p_keycode, false);292if (event.is_valid()) {293event->set_keycode(*old_key);294}295}296p_ss->pressed_keycodes[p_keycode] = p_key;297} else {298p_ss->pressed_keycodes.erase(p_keycode);299}300301return event;302}303304void WaylandThread::_set_current_seat(struct wl_seat *p_seat) {305if (p_seat == wl_seat_current) {306return;307}308309SeatState *old_state = wl_seat_get_seat_state(wl_seat_current);310311if (old_state) {312seat_state_unlock_pointer(old_state);313}314315SeatState *new_state = wl_seat_get_seat_state(p_seat);316seat_state_unlock_pointer(new_state);317318wl_seat_current = p_seat;319pointer_set_constraint(pointer_constraint);320}321322// Returns whether it loaded the theme or not.323bool WaylandThread::_load_cursor_theme(int p_cursor_size) {324if (wl_cursor_theme) {325wl_cursor_theme_destroy(wl_cursor_theme);326wl_cursor_theme = nullptr;327}328329if (cursor_theme_name.is_empty()) {330cursor_theme_name = "default";331}332333print_verbose(vformat("Loading cursor theme \"%s\" size %d.", cursor_theme_name, p_cursor_size));334335wl_cursor_theme = wl_cursor_theme_load(cursor_theme_name.utf8().get_data(), p_cursor_size, registry.wl_shm);336337ERR_FAIL_NULL_V_MSG(wl_cursor_theme, false, "Can't load any cursor theme.");338339static const char *cursor_names[] = {340"left_ptr",341"xterm",342"hand2",343"cross",344"watch",345"left_ptr_watch",346"fleur",347"dnd-move",348"crossed_circle",349"v_double_arrow",350"h_double_arrow",351"size_bdiag",352"size_fdiag",353"move",354"row_resize",355"col_resize",356"question_arrow"357};358359static const char *cursor_names_fallback[] = {360nullptr,361nullptr,362"pointer",363"cross",364"wait",365"progress",366"grabbing",367"hand1",368"forbidden",369"ns-resize",370"ew-resize",371"fd_double_arrow",372"bd_double_arrow",373"fleur",374"sb_v_double_arrow",375"sb_h_double_arrow",376"help"377};378379for (int i = 0; i < DisplayServer::CURSOR_MAX; i++) {380struct wl_cursor *cursor = wl_cursor_theme_get_cursor(wl_cursor_theme, cursor_names[i]);381382if (!cursor && cursor_names_fallback[i]) {383cursor = wl_cursor_theme_get_cursor(wl_cursor_theme, cursor_names_fallback[i]);384}385386if (cursor && cursor->image_count > 0) {387wl_cursors[i] = cursor;388} else {389wl_cursors[i] = nullptr;390print_verbose("Failed loading cursor: " + String(cursor_names[i]));391}392}393394return true;395}396397void WaylandThread::_update_scale(int p_scale) {398if (p_scale <= cursor_scale) {399return;400}401402print_verbose(vformat("Bumping cursor scale to %d", p_scale));403404// There's some display that's bigger than the cache, let's update it.405cursor_scale = p_scale;406407if (wl_cursor_theme == nullptr) {408// Ugh. Either we're still initializing (this must've been called from the409// first roundtrips) or we had some error while doing so. We'll trust that it410// will be updated for us if needed.411return;412}413414int cursor_size = unscaled_cursor_size * p_scale;415416if (_load_cursor_theme(cursor_size)) {417for (struct wl_seat *wl_seat : registry.wl_seats) {418SeatState *ss = wl_seat_get_seat_state(wl_seat);419ERR_FAIL_NULL(ss);420421seat_state_update_cursor(ss);422}423}424}425426void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) {427RegistryState *registry = (RegistryState *)data;428ERR_FAIL_NULL(registry);429430if (strcmp(interface, wl_shm_interface.name) == 0) {431registry->wl_shm = (struct wl_shm *)wl_registry_bind(wl_registry, name, &wl_shm_interface, 1);432registry->wl_shm_name = name;433return;434}435436// NOTE: Deprecated.437if (strcmp(interface, zxdg_exporter_v1_interface.name) == 0) {438registry->xdg_exporter_v1 = (struct zxdg_exporter_v1 *)wl_registry_bind(wl_registry, name, &zxdg_exporter_v1_interface, 1);439registry->xdg_exporter_v1_name = name;440return;441}442443if (strcmp(interface, zxdg_exporter_v2_interface.name) == 0) {444registry->xdg_exporter_v2 = (struct zxdg_exporter_v2 *)wl_registry_bind(wl_registry, name, &zxdg_exporter_v2_interface, 1);445registry->xdg_exporter_v2_name = name;446return;447}448449if (strcmp(interface, wl_compositor_interface.name) == 0) {450registry->wl_compositor = (struct wl_compositor *)wl_registry_bind(wl_registry, name, &wl_compositor_interface, CLAMP((int)version, 1, 6));451registry->wl_compositor_name = name;452return;453}454455if (strcmp(interface, wl_data_device_manager_interface.name) == 0) {456registry->wl_data_device_manager = (struct wl_data_device_manager *)wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, CLAMP((int)version, 1, 3));457registry->wl_data_device_manager_name = name;458459// This global creates some seat data. Let's do that for the ones already available.460for (struct wl_seat *wl_seat : registry->wl_seats) {461SeatState *ss = wl_seat_get_seat_state(wl_seat);462ERR_FAIL_NULL(ss);463464if (ss->wl_data_device == nullptr) {465ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat);466wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss);467}468}469return;470}471472if (strcmp(interface, wl_output_interface.name) == 0) {473struct wl_output *wl_output = (struct wl_output *)wl_registry_bind(wl_registry, name, &wl_output_interface, CLAMP((int)version, 1, 4));474wl_proxy_tag_godot((struct wl_proxy *)wl_output);475476registry->wl_outputs.push_back(wl_output);477478ScreenState *ss = memnew(ScreenState);479ss->wl_output_name = name;480ss->wayland_thread = registry->wayland_thread;481482wl_proxy_tag_godot((struct wl_proxy *)wl_output);483wl_output_add_listener(wl_output, &wl_output_listener, ss);484return;485}486487if (strcmp(interface, wl_seat_interface.name) == 0) {488struct wl_seat *wl_seat = (struct wl_seat *)wl_registry_bind(wl_registry, name, &wl_seat_interface, CLAMP((int)version, 1, 9));489wl_proxy_tag_godot((struct wl_proxy *)wl_seat);490491SeatState *ss = memnew(SeatState);492ss->wl_seat = wl_seat;493ss->wl_seat_name = name;494495ss->registry = registry;496ss->wayland_thread = registry->wayland_thread;497498// Some extra stuff depends on other globals. We'll initialize them if the499// globals are already there, otherwise we'll have to do that once and if they500// get announced.501//502// NOTE: Don't forget to also bind/destroy with the respective global.503if (!ss->wl_data_device && registry->wl_data_device_manager) {504// Clipboard & DnD.505ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat);506wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss);507}508509if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) {510// Primary selection.511ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat);512zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss);513}514515if (!ss->wp_tablet_seat && registry->wp_tablet_manager) {516// Tablet.517ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat);518zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss);519}520521if (!ss->wp_text_input && registry->wp_text_input_manager) {522// IME.523ss->wp_text_input = zwp_text_input_manager_v3_get_text_input(registry->wp_text_input_manager, wl_seat);524zwp_text_input_v3_add_listener(ss->wp_text_input, &wp_text_input_listener, ss);525}526527registry->wl_seats.push_back(wl_seat);528529wl_seat_add_listener(wl_seat, &wl_seat_listener, ss);530531if (registry->wayland_thread->wl_seat_current == nullptr) {532registry->wayland_thread->_set_current_seat(wl_seat);533}534535return;536}537538if (strcmp(interface, xdg_wm_base_interface.name) == 0) {539registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, CLAMP((int)version, 1, 6));540registry->xdg_wm_base_name = name;541542xdg_wm_base_add_listener(registry->xdg_wm_base, &xdg_wm_base_listener, nullptr);543return;544}545546if (strcmp(interface, wp_viewporter_interface.name) == 0) {547registry->wp_viewporter = (struct wp_viewporter *)wl_registry_bind(wl_registry, name, &wp_viewporter_interface, 1);548registry->wp_viewporter_name = name;549}550551if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {552registry->wp_cursor_shape_manager = (struct wp_cursor_shape_manager_v1 *)wl_registry_bind(wl_registry, name, &wp_cursor_shape_manager_v1_interface, 1);553registry->wp_cursor_shape_manager_name = name;554return;555}556557if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) {558registry->wp_fractional_scale_manager = (struct wp_fractional_scale_manager_v1 *)wl_registry_bind(wl_registry, name, &wp_fractional_scale_manager_v1_interface, 1);559registry->wp_fractional_scale_manager_name = name;560561// NOTE: We're not mapping the fractional scale object here because this is562// supposed to be a "startup global". If for some reason this isn't true (who563// knows), add a conditional branch for creating the add-on object.564}565566if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {567registry->xdg_decoration_manager = (struct zxdg_decoration_manager_v1 *)wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1);568registry->xdg_decoration_manager_name = name;569return;570}571572if (strcmp(interface, xdg_system_bell_v1_interface.name) == 0) {573registry->xdg_system_bell = (struct xdg_system_bell_v1 *)wl_registry_bind(wl_registry, name, &xdg_system_bell_v1_interface, 1);574registry->xdg_system_bell_name = name;575return;576}577578if (strcmp(interface, xdg_toplevel_icon_manager_v1_interface.name) == 0) {579registry->xdg_toplevel_icon_manager = (struct xdg_toplevel_icon_manager_v1 *)wl_registry_bind(wl_registry, name, &xdg_toplevel_icon_manager_v1_interface, 1);580registry->xdg_toplevel_icon_manager_name = name;581return;582}583584if (strcmp(interface, xdg_activation_v1_interface.name) == 0) {585registry->xdg_activation = (struct xdg_activation_v1 *)wl_registry_bind(wl_registry, name, &xdg_activation_v1_interface, 1);586registry->xdg_activation_name = name;587return;588}589590if (strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0) {591registry->wp_primary_selection_device_manager = (struct zwp_primary_selection_device_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_primary_selection_device_manager_v1_interface, 1);592593// This global creates some seat data. Let's do that for the ones already available.594for (struct wl_seat *wl_seat : registry->wl_seats) {595SeatState *ss = wl_seat_get_seat_state(wl_seat);596ERR_FAIL_NULL(ss);597598if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) {599ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat);600zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss);601}602}603}604605if (strcmp(interface, zwp_relative_pointer_manager_v1_interface.name) == 0) {606registry->wp_relative_pointer_manager = (struct zwp_relative_pointer_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_relative_pointer_manager_v1_interface, 1);607registry->wp_relative_pointer_manager_name = name;608return;609}610611if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) {612registry->wp_pointer_constraints = (struct zwp_pointer_constraints_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1);613registry->wp_pointer_constraints_name = name;614return;615}616617if (strcmp(interface, zwp_pointer_gestures_v1_interface.name) == 0) {618registry->wp_pointer_gestures = (struct zwp_pointer_gestures_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_gestures_v1_interface, 1);619registry->wp_pointer_gestures_name = name;620return;621}622623if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {624registry->wp_idle_inhibit_manager = (struct zwp_idle_inhibit_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_idle_inhibit_manager_v1_interface, 1);625registry->wp_idle_inhibit_manager_name = name;626return;627}628629if (strcmp(interface, zwp_tablet_manager_v2_interface.name) == 0) {630registry->wp_tablet_manager = (struct zwp_tablet_manager_v2 *)wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1);631registry->wp_tablet_manager_name = name;632633// This global creates some seat data. Let's do that for the ones already available.634for (struct wl_seat *wl_seat : registry->wl_seats) {635SeatState *ss = wl_seat_get_seat_state(wl_seat);636ERR_FAIL_NULL(ss);637638ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat);639zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss);640}641642return;643}644645if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) {646registry->wp_text_input_manager = (struct zwp_text_input_manager_v3 *)wl_registry_bind(wl_registry, name, &zwp_text_input_manager_v3_interface, 1);647registry->wp_text_input_manager_name = name;648649// This global creates some seat data. Let's do that for the ones already available.650for (struct wl_seat *wl_seat : registry->wl_seats) {651SeatState *ss = wl_seat_get_seat_state(wl_seat);652ERR_FAIL_NULL(ss);653654ss->wp_text_input = zwp_text_input_manager_v3_get_text_input(registry->wp_text_input_manager, wl_seat);655zwp_text_input_v3_add_listener(ss->wp_text_input, &wp_text_input_listener, ss);656}657658return;659}660661if (strcmp(interface, FIFO_INTERFACE_NAME) == 0) {662registry->wp_fifo_manager_name = name;663}664}665666void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) {667RegistryState *registry = (RegistryState *)data;668ERR_FAIL_NULL(registry);669670if (name == registry->wl_shm_name) {671if (registry->wl_shm) {672wl_shm_destroy(registry->wl_shm);673registry->wl_shm = nullptr;674}675676registry->wl_shm_name = 0;677678return;679}680681// NOTE: Deprecated.682if (name == registry->xdg_exporter_v1_name) {683if (registry->xdg_exporter_v1) {684zxdg_exporter_v1_destroy(registry->xdg_exporter_v1);685registry->xdg_exporter_v1 = nullptr;686}687688registry->xdg_exporter_v1_name = 0;689690return;691}692693if (name == registry->xdg_exporter_v2_name) {694if (registry->xdg_exporter_v2) {695zxdg_exporter_v2_destroy(registry->xdg_exporter_v2);696registry->xdg_exporter_v2 = nullptr;697}698699registry->xdg_exporter_v2_name = 0;700701return;702}703704if (name == registry->wl_compositor_name) {705if (registry->wl_compositor) {706wl_compositor_destroy(registry->wl_compositor);707registry->wl_compositor = nullptr;708}709710registry->wl_compositor_name = 0;711712return;713}714715if (name == registry->wl_data_device_manager_name) {716if (registry->wl_data_device_manager) {717wl_data_device_manager_destroy(registry->wl_data_device_manager);718registry->wl_data_device_manager = nullptr;719}720721registry->wl_data_device_manager_name = 0;722723// This global is used to create some seat data. Let's clean it.724for (struct wl_seat *wl_seat : registry->wl_seats) {725SeatState *ss = wl_seat_get_seat_state(wl_seat);726ERR_FAIL_NULL(ss);727728if (ss->wl_data_device) {729wl_data_device_destroy(ss->wl_data_device);730ss->wl_data_device = nullptr;731}732733ss->wl_data_device = nullptr;734}735736return;737}738739if (name == registry->xdg_wm_base_name) {740if (registry->xdg_wm_base) {741xdg_wm_base_destroy(registry->xdg_wm_base);742registry->xdg_wm_base = nullptr;743}744745registry->xdg_wm_base_name = 0;746747return;748}749750if (name == registry->wp_viewporter_name) {751for (KeyValue<DisplayServer::WindowID, WindowState> &pair : registry->wayland_thread->windows) {752WindowState ws = pair.value;753if (registry->wp_viewporter) {754wp_viewporter_destroy(registry->wp_viewporter);755registry->wp_viewporter = nullptr;756}757758if (ws.wp_viewport) {759wp_viewport_destroy(ws.wp_viewport);760ws.wp_viewport = nullptr;761}762}763764registry->wp_viewporter_name = 0;765766return;767}768769if (name == registry->wp_cursor_shape_manager_name) {770if (registry->wp_cursor_shape_manager) {771wp_cursor_shape_manager_v1_destroy(registry->wp_cursor_shape_manager);772registry->wp_cursor_shape_manager = nullptr;773}774775registry->wp_cursor_shape_manager_name = 0;776777for (struct wl_seat *wl_seat : registry->wl_seats) {778SeatState *ss = wl_seat_get_seat_state(wl_seat);779ERR_FAIL_NULL(ss);780781if (ss->wp_cursor_shape_device) {782wp_cursor_shape_device_v1_destroy(ss->wp_cursor_shape_device);783ss->wp_cursor_shape_device = nullptr;784}785}786}787788if (name == registry->wp_fractional_scale_manager_name) {789for (KeyValue<DisplayServer::WindowID, WindowState> &pair : registry->wayland_thread->windows) {790WindowState ws = pair.value;791792if (registry->wp_fractional_scale_manager) {793wp_fractional_scale_manager_v1_destroy(registry->wp_fractional_scale_manager);794registry->wp_fractional_scale_manager = nullptr;795}796797if (ws.wp_fractional_scale) {798wp_fractional_scale_v1_destroy(ws.wp_fractional_scale);799ws.wp_fractional_scale = nullptr;800}801}802803registry->wp_fractional_scale_manager_name = 0;804}805806if (name == registry->xdg_decoration_manager_name) {807if (registry->xdg_decoration_manager) {808zxdg_decoration_manager_v1_destroy(registry->xdg_decoration_manager);809registry->xdg_decoration_manager = nullptr;810}811812registry->xdg_decoration_manager_name = 0;813814return;815}816817if (name == registry->xdg_system_bell_name) {818if (registry->xdg_system_bell) {819xdg_system_bell_v1_destroy(registry->xdg_system_bell);820registry->xdg_system_bell = nullptr;821}822823registry->xdg_system_bell_name = 0;824825return;826}827828if (name == registry->xdg_toplevel_icon_manager_name) {829if (registry->xdg_toplevel_icon_manager) {830xdg_toplevel_icon_manager_v1_destroy(registry->xdg_toplevel_icon_manager);831registry->xdg_toplevel_icon_manager = nullptr;832}833834if (registry->wayland_thread->xdg_icon) {835xdg_toplevel_icon_v1_destroy(registry->wayland_thread->xdg_icon);836}837838if (registry->wayland_thread->icon_buffer) {839wl_buffer_destroy(registry->wayland_thread->icon_buffer);840}841842registry->xdg_toplevel_icon_manager_name = 0;843844return;845}846847if (name == registry->xdg_activation_name) {848if (registry->xdg_activation) {849xdg_activation_v1_destroy(registry->xdg_activation);850registry->xdg_activation = nullptr;851}852853registry->xdg_activation_name = 0;854855return;856}857858if (name == registry->wp_primary_selection_device_manager_name) {859if (registry->wp_primary_selection_device_manager) {860zwp_primary_selection_device_manager_v1_destroy(registry->wp_primary_selection_device_manager);861registry->wp_primary_selection_device_manager = nullptr;862}863864registry->wp_primary_selection_device_manager_name = 0;865866// This global is used to create some seat data. Let's clean it.867for (struct wl_seat *wl_seat : registry->wl_seats) {868SeatState *ss = wl_seat_get_seat_state(wl_seat);869ERR_FAIL_NULL(ss);870871if (ss->wp_primary_selection_device) {872zwp_primary_selection_device_v1_destroy(ss->wp_primary_selection_device);873ss->wp_primary_selection_device = nullptr;874}875876if (ss->wp_primary_selection_source) {877zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source);878ss->wp_primary_selection_source = nullptr;879}880881if (ss->wp_primary_selection_offer) {882memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer));883zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer);884ss->wp_primary_selection_offer = nullptr;885}886}887888return;889}890891if (name == registry->wp_relative_pointer_manager_name) {892if (registry->wp_relative_pointer_manager) {893zwp_relative_pointer_manager_v1_destroy(registry->wp_relative_pointer_manager);894registry->wp_relative_pointer_manager = nullptr;895}896897registry->wp_relative_pointer_manager_name = 0;898899// This global is used to create some seat data. Let's clean it.900for (struct wl_seat *wl_seat : registry->wl_seats) {901SeatState *ss = wl_seat_get_seat_state(wl_seat);902ERR_FAIL_NULL(ss);903904if (ss->wp_relative_pointer) {905zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);906ss->wp_relative_pointer = nullptr;907}908}909910return;911}912913if (name == registry->wp_pointer_constraints_name) {914if (registry->wp_pointer_constraints) {915zwp_pointer_constraints_v1_destroy(registry->wp_pointer_constraints);916registry->wp_pointer_constraints = nullptr;917}918919registry->wp_pointer_constraints_name = 0;920921// This global is used to create some seat data. Let's clean it.922for (struct wl_seat *wl_seat : registry->wl_seats) {923SeatState *ss = wl_seat_get_seat_state(wl_seat);924ERR_FAIL_NULL(ss);925926if (ss->wp_relative_pointer) {927zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);928ss->wp_relative_pointer = nullptr;929}930931if (ss->wp_locked_pointer) {932zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);933ss->wp_locked_pointer = nullptr;934}935936if (ss->wp_confined_pointer) {937zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);938ss->wp_confined_pointer = nullptr;939}940}941942return;943}944945if (name == registry->wp_pointer_gestures_name) {946if (registry->wp_pointer_gestures) {947zwp_pointer_gestures_v1_destroy(registry->wp_pointer_gestures);948}949950registry->wp_pointer_gestures = nullptr;951registry->wp_pointer_gestures_name = 0;952953// This global is used to create some seat data. Let's clean it.954for (struct wl_seat *wl_seat : registry->wl_seats) {955SeatState *ss = wl_seat_get_seat_state(wl_seat);956ERR_FAIL_NULL(ss);957958if (ss->wp_pointer_gesture_pinch) {959zwp_pointer_gesture_pinch_v1_destroy(ss->wp_pointer_gesture_pinch);960ss->wp_pointer_gesture_pinch = nullptr;961}962}963964return;965}966967if (name == registry->wp_idle_inhibit_manager_name) {968if (registry->wp_idle_inhibit_manager) {969zwp_idle_inhibit_manager_v1_destroy(registry->wp_idle_inhibit_manager);970registry->wp_idle_inhibit_manager = nullptr;971}972973registry->wp_idle_inhibit_manager_name = 0;974975return;976}977978if (name == registry->wp_tablet_manager_name) {979if (registry->wp_tablet_manager) {980zwp_tablet_manager_v2_destroy(registry->wp_tablet_manager);981registry->wp_tablet_manager = nullptr;982}983984registry->wp_tablet_manager_name = 0;985986// This global is used to create some seat data. Let's clean it.987for (struct wl_seat *wl_seat : registry->wl_seats) {988SeatState *ss = wl_seat_get_seat_state(wl_seat);989ERR_FAIL_NULL(ss);990991for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {992TabletToolState *state = wp_tablet_tool_get_state(tool);993if (state) {994memdelete(state);995}996997zwp_tablet_tool_v2_destroy(tool);998}9991000ss->tablet_tools.clear();1001}10021003return;1004}10051006if (name == registry->wp_text_input_manager_name) {1007if (registry->wp_text_input_manager) {1008zwp_text_input_manager_v3_destroy(registry->wp_text_input_manager);1009registry->wp_text_input_manager = nullptr;1010}10111012registry->wp_text_input_manager_name = 0;10131014for (struct wl_seat *wl_seat : registry->wl_seats) {1015SeatState *ss = wl_seat_get_seat_state(wl_seat);1016ERR_FAIL_NULL(ss);10171018zwp_text_input_v3_destroy(ss->wp_text_input);1019ss->wp_text_input = nullptr;1020}10211022return;1023}10241025{1026// Iterate through all of the seats to find if any got removed.1027List<struct wl_seat *>::Element *E = registry->wl_seats.front();1028while (E) {1029struct wl_seat *wl_seat = E->get();1030List<struct wl_seat *>::Element *N = E->next();10311032SeatState *ss = wl_seat_get_seat_state(wl_seat);1033ERR_FAIL_NULL(ss);10341035if (ss->wl_seat_name == name) {1036if (wl_seat) {1037wl_seat_destroy(wl_seat);1038}10391040if (ss->wl_data_device) {1041wl_data_device_destroy(ss->wl_data_device);1042}10431044if (ss->wp_tablet_seat) {1045zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat);10461047for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {1048TabletToolState *state = wp_tablet_tool_get_state(tool);1049if (state) {1050memdelete(state);1051}10521053zwp_tablet_tool_v2_destroy(tool);1054}1055}10561057memdelete(ss);10581059registry->wl_seats.erase(E);1060return;1061}10621063E = N;1064}1065}10661067{1068// Iterate through all of the outputs to find if any got removed.1069// FIXME: This is a very bruteforce approach.1070List<struct wl_output *>::Element *it = registry->wl_outputs.front();1071while (it) {1072// Iterate through all of the screens to find if any got removed.1073struct wl_output *wl_output = it->get();1074ERR_FAIL_NULL(wl_output);10751076ScreenState *ss = wl_output_get_screen_state(wl_output);10771078if (ss->wl_output_name == name) {1079registry->wl_outputs.erase(it);10801081memdelete(ss);1082wl_output_destroy(wl_output);10831084return;1085}10861087it = it->next();1088}1089}10901091if (name == registry->wp_fifo_manager_name) {1092registry->wp_fifo_manager_name = 0;1093}1094}10951096void WaylandThread::_wl_surface_on_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {1097if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) {1098// This won't have the right data bound to it. Not worth it and would probably1099// just break everything.1100return;1101}11021103WindowState *ws = (WindowState *)data;1104ERR_FAIL_NULL(ws);11051106DEBUG_LOG_WAYLAND_THREAD(vformat("Window entered output %x.\n", (size_t)wl_output));11071108ws->wl_outputs.insert(wl_output);11091110// Workaround for buffer scaling as there's no guaranteed way of knowing the1111// preferred scale.1112// TODO: Skip this branch for newer `wl_surface`s once we add support for1113// `wl_surface::preferred_buffer_scale`1114if (ws->preferred_fractional_scale == 0) {1115window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height);1116}1117}11181119void WaylandThread::_frame_wl_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t callback_data) {1120wl_callback_destroy(wl_callback);11211122WindowState *ws = (WindowState *)data;1123ERR_FAIL_NULL(ws);1124ERR_FAIL_NULL(ws->wayland_thread);1125ERR_FAIL_NULL(ws->wl_surface);11261127ws->last_frame_time = OS::get_singleton()->get_ticks_usec();1128ws->wayland_thread->set_frame();11291130ws->frame_callback = wl_surface_frame(ws->wl_surface);1131wl_callback_add_listener(ws->frame_callback, &frame_wl_callback_listener, ws);11321133if (ws->wl_surface && ws->buffer_scale_changed) {1134// NOTE: We're only now setting the buffer scale as the idea is to get this1135// data committed together with the new frame, all by the rendering driver.1136// This is important because we might otherwise set an invalid combination of1137// buffer size and scale (e.g. odd size and 2x scale). We're pretty much1138// guaranteed to get a proper buffer in the next render loop as the rescaling1139// method also informs the engine of a "window rect change", triggering1140// rendering if needed.1141wl_surface_set_buffer_scale(ws->wl_surface, window_state_get_preferred_buffer_scale(ws));1142}1143}11441145void WaylandThread::_wl_surface_on_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {1146if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) {1147// This won't have the right data bound to it. Not worth it and would probably1148// just break everything.1149return;1150}11511152WindowState *ws = (WindowState *)data;1153ERR_FAIL_NULL(ws);11541155ws->wl_outputs.erase(wl_output);11561157DEBUG_LOG_WAYLAND_THREAD(vformat("Window left output %x.\n", (size_t)wl_output));1158}11591160// TODO: Add support to this event.1161void WaylandThread::_wl_surface_on_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor) {1162}11631164// TODO: Add support to this event.1165void WaylandThread::_wl_surface_on_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform) {1166}11671168void WaylandThread::_wl_output_on_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform) {1169ScreenState *ss = (ScreenState *)data;1170ERR_FAIL_NULL(ss);11711172ss->pending_data.position.x = x;11731174ss->pending_data.position.x = x;1175ss->pending_data.position.y = y;11761177ss->pending_data.physical_size.width = physical_width;1178ss->pending_data.physical_size.height = physical_height;11791180ss->pending_data.make.clear();1181ss->pending_data.make.append_utf8(make);1182ss->pending_data.model.clear();1183ss->pending_data.model.append_utf8(model);11841185// `wl_output::done` is a version 2 addition. We'll directly update the data1186// for compatibility.1187if (wl_output_get_version(wl_output) == 1) {1188ss->data = ss->pending_data;1189}1190}11911192void WaylandThread::_wl_output_on_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {1193ScreenState *ss = (ScreenState *)data;1194ERR_FAIL_NULL(ss);11951196ss->pending_data.size.width = width;1197ss->pending_data.size.height = height;11981199ss->pending_data.refresh_rate = refresh ? refresh / 1000.0f : -1;12001201// `wl_output::done` is a version 2 addition. We'll directly update the data1202// for compatibility.1203if (wl_output_get_version(wl_output) == 1) {1204ss->data = ss->pending_data;1205}1206}12071208// NOTE: The following `wl_output` events are only for version 2 onwards, so we1209// can assume that they're "atomic" (i.e. rely on the `wl_output::done` event).12101211void WaylandThread::_wl_output_on_done(void *data, struct wl_output *wl_output) {1212ScreenState *ss = (ScreenState *)data;1213ERR_FAIL_NULL(ss);12141215ss->data = ss->pending_data;12161217ss->wayland_thread->_update_scale(ss->data.scale);12181219DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x done.", (size_t)wl_output));1220}12211222void WaylandThread::_wl_output_on_scale(void *data, struct wl_output *wl_output, int32_t factor) {1223ScreenState *ss = (ScreenState *)data;1224ERR_FAIL_NULL(ss);12251226ss->pending_data.scale = factor;12271228DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x scale %d", (size_t)wl_output, factor));1229}12301231void WaylandThread::_wl_output_on_name(void *data, struct wl_output *wl_output, const char *name) {1232}12331234void WaylandThread::_wl_output_on_description(void *data, struct wl_output *wl_output, const char *description) {1235}12361237void WaylandThread::_xdg_wm_base_on_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) {1238xdg_wm_base_pong(xdg_wm_base, serial);1239}12401241void WaylandThread::_xdg_surface_on_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) {1242xdg_surface_ack_configure(xdg_surface, serial);12431244WindowState *ws = (WindowState *)data;1245ERR_FAIL_NULL(ws);12461247DEBUG_LOG_WAYLAND_THREAD(vformat("xdg surface on configure rect %s", ws->rect));1248}12491250void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) {1251WindowState *ws = (WindowState *)data;1252ERR_FAIL_NULL(ws);12531254// Expect the window to be in a plain state. It will get properly set if the1255// compositor reports otherwise below.1256ws->mode = DisplayServer::WINDOW_MODE_WINDOWED;1257ws->suspended = false;12581259uint32_t *state = nullptr;1260wl_array_for_each(state, states) {1261switch (*state) {1262case XDG_TOPLEVEL_STATE_MAXIMIZED: {1263ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED;1264} break;12651266case XDG_TOPLEVEL_STATE_FULLSCREEN: {1267ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN;1268} break;12691270case XDG_TOPLEVEL_STATE_SUSPENDED: {1271ws->suspended = true;1272} break;12731274default: {1275// We don't care about the other states (for now).1276} break;1277}1278}12791280if (width != 0 && height != 0) {1281window_state_update_size(ws, width, height);1282}12831284DEBUG_LOG_WAYLAND_THREAD(vformat("XDG toplevel on configure width %d height %d.", width, height));1285}12861287void WaylandThread::_xdg_toplevel_on_close(void *data, struct xdg_toplevel *xdg_toplevel) {1288WindowState *ws = (WindowState *)data;1289ERR_FAIL_NULL(ws);12901291Ref<WindowEventMessage> msg;1292msg.instantiate();1293msg->id = ws->id;1294msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST;1295ws->wayland_thread->push_message(msg);1296}12971298void WaylandThread::_xdg_toplevel_on_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) {1299}13001301void WaylandThread::_xdg_toplevel_on_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities) {1302WindowState *ws = (WindowState *)data;1303ERR_FAIL_NULL(ws);13041305ws->can_maximize = false;1306ws->can_fullscreen = false;1307ws->can_minimize = false;13081309uint32_t *capability = nullptr;1310wl_array_for_each(capability, capabilities) {1311switch (*capability) {1312case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: {1313ws->can_maximize = true;1314} break;1315case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: {1316ws->can_fullscreen = true;1317} break;13181319case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: {1320ws->can_minimize = true;1321} break;13221323default: {1324} break;1325}1326}1327}13281329void WaylandThread::_xdg_popup_on_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height) {1330WindowState *ws = (WindowState *)data;1331ERR_FAIL_NULL(ws);13321333if (width != 0 && height != 0) {1334window_state_update_size(ws, width, height);1335}13361337WindowState *parent = ws->wayland_thread->window_get_state(ws->parent_id);1338ERR_FAIL_NULL(parent);13391340Point2i pos = Point2i(x, y);1341#ifdef LIBDECOR_ENABLED1342if (parent->libdecor_frame) {1343int translated_x = x;1344int translated_y = y;1345libdecor_frame_translate_coordinate(parent->libdecor_frame, x, y, &translated_x, &translated_y);13461347pos.x = translated_x;1348pos.y = translated_y;1349}1350#endif13511352// Looks like the position returned here is relative to the parent. We have to1353// accumulate it or there's gonna be a lot of confusion godot-side.1354pos += parent->rect.position;13551356if (ws->rect.position != pos) {1357DEBUG_LOG_WAYLAND_THREAD(vformat("Repositioning popup %d from %s to %s", ws->id, ws->rect.position, pos));13581359double parent_scale = window_state_get_scale_factor(parent);13601361ws->rect.position = pos;13621363Ref<WindowRectMessage> rect_msg;1364rect_msg.instantiate();1365rect_msg->id = ws->id;1366rect_msg->rect.position = scale_vector2i(ws->rect.position, parent_scale);1367rect_msg->rect.size = scale_vector2i(ws->rect.size, parent_scale);13681369ws->wayland_thread->push_message(rect_msg);1370}13711372DEBUG_LOG_WAYLAND_THREAD(vformat("xdg popup on configure x%d y%d w%d h%d", x, y, width, height));1373}13741375void WaylandThread::_xdg_popup_on_popup_done(void *data, struct xdg_popup *xdg_popup) {1376WindowState *ws = (WindowState *)data;1377ERR_FAIL_NULL(ws);13781379Ref<WindowEventMessage> ev_msg;1380ev_msg.instantiate();1381ev_msg->id = ws->id;1382ev_msg->event = DisplayServer::WINDOW_EVENT_FORCE_CLOSE;13831384ws->wayland_thread->push_message(ev_msg);1385}13861387void WaylandThread::_xdg_popup_on_repositioned(void *data, struct xdg_popup *xdg_popup, uint32_t token) {1388DEBUG_LOG_WAYLAND_THREAD(vformat("stub xdg popup repositioned %x", token));1389}13901391// NOTE: Deprecated.1392void WaylandThread::_xdg_exported_v1_on_handle(void *data, zxdg_exported_v1 *exported, const char *handle) {1393WindowState *ws = (WindowState *)data;1394ERR_FAIL_NULL(ws);13951396ws->exported_handle = vformat("wayland:%s", String::utf8(handle));1397}13981399void WaylandThread::_xdg_exported_v2_on_handle(void *data, zxdg_exported_v2 *exported, const char *handle) {1400WindowState *ws = (WindowState *)data;1401ERR_FAIL_NULL(ws);14021403ws->exported_handle = vformat("wayland:%s", String::utf8(handle));1404}14051406void WaylandThread::_xdg_toplevel_decoration_on_configure(void *data, struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration, uint32_t mode) {1407if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE) {1408#ifdef LIBDECOR_ENABLED1409WARN_PRINT_ONCE("Native client side decorations are not yet supported without libdecor!");1410#else1411WARN_PRINT_ONCE("Native client side decorations are not yet supported!");1412#endif // LIBDECOR_ENABLED1413}1414}14151416#ifdef LIBDECOR_ENABLED1417void WaylandThread::libdecor_on_error(struct libdecor *context, enum libdecor_error error, const char *message) {1418ERR_PRINT(vformat("libdecor error %d: %s", error, message));1419}14201421// NOTE: This is pretty much a reimplementation of _xdg_surface_on_configure1422// and _xdg_toplevel_on_configure. Libdecor really likes wrapping everything,1423// forcing us to do stuff like this.1424void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *user_data) {1425WindowState *ws = (WindowState *)user_data;1426ERR_FAIL_NULL(ws);14271428int width = 0;1429int height = 0;14301431ws->pending_libdecor_configuration = configuration;14321433if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {1434// The configuration doesn't have a size. We'll use the one already set in the window.1435width = ws->rect.size.width;1436height = ws->rect.size.height;1437}14381439ERR_FAIL_COND_MSG(width == 0 || height == 0, "Window has invalid size.");14401441libdecor_window_state window_state = LIBDECOR_WINDOW_STATE_NONE;14421443// Expect the window to be in a plain state. It will get properly set if the1444// compositor reports otherwise below.1445ws->mode = DisplayServer::WINDOW_MODE_WINDOWED;1446ws->suspended = false;14471448if (libdecor_configuration_get_window_state(configuration, &window_state)) {1449if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) {1450ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED;1451}14521453if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) {1454ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN;1455}14561457if (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) {1458ws->suspended = true;1459}1460}14611462window_state_update_size(ws, width, height);14631464DEBUG_LOG_WAYLAND_THREAD(vformat("libdecor frame on configure rect %s", ws->rect));1465}14661467void WaylandThread::libdecor_frame_on_close(struct libdecor_frame *frame, void *user_data) {1468WindowState *ws = (WindowState *)user_data;1469ERR_FAIL_NULL(ws);14701471Ref<WindowEventMessage> winevent_msg;1472winevent_msg.instantiate();1473winevent_msg->id = ws->id;1474winevent_msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST;14751476ws->wayland_thread->push_message(winevent_msg);14771478DEBUG_LOG_WAYLAND_THREAD("libdecor frame on close");1479}14801481void WaylandThread::libdecor_frame_on_commit(struct libdecor_frame *frame, void *user_data) {1482// We're skipping this as we don't really care about libdecor's commit for1483// atomicity reasons. See `_frame_wl_callback_on_done` for more info.14841485DEBUG_LOG_WAYLAND_THREAD("libdecor frame on commit");1486}14871488void WaylandThread::libdecor_frame_on_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data) {1489}1490#endif // LIBDECOR_ENABLED14911492void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) {1493SeatState *ss = (SeatState *)data;14941495ERR_FAIL_NULL(ss);14961497// TODO: Handle touch.14981499// Pointer handling.1500if (capabilities & WL_SEAT_CAPABILITY_POINTER) {1501if (!ss->wl_pointer) {1502ss->cursor_surface = wl_compositor_create_surface(ss->registry->wl_compositor);1503wl_surface_commit(ss->cursor_surface);15041505ss->wl_pointer = wl_seat_get_pointer(wl_seat);1506wl_pointer_add_listener(ss->wl_pointer, &wl_pointer_listener, ss);15071508if (ss->registry->wp_cursor_shape_manager) {1509ss->wp_cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(ss->registry->wp_cursor_shape_manager, ss->wl_pointer);1510}15111512if (ss->registry->wp_relative_pointer_manager) {1513ss->wp_relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(ss->registry->wp_relative_pointer_manager, ss->wl_pointer);1514zwp_relative_pointer_v1_add_listener(ss->wp_relative_pointer, &wp_relative_pointer_listener, ss);1515}15161517if (ss->registry->wp_pointer_gestures) {1518ss->wp_pointer_gesture_pinch = zwp_pointer_gestures_v1_get_pinch_gesture(ss->registry->wp_pointer_gestures, ss->wl_pointer);1519zwp_pointer_gesture_pinch_v1_add_listener(ss->wp_pointer_gesture_pinch, &wp_pointer_gesture_pinch_listener, ss);1520}15211522// TODO: Constrain new pointers if the global mouse mode is constrained.1523}1524} else {1525if (ss->cursor_frame_callback) {1526// Just in case. I got bitten by weird race-like conditions already.1527wl_callback_set_user_data(ss->cursor_frame_callback, nullptr);15281529wl_callback_destroy(ss->cursor_frame_callback);1530ss->cursor_frame_callback = nullptr;1531}15321533if (ss->cursor_surface) {1534wl_surface_destroy(ss->cursor_surface);1535ss->cursor_surface = nullptr;1536}15371538if (ss->wl_pointer) {1539wl_pointer_destroy(ss->wl_pointer);1540ss->wl_pointer = nullptr;1541}15421543if (ss->wp_cursor_shape_device) {1544wp_cursor_shape_device_v1_destroy(ss->wp_cursor_shape_device);1545ss->wp_cursor_shape_device = nullptr;1546}15471548if (ss->wp_relative_pointer) {1549zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);1550ss->wp_relative_pointer = nullptr;1551}15521553if (ss->wp_confined_pointer) {1554zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);1555ss->wp_confined_pointer = nullptr;1556}15571558if (ss->wp_locked_pointer) {1559zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);1560ss->wp_locked_pointer = nullptr;1561}1562}15631564// Keyboard handling.1565if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {1566if (!ss->wl_keyboard) {1567ss->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);1568ERR_FAIL_NULL(ss->xkb_context);15691570ss->wl_keyboard = wl_seat_get_keyboard(wl_seat);1571wl_keyboard_add_listener(ss->wl_keyboard, &wl_keyboard_listener, ss);1572}1573} else {1574if (ss->xkb_context) {1575xkb_context_unref(ss->xkb_context);1576ss->xkb_context = nullptr;1577}15781579if (ss->wl_keyboard) {1580wl_keyboard_destroy(ss->wl_keyboard);1581ss->wl_keyboard = nullptr;1582}1583}1584}15851586void WaylandThread::_wl_seat_on_name(void *data, struct wl_seat *wl_seat, const char *name) {1587}15881589void WaylandThread::_cursor_frame_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t time_ms) {1590wl_callback_destroy(wl_callback);15911592SeatState *ss = (SeatState *)data;1593ERR_FAIL_NULL(ss);15941595ss->cursor_frame_callback = nullptr;15961597ss->cursor_time_ms = time_ms;15981599seat_state_update_cursor(ss);1600}16011602void WaylandThread::_wl_pointer_on_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {1603WindowState *ws = wl_surface_get_window_state(surface);1604if (!ws) {1605return;1606}16071608SeatState *ss = (SeatState *)data;1609ERR_FAIL_NULL(ss);16101611ERR_FAIL_NULL(ss->cursor_surface);16121613PointerData &pd = ss->pointer_data_buffer;16141615ss->pointer_enter_serial = serial;1616pd.pointed_id = ws->id;1617pd.last_pointed_id = ws->id;1618pd.position.x = wl_fixed_to_double(surface_x);1619pd.position.y = wl_fixed_to_double(surface_y);16201621seat_state_update_cursor(ss);16221623DEBUG_LOG_WAYLAND_THREAD(vformat("Pointer entered window %d.", ws->id));1624}16251626void WaylandThread::_wl_pointer_on_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) {1627// NOTE: `surface` will probably be null when the surface is destroyed.1628// See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/3661629// See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/46516301631SeatState *ss = (SeatState *)data;1632ERR_FAIL_NULL(ss);16331634PointerData &pd = ss->pointer_data_buffer;16351636if (pd.pointed_id == DisplayServer::INVALID_WINDOW_ID) {1637// We're probably on a decoration or some other third-party thing.1638return;1639}16401641DisplayServer::WindowID id = pd.pointed_id;16421643pd.pointed_id = DisplayServer::INVALID_WINDOW_ID;1644pd.pressed_button_mask.clear();16451646DEBUG_LOG_WAYLAND_THREAD(vformat("Pointer left window %d.", id));1647}16481649void WaylandThread::_wl_pointer_on_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {1650SeatState *ss = (SeatState *)data;1651ERR_FAIL_NULL(ss);16521653PointerData &pd = ss->pointer_data_buffer;16541655pd.position.x = wl_fixed_to_double(surface_x);1656pd.position.y = wl_fixed_to_double(surface_y);16571658pd.motion_time = time;1659}16601661void WaylandThread::_wl_pointer_on_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {1662SeatState *ss = (SeatState *)data;1663ERR_FAIL_NULL(ss);16641665PointerData &pd = ss->pointer_data_buffer;16661667MouseButton button_pressed = MouseButton::NONE;16681669switch (button) {1670case BTN_LEFT:1671button_pressed = MouseButton::LEFT;1672break;16731674case BTN_MIDDLE:1675button_pressed = MouseButton::MIDDLE;1676break;16771678case BTN_RIGHT:1679button_pressed = MouseButton::RIGHT;1680break;16811682case BTN_EXTRA:1683button_pressed = MouseButton::MB_XBUTTON1;1684break;16851686case BTN_SIDE:1687button_pressed = MouseButton::MB_XBUTTON2;1688break;16891690default: {1691}1692}16931694MouseButtonMask mask = mouse_button_to_mask(button_pressed);16951696if (state & WL_POINTER_BUTTON_STATE_PRESSED) {1697pd.pressed_button_mask.set_flag(mask);1698pd.last_button_pressed = button_pressed;1699pd.double_click_begun = true;1700} else {1701pd.pressed_button_mask.clear_flag(mask);1702}17031704pd.button_time = time;1705pd.button_serial = serial;1706}17071708void WaylandThread::_wl_pointer_on_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {1709SeatState *ss = (SeatState *)data;1710ERR_FAIL_NULL(ss);17111712PointerData &pd = ss->pointer_data_buffer;17131714switch (axis) {1715case WL_POINTER_AXIS_VERTICAL_SCROLL: {1716pd.scroll_vector.y = wl_fixed_to_double(value);1717} break;17181719case WL_POINTER_AXIS_HORIZONTAL_SCROLL: {1720pd.scroll_vector.x = wl_fixed_to_double(value);1721} break;1722}17231724pd.button_time = time;1725}17261727void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_pointer) {1728SeatState *ss = (SeatState *)data;1729ERR_FAIL_NULL(ss);17301731WaylandThread *wayland_thread = ss->wayland_thread;1732ERR_FAIL_NULL(wayland_thread);17331734PointerData &old_pd = ss->pointer_data;1735PointerData &pd = ss->pointer_data_buffer;17361737if (pd.pointed_id != old_pd.pointed_id) {1738if (old_pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {1739Ref<WindowEventMessage> msg;1740msg.instantiate();1741msg->id = old_pd.pointed_id;1742msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;17431744wayland_thread->push_message(msg);1745}17461747if (pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {1748Ref<WindowEventMessage> msg;1749msg.instantiate();1750msg->id = pd.pointed_id;1751msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;17521753wayland_thread->push_message(msg);1754}1755}17561757WindowState *ws = nullptr;17581759// NOTE: At least on sway, with wl_pointer version 5 or greater,1760// wl_pointer::leave might be emitted with other events (like1761// wl_pointer::button) within the same wl_pointer::frame. Because of this, we1762// need to account for when the currently pointed window might be invalid1763// (third-party or even none) and fall back to the old one.1764if (pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {1765ws = ss->wayland_thread->window_get_state(pd.pointed_id);1766ERR_FAIL_NULL(ws);1767} else if (old_pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {1768ws = ss->wayland_thread->window_get_state(old_pd.pointed_id);1769ERR_FAIL_NULL(ws);1770}17711772if (ws == nullptr) {1773// We're probably on a decoration or some other third-party thing. Let's1774// "commit" the data and call it a day.1775old_pd = pd;1776return;1777}17781779double scale = window_state_get_scale_factor(ws);17801781wayland_thread->_set_current_seat(ss->wl_seat);17821783if (old_pd.motion_time != pd.motion_time || old_pd.relative_motion_time != pd.relative_motion_time) {1784Ref<InputEventMouseMotion> mm;1785mm.instantiate();17861787// Set all pressed modifiers.1788mm->set_shift_pressed(ss->shift_pressed);1789mm->set_ctrl_pressed(ss->ctrl_pressed);1790mm->set_alt_pressed(ss->alt_pressed);1791mm->set_meta_pressed(ss->meta_pressed);17921793mm->set_window_id(ws->id);17941795mm->set_button_mask(pd.pressed_button_mask);17961797mm->set_position(pd.position * scale);1798mm->set_global_position(pd.position * scale);17991800Vector2 pos_delta = (pd.position - old_pd.position) * scale;18011802if (old_pd.relative_motion_time != pd.relative_motion_time) {1803uint32_t time_delta = pd.relative_motion_time - old_pd.relative_motion_time;18041805mm->set_relative(pd.relative_motion * scale);1806mm->set_velocity((Vector2)pos_delta / time_delta);1807} else {1808// The spec includes the possibility of having motion events without an1809// associated relative motion event. If that's the case, fallback to a1810// simple delta of the position. The captured mouse won't report the1811// relative speed anymore though.1812uint32_t time_delta = pd.motion_time - old_pd.motion_time;18131814mm->set_relative(pos_delta);1815mm->set_velocity((Vector2)pos_delta / time_delta);1816}1817mm->set_relative_screen_position(mm->get_relative());1818mm->set_screen_velocity(mm->get_velocity());18191820Ref<InputEventMessage> msg;1821msg.instantiate();18221823msg->event = mm;18241825wayland_thread->push_message(msg);1826}18271828if (pd.discrete_scroll_vector_120 - old_pd.discrete_scroll_vector_120 != Vector2i()) {1829// This is a discrete scroll (eg. from a scroll wheel), so we'll just emit1830// scroll wheel buttons.1831if (pd.scroll_vector.y != 0) {1832MouseButton button = pd.scroll_vector.y > 0 ? MouseButton::WHEEL_DOWN : MouseButton::WHEEL_UP;1833pd.pressed_button_mask.set_flag(mouse_button_to_mask(button));1834}18351836if (pd.scroll_vector.x != 0) {1837MouseButton button = pd.scroll_vector.x > 0 ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT;1838pd.pressed_button_mask.set_flag(mouse_button_to_mask(button));1839}1840} else {1841if (pd.scroll_vector - old_pd.scroll_vector != Vector2()) {1842// This is a continuous scroll, so we'll emit a pan gesture.1843Ref<InputEventPanGesture> pg;1844pg.instantiate();18451846// Set all pressed modifiers.1847pg->set_shift_pressed(ss->shift_pressed);1848pg->set_ctrl_pressed(ss->ctrl_pressed);1849pg->set_alt_pressed(ss->alt_pressed);1850pg->set_meta_pressed(ss->meta_pressed);18511852pg->set_position(pd.position * scale);18531854pg->set_window_id(ws->id);18551856pg->set_delta(pd.scroll_vector);18571858Ref<InputEventMessage> msg;1859msg.instantiate();18601861msg->event = pg;18621863wayland_thread->push_message(msg);1864}1865}18661867if (old_pd.pressed_button_mask != pd.pressed_button_mask) {1868BitField<MouseButtonMask> pressed_mask_delta = old_pd.pressed_button_mask.get_different(pd.pressed_button_mask);18691870const MouseButton buttons_to_test[] = {1871MouseButton::LEFT,1872MouseButton::MIDDLE,1873MouseButton::RIGHT,1874MouseButton::WHEEL_UP,1875MouseButton::WHEEL_DOWN,1876MouseButton::WHEEL_LEFT,1877MouseButton::WHEEL_RIGHT,1878MouseButton::MB_XBUTTON1,1879MouseButton::MB_XBUTTON2,1880};18811882for (MouseButton test_button : buttons_to_test) {1883MouseButtonMask test_button_mask = mouse_button_to_mask(test_button);1884if (pressed_mask_delta.has_flag(test_button_mask)) {1885Ref<InputEventMouseButton> mb;1886mb.instantiate();18871888// Set all pressed modifiers.1889mb->set_shift_pressed(ss->shift_pressed);1890mb->set_ctrl_pressed(ss->ctrl_pressed);1891mb->set_alt_pressed(ss->alt_pressed);1892mb->set_meta_pressed(ss->meta_pressed);18931894mb->set_window_id(ws->id);1895mb->set_position(pd.position * scale);1896mb->set_global_position(pd.position * scale);18971898if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN) {1899// If this is a discrete scroll, specify how many "clicks" it did for this1900// pointer frame.1901mb->set_factor(Math::abs(pd.discrete_scroll_vector_120.y / (float)120));1902}19031904if (test_button == MouseButton::WHEEL_RIGHT || test_button == MouseButton::WHEEL_LEFT) {1905// If this is a discrete scroll, specify how many "clicks" it did for this1906// pointer frame.1907mb->set_factor(std::abs(pd.discrete_scroll_vector_120.x / (float)120));1908}19091910mb->set_button_mask(pd.pressed_button_mask);19111912mb->set_button_index(test_button);1913mb->set_pressed(pd.pressed_button_mask.has_flag(test_button_mask));19141915// We have to set the last position pressed here as we can't take for1916// granted what the individual events might have seen due to them not having1917// a guaranteed order.1918if (mb->is_pressed()) {1919pd.last_pressed_position = pd.position;1920}19211922if (old_pd.double_click_begun && mb->is_pressed() && pd.last_button_pressed == old_pd.last_button_pressed && (pd.button_time - old_pd.button_time) < 400 && Vector2(old_pd.last_pressed_position * scale).distance_to(Vector2(pd.last_pressed_position * scale)) < 5) {1923pd.double_click_begun = false;1924mb->set_double_click(true);1925}19261927Ref<InputEventMessage> msg;1928msg.instantiate();19291930msg->event = mb;19311932wayland_thread->push_message(msg);19331934// Send an event resetting immediately the wheel key.1935// Wayland specification defines axis_stop events as optional and says to1936// treat all axis events as unterminated. As such, we have to manually do1937// it ourselves.1938if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN || test_button == MouseButton::WHEEL_LEFT || test_button == MouseButton::WHEEL_RIGHT) {1939// FIXME: This is ugly, I can't find a clean way to clone an InputEvent.1940// This works for now, despite being horrible.1941Ref<InputEventMouseButton> wh_up;1942wh_up.instantiate();19431944wh_up->set_window_id(ws->id);1945wh_up->set_position(pd.position * scale);1946wh_up->set_global_position(pd.position * scale);19471948// We have to unset the button to avoid it getting stuck.1949pd.pressed_button_mask.clear_flag(test_button_mask);1950wh_up->set_button_mask(pd.pressed_button_mask);19511952wh_up->set_button_index(test_button);1953wh_up->set_pressed(false);19541955Ref<InputEventMessage> msg_up;1956msg_up.instantiate();1957msg_up->event = wh_up;1958wayland_thread->push_message(msg_up);1959}1960}1961}1962}19631964// Reset the scroll vectors as we already handled them.1965pd.scroll_vector = Vector2();1966pd.discrete_scroll_vector_120 = Vector2i();19671968// Update the data all getters read. Wayland's specification requires us to do1969// this, since all pointer actions are sent in individual events.1970old_pd = pd;1971}19721973void WaylandThread::_wl_pointer_on_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source) {1974SeatState *ss = (SeatState *)data;1975ERR_FAIL_NULL(ss);19761977ss->pointer_data_buffer.scroll_type = axis_source;1978}19791980void WaylandThread::_wl_pointer_on_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) {1981}19821983// NOTE: This event is deprecated since version 8 and superseded by1984// `wl_pointer::axis_value120`. This thus converts the data to its1985// fraction-of-120 format.1986void WaylandThread::_wl_pointer_on_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) {1987SeatState *ss = (SeatState *)data;1988ERR_FAIL_NULL(ss);19891990PointerData &pd = ss->pointer_data_buffer;19911992// NOTE: We can allow ourselves to not accumulate this data (and thus just1993// assign it) as the spec guarantees only one event per axis type.19941995if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {1996pd.discrete_scroll_vector_120.y = discrete * 120;1997}19981999if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {2000pd.discrete_scroll_vector_120.x = discrete * 120;2001}2002}20032004// Supersedes `wl_pointer::axis_discrete` Since version 8.2005void WaylandThread::_wl_pointer_on_axis_value120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) {2006SeatState *ss = (SeatState *)data;2007ERR_FAIL_NULL(ss);20082009PointerData &pd = ss->pointer_data_buffer;20102011if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {2012pd.discrete_scroll_vector_120.y += value120;2013}20142015if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {2016pd.discrete_scroll_vector_120.x += value120;2017}2018}20192020// TODO: Add support to this event.2021void WaylandThread::_wl_pointer_on_axis_relative_direction(void *data, struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction) {2022}20232024void WaylandThread::_wl_keyboard_on_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) {2025ERR_FAIL_COND_MSG(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, "Unsupported keymap format announced from the Wayland compositor.");20262027SeatState *ss = (SeatState *)data;2028ERR_FAIL_NULL(ss);20292030if (ss->keymap_buffer) {2031// We have already a mapped buffer, so we unmap it. There's no need to reset2032// its pointer or size, as we're gonna set them below.2033munmap((void *)ss->keymap_buffer, ss->keymap_buffer_size);2034ss->keymap_buffer = nullptr;2035}20362037ss->keymap_buffer = (const char *)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);2038ss->keymap_buffer_size = size;20392040xkb_keymap_unref(ss->xkb_keymap);2041ss->xkb_keymap = xkb_keymap_new_from_string(ss->xkb_context, ss->keymap_buffer,2042XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);20432044xkb_state_unref(ss->xkb_state);2045ss->xkb_state = xkb_state_new(ss->xkb_keymap);2046}20472048void WaylandThread::_wl_keyboard_on_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {2049WindowState *ws = wl_surface_get_window_state(surface);2050if (!ws) {2051return;2052}20532054SeatState *ss = (SeatState *)data;2055ERR_FAIL_NULL(ss);20562057WaylandThread *wayland_thread = ss->wayland_thread;2058ERR_FAIL_NULL(wayland_thread);20592060ss->focused_id = ws->id;20612062wayland_thread->_set_current_seat(ss->wl_seat);20632064Ref<WindowEventMessage> msg;2065msg.instantiate();2066msg->id = ws->id;2067msg->event = DisplayServer::WINDOW_EVENT_FOCUS_IN;2068wayland_thread->push_message(msg);20692070DEBUG_LOG_WAYLAND_THREAD(vformat("Keyboard focused window %d.", ws->id));2071}20722073void WaylandThread::_wl_keyboard_on_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) {2074// NOTE: `surface` will probably be null when the surface is destroyed.2075// See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/3662076// See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/46520772078if (surface && !wl_proxy_is_godot((struct wl_proxy *)surface)) {2079return;2080}20812082SeatState *ss = (SeatState *)data;2083ERR_FAIL_NULL(ss);20842085WaylandThread *wayland_thread = ss->wayland_thread;2086ERR_FAIL_NULL(wayland_thread);20872088ss->repeating_keycode = XKB_KEYCODE_INVALID;20892090if (ss->focused_id == DisplayServer::INVALID_WINDOW_ID) {2091// We're probably on a decoration or some other third-party thing.2092return;2093}20942095WindowState *ws = wayland_thread->window_get_state(ss->focused_id);2096ERR_FAIL_NULL(ws);20972098ss->focused_id = DisplayServer::INVALID_WINDOW_ID;20992100Ref<WindowEventMessage> msg;2101msg.instantiate();2102msg->id = ws->id;2103msg->event = DisplayServer::WINDOW_EVENT_FOCUS_OUT;2104wayland_thread->push_message(msg);21052106DEBUG_LOG_WAYLAND_THREAD(vformat("Keyboard unfocused window %d.", ws->id));2107}21082109void WaylandThread::_wl_keyboard_on_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {2110SeatState *ss = (SeatState *)data;2111ERR_FAIL_NULL(ss);21122113if (ss->focused_id == DisplayServer::INVALID_WINDOW_ID) {2114return;2115}21162117WaylandThread *wayland_thread = ss->wayland_thread;2118ERR_FAIL_NULL(wayland_thread);21192120// We have to add 8 to the scancode to get an XKB-compatible keycode.2121xkb_keycode_t xkb_keycode = key + 8;21222123bool pressed = state & WL_KEYBOARD_KEY_STATE_PRESSED;21242125if (pressed) {2126if (xkb_keymap_key_repeats(ss->xkb_keymap, xkb_keycode)) {2127ss->last_repeat_start_msec = OS::get_singleton()->get_ticks_msec();2128ss->repeating_keycode = xkb_keycode;2129}21302131ss->last_key_pressed_serial = serial;2132} else if (ss->repeating_keycode == xkb_keycode) {2133ss->repeating_keycode = XKB_KEYCODE_INVALID;2134}21352136Ref<InputEventKey> k = _seat_state_get_key_event(ss, xkb_keycode, pressed);2137if (k.is_null()) {2138return;2139}21402141Ref<InputEventKey> uk = _seat_state_get_unstuck_key_event(ss, xkb_keycode, pressed, k->get_keycode());2142if (uk.is_valid()) {2143Ref<InputEventMessage> u_msg;2144u_msg.instantiate();2145u_msg->event = uk;2146wayland_thread->push_message(u_msg);2147}21482149Ref<InputEventMessage> msg;2150msg.instantiate();2151msg->event = k;2152wayland_thread->push_message(msg);2153}21542155void WaylandThread::_wl_keyboard_on_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {2156SeatState *ss = (SeatState *)data;2157ERR_FAIL_NULL(ss);21582159xkb_state_update_mask(ss->xkb_state, mods_depressed, mods_latched, mods_locked, ss->current_layout_index, ss->current_layout_index, group);21602161ss->shift_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED);2162ss->ctrl_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED);2163ss->alt_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_DEPRESSED);2164ss->meta_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_DEPRESSED);21652166ss->current_layout_index = group;2167}21682169void WaylandThread::_wl_keyboard_on_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) {2170SeatState *ss = (SeatState *)data;2171ERR_FAIL_NULL(ss);21722173ss->repeat_key_delay_msec = 1000 / rate;2174ss->repeat_start_delay_msec = delay;2175}21762177// NOTE: Don't forget to `memfree` the offer's state.2178void WaylandThread::_wl_data_device_on_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {2179wl_proxy_tag_godot((struct wl_proxy *)id);2180wl_data_offer_add_listener(id, &wl_data_offer_listener, memnew(OfferState));2181}21822183void WaylandThread::_wl_data_device_on_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {2184WindowState *ws = wl_surface_get_window_state(surface);2185if (!ws) {2186return;2187}21882189SeatState *ss = (SeatState *)data;2190ERR_FAIL_NULL(ss);21912192ss->dnd_id = ws->id;21932194ss->dnd_enter_serial = serial;2195ss->wl_data_offer_dnd = id;21962197// Godot only supports DnD file copying for now.2198wl_data_offer_accept(id, serial, "text/uri-list");2199wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);2200}22012202void WaylandThread::_wl_data_device_on_leave(void *data, struct wl_data_device *wl_data_device) {2203SeatState *ss = (SeatState *)data;2204ERR_FAIL_NULL(ss);22052206if (ss->wl_data_offer_dnd) {2207memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd));2208wl_data_offer_destroy(ss->wl_data_offer_dnd);2209ss->wl_data_offer_dnd = nullptr;2210ss->dnd_id = DisplayServer::INVALID_WINDOW_ID;2211}2212}22132214void WaylandThread::_wl_data_device_on_motion(void *data, struct wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) {2215}22162217void WaylandThread::_wl_data_device_on_drop(void *data, struct wl_data_device *wl_data_device) {2218SeatState *ss = (SeatState *)data;2219ERR_FAIL_NULL(ss);22202221WaylandThread *wayland_thread = ss->wayland_thread;2222ERR_FAIL_NULL(wayland_thread);22232224OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_dnd);2225ERR_FAIL_NULL(os);22262227if (os) {2228Ref<DropFilesEventMessage> msg;2229msg.instantiate();2230msg->id = ss->dnd_id;22312232Vector<uint8_t> list_data = _wl_data_offer_read(wayland_thread->wl_display, "text/uri-list", ss->wl_data_offer_dnd);22332234msg->files = String::utf8((const char *)list_data.ptr(), list_data.size()).split("\r\n", false);2235for (int i = 0; i < msg->files.size(); i++) {2236msg->files.write[i] = msg->files[i].replace("file://", "").uri_file_decode();2237}22382239wayland_thread->push_message(msg);22402241wl_data_offer_finish(ss->wl_data_offer_dnd);2242}22432244memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd));2245wl_data_offer_destroy(ss->wl_data_offer_dnd);2246ss->wl_data_offer_dnd = nullptr;2247ss->dnd_id = DisplayServer::INVALID_WINDOW_ID;2248}22492250void WaylandThread::_wl_data_device_on_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {2251SeatState *ss = (SeatState *)data;2252ERR_FAIL_NULL(ss);22532254if (ss->wl_data_offer_selection) {2255memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_selection));2256wl_data_offer_destroy(ss->wl_data_offer_selection);2257}22582259ss->wl_data_offer_selection = id;2260}22612262void WaylandThread::_wl_data_offer_on_offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type) {2263OfferState *os = (OfferState *)data;2264ERR_FAIL_NULL(os);22652266if (os) {2267os->mime_types.insert(String::utf8(mime_type));2268}2269}22702271void WaylandThread::_wl_data_offer_on_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions) {2272}22732274void WaylandThread::_wl_data_offer_on_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) {2275}22762277void WaylandThread::_wl_data_source_on_target(void *data, struct wl_data_source *wl_data_source, const char *mime_type) {2278}22792280void WaylandThread::_wl_data_source_on_send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd) {2281SeatState *ss = (SeatState *)data;2282ERR_FAIL_NULL(ss);22832284Vector<uint8_t> *data_to_send = nullptr;22852286if (wl_data_source == ss->wl_data_source_selection) {2287data_to_send = &ss->selection_data;2288DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested selection.");2289}22902291if (data_to_send) {2292ssize_t written_bytes = 0;22932294bool valid_mime = false;22952296if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) {2297valid_mime = true;2298} else if (strcmp(mime_type, "text/plain") == 0) {2299valid_mime = true;2300}23012302if (valid_mime) {2303written_bytes = write(fd, data_to_send->ptr(), data_to_send->size());2304}23052306if (written_bytes > 0) {2307DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes));2308} else if (written_bytes == 0) {2309DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent.");2310} else {2311ERR_PRINT(vformat("Clipboard: write error %d.", errno));2312}2313}23142315close(fd);2316}23172318void WaylandThread::_wl_data_source_on_cancelled(void *data, struct wl_data_source *wl_data_source) {2319SeatState *ss = (SeatState *)data;2320ERR_FAIL_NULL(ss);23212322wl_data_source_destroy(wl_data_source);23232324if (wl_data_source == ss->wl_data_source_selection) {2325ss->wl_data_source_selection = nullptr;2326ss->selection_data.clear();23272328DEBUG_LOG_WAYLAND_THREAD("Clipboard: selection set by another program.");2329return;2330}2331}23322333void WaylandThread::_wl_data_source_on_dnd_drop_performed(void *data, struct wl_data_source *wl_data_source) {2334}23352336void WaylandThread::_wl_data_source_on_dnd_finished(void *data, struct wl_data_source *wl_data_source) {2337}23382339void WaylandThread::_wl_data_source_on_action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action) {2340}23412342void WaylandThread::_wp_fractional_scale_on_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale) {2343WindowState *ws = (WindowState *)data;2344ERR_FAIL_NULL(ws);23452346ws->preferred_fractional_scale = (double)scale / 120;23472348window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height);2349}23502351void WaylandThread::_wp_relative_pointer_on_relative_motion(void *data, struct zwp_relative_pointer_v1 *wp_relative_pointer, uint32_t uptime_hi, uint32_t uptime_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) {2352SeatState *ss = (SeatState *)data;2353ERR_FAIL_NULL(ss);23542355PointerData &pd = ss->pointer_data_buffer;23562357pd.relative_motion.x = wl_fixed_to_double(dx);2358pd.relative_motion.y = wl_fixed_to_double(dy);23592360pd.relative_motion_time = uptime_lo;2361}23622363void WaylandThread::_wp_pointer_gesture_pinch_on_begin(void *data, struct zwp_pointer_gesture_pinch_v1 *wp_pointer_gesture_pinch_v1, uint32_t serial, uint32_t time, struct wl_surface *surface, uint32_t fingers) {2364SeatState *ss = (SeatState *)data;2365ERR_FAIL_NULL(ss);23662367if (fingers == 2) {2368ss->old_pinch_scale = wl_fixed_from_int(1);2369ss->active_gesture = Gesture::MAGNIFY;2370}2371}23722373void WaylandThread::_wp_pointer_gesture_pinch_on_update(void *data, struct zwp_pointer_gesture_pinch_v1 *wp_pointer_gesture_pinch_v1, uint32_t time, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t scale, wl_fixed_t rotation) {2374SeatState *ss = (SeatState *)data;2375ERR_FAIL_NULL(ss);23762377// NOTE: From what I can tell, this and all other pointer gestures are separate2378// from the "frame" mechanism of regular pointers. Thus, let's just assume we2379// can read from the "committed" state.2380const PointerData &pd = ss->pointer_data;23812382WaylandThread *wayland_thread = ss->wayland_thread;2383ERR_FAIL_NULL(wayland_thread);23842385WindowState *ws = wayland_thread->window_get_state(pd.pointed_id);2386ERR_FAIL_NULL(ws);23872388double win_scale = window_state_get_scale_factor(ws);23892390if (ss->active_gesture == Gesture::MAGNIFY) {2391Ref<InputEventMagnifyGesture> mg;2392mg.instantiate();23932394mg->set_window_id(pd.pointed_id);23952396if (ws) {2397mg->set_window_id(ws->id);2398}23992400// Set all pressed modifiers.2401mg->set_shift_pressed(ss->shift_pressed);2402mg->set_ctrl_pressed(ss->ctrl_pressed);2403mg->set_alt_pressed(ss->alt_pressed);2404mg->set_meta_pressed(ss->meta_pressed);24052406mg->set_position(pd.position * win_scale);24072408wl_fixed_t scale_delta = scale - ss->old_pinch_scale;2409mg->set_factor(1 + wl_fixed_to_double(scale_delta));24102411Ref<InputEventMessage> magnify_msg;2412magnify_msg.instantiate();2413magnify_msg->event = mg;24142415// Since Wayland allows only one gesture at a time and godot instead expects2416// both of them, we'll have to create two separate input events: one for2417// magnification and one for panning.24182419Ref<InputEventPanGesture> pg;2420pg.instantiate();24212422// Set all pressed modifiers.2423pg->set_shift_pressed(ss->shift_pressed);2424pg->set_ctrl_pressed(ss->ctrl_pressed);2425pg->set_alt_pressed(ss->alt_pressed);2426pg->set_meta_pressed(ss->meta_pressed);24272428pg->set_position(pd.position * win_scale);2429pg->set_delta(Vector2(wl_fixed_to_double(dx), wl_fixed_to_double(dy)));24302431Ref<InputEventMessage> pan_msg;2432pan_msg.instantiate();2433pan_msg->event = pg;24342435wayland_thread->push_message(magnify_msg);2436wayland_thread->push_message(pan_msg);24372438ss->old_pinch_scale = scale;2439}2440}24412442void WaylandThread::_wp_pointer_gesture_pinch_on_end(void *data, struct zwp_pointer_gesture_pinch_v1 *wp_pointer_gesture_pinch_v1, uint32_t serial, uint32_t time, int32_t cancelled) {2443SeatState *ss = (SeatState *)data;2444ERR_FAIL_NULL(ss);24452446ss->active_gesture = Gesture::NONE;2447}24482449// NOTE: Don't forget to `memfree` the offer's state.2450void WaylandThread::_wp_primary_selection_device_on_data_offer(void *data, struct zwp_primary_selection_device_v1 *wp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *offer) {2451wl_proxy_tag_godot((struct wl_proxy *)offer);2452zwp_primary_selection_offer_v1_add_listener(offer, &wp_primary_selection_offer_listener, memnew(OfferState));2453}24542455void WaylandThread::_wp_primary_selection_device_on_selection(void *data, struct zwp_primary_selection_device_v1 *wp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *id) {2456SeatState *ss = (SeatState *)data;2457ERR_FAIL_NULL(ss);24582459if (ss->wp_primary_selection_offer) {2460memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer));2461zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer);2462}24632464ss->wp_primary_selection_offer = id;2465}24662467void WaylandThread::_wp_primary_selection_offer_on_offer(void *data, struct zwp_primary_selection_offer_v1 *wp_primary_selection_offer_v1, const char *mime_type) {2468OfferState *os = (OfferState *)data;2469ERR_FAIL_NULL(os);24702471if (os) {2472os->mime_types.insert(String::utf8(mime_type));2473}2474}24752476void WaylandThread::_wp_primary_selection_source_on_send(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1, const char *mime_type, int32_t fd) {2477SeatState *ss = (SeatState *)data;2478ERR_FAIL_NULL(ss);24792480Vector<uint8_t> *data_to_send = nullptr;24812482if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) {2483data_to_send = &ss->primary_data;2484DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested primary selection.");2485}24862487if (data_to_send) {2488ssize_t written_bytes = 0;24892490if (strcmp(mime_type, "text/plain") == 0) {2491written_bytes = write(fd, data_to_send->ptr(), data_to_send->size());2492}24932494if (written_bytes > 0) {2495DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes));2496} else if (written_bytes == 0) {2497DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent.");2498} else {2499ERR_PRINT(vformat("Clipboard: write error %d.", errno));2500}2501}25022503close(fd);2504}25052506void WaylandThread::_wp_primary_selection_source_on_cancelled(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1) {2507SeatState *ss = (SeatState *)data;2508ERR_FAIL_NULL(ss);25092510if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) {2511zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source);2512ss->wp_primary_selection_source = nullptr;25132514ss->primary_data.clear();25152516DEBUG_LOG_WAYLAND_THREAD("Clipboard: primary selection set by another program.");2517return;2518}2519}25202521void WaylandThread::_wp_tablet_seat_on_tablet_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_v2 *id) {2522}25232524void WaylandThread::_wp_tablet_seat_on_tool_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_tool_v2 *id) {2525SeatState *ss = (SeatState *)data;2526ERR_FAIL_NULL(ss);25272528TabletToolState *state = memnew(TabletToolState);2529state->wl_seat = ss->wl_seat;25302531wl_proxy_tag_godot((struct wl_proxy *)id);2532zwp_tablet_tool_v2_add_listener(id, &wp_tablet_tool_listener, state);2533ss->tablet_tools.push_back(id);2534}25352536void WaylandThread::_wp_tablet_seat_on_pad_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_pad_v2 *id) {2537}25382539void WaylandThread::_wp_tablet_tool_on_type(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t tool_type) {2540TabletToolState *state = wp_tablet_tool_get_state(wp_tablet_tool_v2);25412542if (state && tool_type == ZWP_TABLET_TOOL_V2_TYPE_ERASER) {2543state->is_eraser = true;2544}2545}25462547void WaylandThread::_wp_tablet_tool_on_hardware_serial(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t hardware_serial_hi, uint32_t hardware_serial_lo) {2548}25492550void WaylandThread::_wp_tablet_tool_on_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t hardware_id_hi, uint32_t hardware_id_lo) {2551}25522553void WaylandThread::_wp_tablet_tool_on_capability(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t capability) {2554}25552556void WaylandThread::_wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {2557}25582559void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {2560TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2561if (!ts) {2562return;2563}25642565SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);2566if (!ss) {2567return;2568}25692570List<struct zwp_tablet_tool_v2 *>::Element *E = ss->tablet_tools.find(wp_tablet_tool_v2);25712572if (E && E->get()) {2573struct zwp_tablet_tool_v2 *tool = E->get();2574TabletToolState *state = wp_tablet_tool_get_state(tool);2575if (state) {2576memdelete(state);2577}25782579zwp_tablet_tool_v2_destroy(tool);2580ss->tablet_tools.erase(E);2581}2582}25832584void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) {2585// NOTE: Works pretty much like wl_pointer::enter.25862587WindowState *ws = wl_surface_get_window_state(surface);2588if (!ws) {2589return;2590}25912592TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2593ERR_FAIL_NULL(ts);25942595ts->data_pending.proximity_serial = serial;2596ts->data_pending.proximal_id = ws->id;2597ts->data_pending.last_proximal_id = ws->id;25982599DEBUG_LOG_WAYLAND_THREAD(vformat("Tablet tool entered window %d.", ts->data_pending.proximal_id));2600}26012602void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {2603// NOTE: Works pretty much like wl_pointer::leave.26042605TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2606ERR_FAIL_NULL(ts);26072608if (ts->data_pending.proximal_id == DisplayServer::INVALID_WINDOW_ID) {2609// We're probably on a decoration or some other third-party thing.2610return;2611}26122613DisplayServer::WindowID id = ts->data_pending.proximal_id;26142615ts->data_pending.proximal_id = DisplayServer::INVALID_WINDOW_ID;2616ts->data_pending.pressed_button_mask.clear();26172618DEBUG_LOG_WAYLAND_THREAD(vformat("Tablet tool left window %d.", id));2619}26202621void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial) {2622// NOTE: Works pretty much like wl_pointer::button but only for a pressed left2623// button.26242625TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2626ERR_FAIL_NULL(ts);26272628TabletToolData &td = ts->data_pending;26292630td.pressed_button_mask.set_flag(mouse_button_to_mask(MouseButton::LEFT));2631td.last_button_pressed = MouseButton::LEFT;2632td.double_click_begun = true;26332634// The protocol doesn't cover this, but we can use this funky hack to make2635// double clicking work.2636td.button_time = OS::get_singleton()->get_ticks_msec();2637}26382639void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {2640// NOTE: Works pretty much like wl_pointer::button but only for a released left2641// button.26422643TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2644ERR_FAIL_NULL(ts);26452646TabletToolData &td = ts->data_pending;26472648td.pressed_button_mask.clear_flag(mouse_button_to_mask(MouseButton::LEFT));26492650// The protocol doesn't cover this, but we can use this funky hack to make2651// double clicking work.2652td.button_time = OS::get_singleton()->get_ticks_msec();2653}26542655void WaylandThread::_wp_tablet_tool_on_motion(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t x, wl_fixed_t y) {2656// NOTE: Works pretty much like wl_pointer::motion.26572658TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2659ERR_FAIL_NULL(ts);26602661TabletToolData &td = ts->data_pending;26622663td.position.x = wl_fixed_to_double(x);2664td.position.y = wl_fixed_to_double(y);2665}26662667void WaylandThread::_wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t pressure) {2668TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2669ERR_FAIL_NULL(ts);26702671ts->data_pending.pressure = pressure;2672}26732674void WaylandThread::_wp_tablet_tool_on_distance(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t distance) {2675// Unsupported2676}26772678void WaylandThread::_wp_tablet_tool_on_tilt(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t tilt_x, wl_fixed_t tilt_y) {2679TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2680ERR_FAIL_NULL(ts);26812682TabletToolData &td = ts->data_pending;26832684td.tilt.x = wl_fixed_to_double(tilt_x);2685td.tilt.y = wl_fixed_to_double(tilt_y);2686}26872688void WaylandThread::_wp_tablet_tool_on_rotation(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t degrees) {2689// Unsupported.2690}26912692void WaylandThread::_wp_tablet_tool_on_slider(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, int32_t position) {2693// Unsupported.2694}26952696void WaylandThread::_wp_tablet_tool_on_wheel(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t degrees, int32_t clicks) {2697// TODO2698}26992700void WaylandThread::_wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial, uint32_t button, uint32_t state) {2701// NOTE: Works pretty much like wl_pointer::button.27022703TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2704ERR_FAIL_NULL(ts);27052706TabletToolData &td = ts->data_pending;27072708MouseButton mouse_button = MouseButton::NONE;27092710if (button == BTN_STYLUS) {2711mouse_button = MouseButton::LEFT;2712}27132714if (button == BTN_STYLUS2) {2715mouse_button = MouseButton::RIGHT;2716}27172718if (mouse_button != MouseButton::NONE) {2719MouseButtonMask mask = mouse_button_to_mask(mouse_button);27202721if (state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_PRESSED) {2722td.pressed_button_mask.set_flag(mask);2723td.last_button_pressed = mouse_button;2724td.double_click_begun = true;2725} else {2726td.pressed_button_mask.clear_flag(mask);2727}27282729// The protocol doesn't cover this, but we can use this funky hack to make2730// double clicking work.2731td.button_time = OS::get_singleton()->get_ticks_msec();2732}2733}27342735void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t time) {2736// NOTE: Works pretty much like wl_pointer::frame.27372738TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2739ERR_FAIL_NULL(ts);27402741SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);2742ERR_FAIL_NULL(ss);27432744WaylandThread *wayland_thread = ss->wayland_thread;2745ERR_FAIL_NULL(wayland_thread);27462747TabletToolData &old_td = ts->data;2748TabletToolData &td = ts->data_pending;27492750if (td.proximal_id != old_td.proximal_id) {2751if (old_td.proximal_id != DisplayServer::INVALID_WINDOW_ID) {2752Ref<WindowEventMessage> msg;2753msg.instantiate();2754msg->id = old_td.proximal_id;2755msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;27562757wayland_thread->push_message(msg);2758}27592760if (td.proximal_id != DisplayServer::INVALID_WINDOW_ID) {2761Ref<WindowEventMessage> msg;2762msg.instantiate();2763msg->id = td.proximal_id;2764msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;27652766wayland_thread->push_message(msg);2767}2768}27692770if (td.proximal_id == DisplayServer::INVALID_WINDOW_ID) {2771// We're probably on a decoration or some other third-party thing. Let's2772// "commit" the data and call it a day.2773old_td = td;2774return;2775}27762777WindowState *ws = wayland_thread->window_get_state(td.proximal_id);2778ERR_FAIL_NULL(ws);27792780double scale = window_state_get_scale_factor(ws);2781if (old_td.position != td.position || old_td.tilt != td.tilt || old_td.pressure != td.pressure) {2782td.motion_time = time;27832784Ref<InputEventMouseMotion> mm;2785mm.instantiate();27862787mm->set_window_id(td.proximal_id);27882789// Set all pressed modifiers.2790mm->set_shift_pressed(ss->shift_pressed);2791mm->set_ctrl_pressed(ss->ctrl_pressed);2792mm->set_alt_pressed(ss->alt_pressed);2793mm->set_meta_pressed(ss->meta_pressed);27942795mm->set_button_mask(td.pressed_button_mask);27962797mm->set_global_position(td.position * scale);2798mm->set_position(td.position * scale);27992800// NOTE: The Godot API expects normalized values and we store them raw,2801// straight from the compositor, so we have to normalize them here.28022803// According to the tablet proto spec, tilt is expressed in degrees relative2804// to the Z axis of the tablet, so it shouldn't go over 90 degrees either way,2805// I think. We'll clamp it just in case.2806td.tilt = td.tilt.clampf(-90, 90);28072808mm->set_tilt(td.tilt / 90);28092810// The tablet proto spec explicitly says that pressure is defined as a value2811// between 0 to 65535.2812mm->set_pressure(td.pressure / (float)65535);28132814mm->set_pen_inverted(ts->is_eraser);28152816Vector2 pos_delta = (td.position - old_td.position) * scale;28172818mm->set_relative(pos_delta);2819mm->set_relative_screen_position(pos_delta);28202821uint32_t time_delta = td.motion_time - old_td.motion_time;2822mm->set_velocity((Vector2)pos_delta / time_delta);28232824Ref<InputEventMessage> inputev_msg;2825inputev_msg.instantiate();28262827inputev_msg->event = mm;28282829wayland_thread->push_message(inputev_msg);2830}28312832if (old_td.pressed_button_mask != td.pressed_button_mask) {2833td.button_time = time;28342835BitField<MouseButtonMask> pressed_mask_delta = old_td.pressed_button_mask.get_different(td.pressed_button_mask);28362837for (MouseButton test_button : { MouseButton::LEFT, MouseButton::RIGHT }) {2838MouseButtonMask test_button_mask = mouse_button_to_mask(test_button);28392840if (pressed_mask_delta.has_flag(test_button_mask)) {2841Ref<InputEventMouseButton> mb;2842mb.instantiate();28432844// Set all pressed modifiers.2845mb->set_shift_pressed(ss->shift_pressed);2846mb->set_ctrl_pressed(ss->ctrl_pressed);2847mb->set_alt_pressed(ss->alt_pressed);2848mb->set_meta_pressed(ss->meta_pressed);28492850mb->set_window_id(td.proximal_id);2851mb->set_position(td.position * scale);2852mb->set_global_position(td.position * scale);28532854mb->set_button_mask(td.pressed_button_mask);2855mb->set_button_index(test_button);2856mb->set_pressed(td.pressed_button_mask.has_flag(test_button_mask));28572858// We have to set the last position pressed here as we can't take for2859// granted what the individual events might have seen due to them not having2860// a garaunteed order.2861if (mb->is_pressed()) {2862td.last_pressed_position = td.position;2863}28642865if (old_td.double_click_begun && mb->is_pressed() && td.last_button_pressed == old_td.last_button_pressed && (td.button_time - old_td.button_time) < 400 && Vector2(td.last_pressed_position * scale).distance_to(Vector2(old_td.last_pressed_position * scale)) < 5) {2866td.double_click_begun = false;2867mb->set_double_click(true);2868}28692870Ref<InputEventMessage> msg;2871msg.instantiate();28722873msg->event = mb;28742875wayland_thread->push_message(msg);2876}2877}2878}28792880old_td = td;2881}28822883void WaylandThread::_wp_text_input_on_enter(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface) {2884SeatState *ss = (SeatState *)data;2885if (!ss) {2886return;2887}28882889WindowState *ws = wl_surface_get_window_state(surface);2890if (!ws) {2891return;2892}28932894ss->ime_window_id = ws->id;2895ss->ime_enabled = true;2896}28972898void WaylandThread::_wp_text_input_on_leave(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface) {2899SeatState *ss = (SeatState *)data;2900if (!ss) {2901return;2902}29032904Ref<IMEUpdateEventMessage> msg;2905msg.instantiate();2906msg->id = ss->ime_window_id;2907msg->text = String();2908msg->selection = Vector2i();2909ss->wayland_thread->push_message(msg);29102911ss->ime_window_id = DisplayServer::INVALID_WINDOW_ID;2912ss->ime_enabled = false;2913ss->ime_active = false;2914ss->ime_text = String();2915ss->ime_text_commit = String();2916ss->ime_cursor = Vector2i();2917}29182919void WaylandThread::_wp_text_input_on_preedit_string(void *data, struct zwp_text_input_v3 *wp_text_input_v3, const char *text, int32_t cursor_begin, int32_t cursor_end) {2920SeatState *ss = (SeatState *)data;2921if (!ss) {2922return;2923}29242925ss->ime_text = String::utf8(text);29262927// Convert cursor positions from UTF-8 to UTF-32 offset.2928int32_t cursor_begin_utf32 = 0;2929int32_t cursor_end_utf32 = 0;2930for (int i = 0; i < ss->ime_text.length(); i++) {2931uint32_t c = ss->ime_text[i];2932if (c <= 0x7f) { // 7 bits.2933cursor_begin -= 1;2934cursor_end -= 1;2935} else if (c <= 0x7ff) { // 11 bits2936cursor_begin -= 2;2937cursor_end -= 2;2938} else if (c <= 0xffff) { // 16 bits2939cursor_begin -= 3;2940cursor_end -= 3;2941} else if (c <= 0x001fffff) { // 21 bits2942cursor_begin -= 4;2943cursor_end -= 4;2944} else if (c <= 0x03ffffff) { // 26 bits2945cursor_begin -= 5;2946cursor_end -= 5;2947} else if (c <= 0x7fffffff) { // 31 bits2948cursor_begin -= 6;2949cursor_end -= 6;2950} else {2951cursor_begin -= 1;2952cursor_end -= 1;2953}2954if (cursor_begin == 0) {2955cursor_begin_utf32 = i + 1;2956}2957if (cursor_end == 0) {2958cursor_end_utf32 = i + 1;2959}2960if (cursor_begin <= 0 && cursor_end <= 0) {2961break;2962}2963}2964ss->ime_cursor = Vector2i(cursor_begin_utf32, cursor_end_utf32 - cursor_begin_utf32);2965}29662967void WaylandThread::_wp_text_input_on_commit_string(void *data, struct zwp_text_input_v3 *wp_text_input_v3, const char *text) {2968SeatState *ss = (SeatState *)data;2969if (!ss) {2970return;2971}29722973ss->ime_text_commit = String::utf8(text);2974}29752976void WaylandThread::_wp_text_input_on_delete_surrounding_text(void *data, struct zwp_text_input_v3 *wp_text_input_v3, uint32_t before_length, uint32_t after_length) {2977// Not implemented.2978}29792980void WaylandThread::_wp_text_input_on_done(void *data, struct zwp_text_input_v3 *wp_text_input_v3, uint32_t serial) {2981SeatState *ss = (SeatState *)data;2982if (!ss) {2983return;2984}29852986if (!ss->ime_text_commit.is_empty()) {2987Ref<IMECommitEventMessage> msg;2988msg.instantiate();2989msg->id = ss->ime_window_id;2990msg->text = ss->ime_text_commit;2991ss->wayland_thread->push_message(msg);2992} else {2993Ref<IMEUpdateEventMessage> msg;2994msg.instantiate();2995msg->id = ss->ime_window_id;2996msg->text = ss->ime_text;2997msg->selection = ss->ime_cursor;2998ss->wayland_thread->push_message(msg);2999}30003001ss->ime_text = String();3002ss->ime_text_commit = String();3003ss->ime_cursor = Vector2i();3004}30053006void WaylandThread::_xdg_activation_token_on_done(void *data, struct xdg_activation_token_v1 *xdg_activation_token, const char *token) {3007WindowState *ws = (WindowState *)data;3008ERR_FAIL_NULL(ws);3009ERR_FAIL_NULL(ws->wayland_thread);3010ERR_FAIL_NULL(ws->wl_surface);30113012xdg_activation_v1_activate(ws->wayland_thread->registry.xdg_activation, token, ws->wl_surface);3013xdg_activation_token_v1_destroy(xdg_activation_token);30143015DEBUG_LOG_WAYLAND_THREAD(vformat("Received activation token and requested window activation."));3016}30173018// NOTE: This must be started after a valid wl_display is loaded.3019void WaylandThread::_poll_events_thread(void *p_data) {3020ThreadData *data = (ThreadData *)p_data;3021ERR_FAIL_NULL(data);3022ERR_FAIL_NULL(data->wl_display);30233024struct pollfd poll_fd;3025poll_fd.fd = wl_display_get_fd(data->wl_display);3026poll_fd.events = POLLIN | POLLHUP;30273028while (true) {3029// Empty the event queue while it's full.3030while (wl_display_prepare_read(data->wl_display) != 0) {3031// We aren't using wl_display_dispatch(), instead "manually" handling events3032// through wl_display_dispatch_pending so that we can use a global mutex and3033// be sure that this and the main thread won't race over stuff, as long as3034// the main thread locks it too.3035//3036// Note that the main thread can still call wl_display_roundtrip as that3037// method directly handles all events, effectively bypassing this polling3038// loop and thus the mutex locking, avoiding a deadlock.3039MutexLock mutex_lock(data->mutex);30403041if (wl_display_dispatch_pending(data->wl_display) == -1) {3042// Oh no. We'll check and handle any display error below.3043break;3044}3045}30463047int werror = wl_display_get_error(data->wl_display);30483049if (werror) {3050if (werror == EPROTO) {3051struct wl_interface *wl_interface = nullptr;3052uint32_t id = 0;30533054int error_code = wl_display_get_protocol_error(data->wl_display, (const struct wl_interface **)&wl_interface, &id);3055CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id));3056} else {3057CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror));3058}3059}30603061wl_display_flush(data->wl_display);30623063// Wait for the event file descriptor to have new data.3064poll(&poll_fd, 1, -1);30653066if (data->thread_done.is_set()) {3067wl_display_cancel_read(data->wl_display);3068break;3069}30703071if (poll_fd.revents | POLLIN) {3072// Load the queues with fresh new data.3073wl_display_read_events(data->wl_display);3074} else {3075// Oh well... Stop signaling that we want to read.3076wl_display_cancel_read(data->wl_display);3077}30783079// The docs advise to redispatch unconditionally and it looks like that if we3080// don't do this we can't catch protocol errors, which is bad.3081MutexLock mutex_lock(data->mutex);3082wl_display_dispatch_pending(data->wl_display);3083}3084}30853086struct wl_display *WaylandThread::get_wl_display() const {3087return wl_display;3088}30893090// NOTE: Stuff like libdecor can (and will) register foreign proxies which3091// aren't formatted as we like. This method is needed to detect whether a proxy3092// has our tag. Also, be careful! The proxy has to be manually tagged or it3093// won't be recognized.3094bool WaylandThread::wl_proxy_is_godot(struct wl_proxy *p_proxy) {3095ERR_FAIL_NULL_V(p_proxy, false);30963097return wl_proxy_get_tag(p_proxy) == &proxy_tag;3098}30993100void WaylandThread::wl_proxy_tag_godot(struct wl_proxy *p_proxy) {3101ERR_FAIL_NULL(p_proxy);31023103wl_proxy_set_tag(p_proxy, &proxy_tag);3104}31053106// Returns the wl_surface's `WindowState`, otherwise `nullptr`.3107// NOTE: This will fail if the surface isn't tagged as ours.3108WaylandThread::WindowState *WaylandThread::wl_surface_get_window_state(struct wl_surface *p_surface) {3109if (p_surface && wl_proxy_is_godot((wl_proxy *)p_surface)) {3110return (WindowState *)wl_surface_get_user_data(p_surface);3111}31123113return nullptr;3114}31153116// Returns the wl_outputs's `ScreenState`, otherwise `nullptr`.3117// NOTE: This will fail if the output isn't tagged as ours.3118WaylandThread::ScreenState *WaylandThread::wl_output_get_screen_state(struct wl_output *p_output) {3119if (p_output && wl_proxy_is_godot((wl_proxy *)p_output)) {3120return (ScreenState *)wl_output_get_user_data(p_output);3121}31223123return nullptr;3124}31253126// Returns the wl_seat's `SeatState`, otherwise `nullptr`.3127// NOTE: This will fail if the output isn't tagged as ours.3128WaylandThread::SeatState *WaylandThread::wl_seat_get_seat_state(struct wl_seat *p_seat) {3129if (p_seat && wl_proxy_is_godot((wl_proxy *)p_seat)) {3130return (SeatState *)wl_seat_get_user_data(p_seat);3131}31323133return nullptr;3134}31353136// Returns the wp_tablet_tool's `TabletToolState`, otherwise `nullptr`.3137// NOTE: This will fail if the output isn't tagged as ours.3138WaylandThread::TabletToolState *WaylandThread::wp_tablet_tool_get_state(struct zwp_tablet_tool_v2 *p_tool) {3139if (p_tool && wl_proxy_is_godot((wl_proxy *)p_tool)) {3140return (TabletToolState *)zwp_tablet_tool_v2_get_user_data(p_tool);3141}31423143return nullptr;3144}3145// Returns the wl_data_offer's `OfferState`, otherwise `nullptr`.3146// NOTE: This will fail if the output isn't tagged as ours.3147WaylandThread::OfferState *WaylandThread::wl_data_offer_get_offer_state(struct wl_data_offer *p_offer) {3148if (p_offer && wl_proxy_is_godot((wl_proxy *)p_offer)) {3149return (OfferState *)wl_data_offer_get_user_data(p_offer);3150}31513152return nullptr;3153}31543155// Returns the wl_data_offer's `OfferState`, otherwise `nullptr`.3156// NOTE: This will fail if the output isn't tagged as ours.3157WaylandThread::OfferState *WaylandThread::wp_primary_selection_offer_get_offer_state(struct zwp_primary_selection_offer_v1 *p_offer) {3158if (p_offer && wl_proxy_is_godot((wl_proxy *)p_offer)) {3159return (OfferState *)zwp_primary_selection_offer_v1_get_user_data(p_offer);3160}31613162return nullptr;3163}31643165// This is implemented as a method because this is the simplest way of3166// accounting for dynamic output scale changes.3167int WaylandThread::window_state_get_preferred_buffer_scale(WindowState *p_ws) {3168ERR_FAIL_NULL_V(p_ws, 1);31693170if (p_ws->preferred_fractional_scale > 0) {3171// We're scaling fractionally. Per spec, the buffer scale is always 1.3172return 1;3173}31743175if (p_ws->wl_outputs.is_empty()) {3176DEBUG_LOG_WAYLAND_THREAD("Window has no output associated, returning buffer scale of 1.");3177return 1;3178}31793180// TODO: Cache value?3181int max_size = 1;31823183// ================================ IMPORTANT =================================3184// NOTE: Due to a Godot limitation, we can't really rescale the whole UI yet.3185// Because of this reason, all platforms have resorted to forcing the highest3186// scale possible of a system on any window, despite of what screen it's onto.3187// On this backend everything's already in place for dynamic window scale3188// handling, but in the meantime we'll just select the biggest _global_ output.3189// To restore dynamic scale selection, simply iterate over `p_ws->wl_outputs`3190// instead.3191for (struct wl_output *wl_output : p_ws->registry->wl_outputs) {3192ScreenState *ss = wl_output_get_screen_state(wl_output);31933194if (ss && ss->pending_data.scale > max_size) {3195// NOTE: For some mystical reason, wl_output.done is emitted _after_ windows3196// get resized but the scale event gets sent _before_ that. I'm still leaning3197// towards the idea that rescaling when a window gets a resolution change is a3198// pretty good approach, but this means that we'll have to use the screen data3199// before it's "committed".3200// FIXME: Use the committed data. Somehow.3201max_size = ss->pending_data.scale;3202}3203}32043205return max_size;3206}32073208double WaylandThread::window_state_get_scale_factor(WindowState *p_ws) {3209ERR_FAIL_NULL_V(p_ws, 1);32103211if (p_ws->fractional_scale > 0) {3212// The fractional scale amount takes priority.3213return p_ws->fractional_scale;3214}32153216return p_ws->buffer_scale;3217}32183219void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int p_height) {3220ERR_FAIL_NULL(p_ws);32213222int preferred_buffer_scale = window_state_get_preferred_buffer_scale(p_ws);3223bool using_fractional = p_ws->preferred_fractional_scale > 0;32243225// If neither is true we no-op.3226bool scale_changed = true;3227bool size_changed = true;32283229if (p_ws->rect.size.width != p_width || p_ws->rect.size.height != p_height) {3230p_ws->rect.size.width = p_width;3231p_ws->rect.size.height = p_height;32323233size_changed = true;3234}32353236if (using_fractional && p_ws->fractional_scale != p_ws->preferred_fractional_scale) {3237p_ws->fractional_scale = p_ws->preferred_fractional_scale;3238scale_changed = true;3239}32403241if (p_ws->buffer_scale != preferred_buffer_scale) {3242// The buffer scale is always important, even if we use frac scaling.3243p_ws->buffer_scale = preferred_buffer_scale;3244p_ws->buffer_scale_changed = true;32453246if (!using_fractional) {3247// We don't bother updating everything else if it's turned on though.3248scale_changed = true;3249}3250}32513252if (p_ws->wl_surface) {3253if (p_ws->wp_viewport) {3254wp_viewport_set_destination(p_ws->wp_viewport, p_width, p_height);3255}32563257if (p_ws->xdg_surface) {3258xdg_surface_set_window_geometry(p_ws->xdg_surface, 0, 0, p_width, p_height);3259}3260}32613262#ifdef LIBDECOR_ENABLED3263if (p_ws->libdecor_frame) {3264struct libdecor_state *state = libdecor_state_new(p_width, p_height);3265libdecor_frame_commit(p_ws->libdecor_frame, state, p_ws->pending_libdecor_configuration);3266libdecor_state_free(state);3267p_ws->pending_libdecor_configuration = nullptr;3268}3269#endif32703271if (size_changed || scale_changed) {3272double win_scale = window_state_get_scale_factor(p_ws);3273Size2i scaled_size = scale_vector2i(p_ws->rect.size, win_scale);32743275if (using_fractional) {3276DEBUG_LOG_WAYLAND_THREAD(vformat("Resizing the window from %s to %s (fractional scale x%f).", p_ws->rect.size, scaled_size, p_ws->fractional_scale));3277} else {3278DEBUG_LOG_WAYLAND_THREAD(vformat("Resizing the window from %s to %s (buffer scale x%d).", p_ws->rect.size, scaled_size, p_ws->buffer_scale));3279}32803281// FIXME: Actually resize the hint instead of centering it.3282p_ws->wayland_thread->pointer_set_hint(scaled_size / 2);32833284Ref<WindowRectMessage> rect_msg;3285rect_msg.instantiate();3286rect_msg->id = p_ws->id;3287rect_msg->rect.position = scale_vector2i(p_ws->rect.position, win_scale);3288rect_msg->rect.size = scaled_size;3289p_ws->wayland_thread->push_message(rect_msg);3290}32913292if (scale_changed) {3293Ref<WindowEventMessage> dpi_msg;3294dpi_msg.instantiate();3295dpi_msg->id = p_ws->id;3296dpi_msg->event = DisplayServer::WINDOW_EVENT_DPI_CHANGE;3297p_ws->wayland_thread->push_message(dpi_msg);3298}3299}33003301// Scales a vector according to wp_fractional_scale's rules, where coordinates3302// must be scaled with away from zero half-rounding.3303Vector2i WaylandThread::scale_vector2i(const Vector2i &p_vector, double p_amount) {3304// This snippet is tiny, I know, but this is done a lot.3305int x = std::round(p_vector.x * p_amount);3306int y = std::round(p_vector.y * p_amount);33073308return Vector2i(x, y);3309}33103311void WaylandThread::seat_state_unlock_pointer(SeatState *p_ss) {3312ERR_FAIL_NULL(p_ss);33133314if (p_ss->wl_pointer == nullptr) {3315return;3316}33173318if (p_ss->wp_locked_pointer) {3319zwp_locked_pointer_v1_destroy(p_ss->wp_locked_pointer);3320p_ss->wp_locked_pointer = nullptr;3321}33223323if (p_ss->wp_confined_pointer) {3324zwp_confined_pointer_v1_destroy(p_ss->wp_confined_pointer);3325p_ss->wp_confined_pointer = nullptr;3326}3327}33283329void WaylandThread::seat_state_lock_pointer(SeatState *p_ss) {3330ERR_FAIL_NULL(p_ss);33313332if (p_ss->wl_pointer == nullptr) {3333return;3334}33353336if (registry.wp_pointer_constraints == nullptr) {3337return;3338}33393340if (p_ss->wp_locked_pointer == nullptr) {3341struct wl_surface *locked_surface = window_get_wl_surface(p_ss->pointer_data.last_pointed_id);3342ERR_FAIL_NULL(locked_surface);33433344p_ss->wp_locked_pointer = zwp_pointer_constraints_v1_lock_pointer(registry.wp_pointer_constraints, locked_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);3345}3346}33473348void WaylandThread::seat_state_set_hint(SeatState *p_ss, int p_x, int p_y) {3349if (p_ss->wp_locked_pointer == nullptr) {3350return;3351}33523353zwp_locked_pointer_v1_set_cursor_position_hint(p_ss->wp_locked_pointer, wl_fixed_from_int(p_x), wl_fixed_from_int(p_y));3354}33553356void WaylandThread::seat_state_confine_pointer(SeatState *p_ss) {3357ERR_FAIL_NULL(p_ss);33583359if (p_ss->wl_pointer == nullptr) {3360return;3361}33623363if (registry.wp_pointer_constraints == nullptr) {3364return;3365}33663367if (p_ss->wp_confined_pointer == nullptr) {3368struct wl_surface *confined_surface = window_get_wl_surface(p_ss->pointer_data.last_pointed_id);3369ERR_FAIL_NULL(confined_surface);33703371p_ss->wp_confined_pointer = zwp_pointer_constraints_v1_confine_pointer(registry.wp_pointer_constraints, confined_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);3372}3373}33743375void WaylandThread::seat_state_update_cursor(SeatState *p_ss) {3376ERR_FAIL_NULL(p_ss);33773378WaylandThread *thread = p_ss->wayland_thread;3379ERR_FAIL_NULL(p_ss->wayland_thread);33803381if (!p_ss->wl_pointer || !p_ss->cursor_surface) {3382return;3383}33843385// NOTE: Those values are valid by default and will hide the cursor when3386// unchanged.3387struct wl_buffer *cursor_buffer = nullptr;3388uint32_t hotspot_x = 0;3389uint32_t hotspot_y = 0;3390int scale = 1;33913392if (thread->cursor_visible) {3393DisplayServer::CursorShape shape = thread->cursor_shape;33943395struct CustomCursor *custom_cursor = thread->custom_cursors.getptr(shape);33963397if (custom_cursor) {3398cursor_buffer = custom_cursor->wl_buffer;3399hotspot_x = custom_cursor->hotspot.x;3400hotspot_y = custom_cursor->hotspot.y;34013402// We can't really reasonably scale custom cursors, so we'll let the3403// compositor do it for us (badly).3404scale = 1;3405} else if (thread->registry.wp_cursor_shape_manager) {3406wp_cursor_shape_device_v1_shape wp_shape = thread->standard_cursors[shape];3407wp_cursor_shape_device_v1_set_shape(p_ss->wp_cursor_shape_device, p_ss->pointer_enter_serial, wp_shape);34083409// We should avoid calling the `wl_pointer_set_cursor` at the end of this method.3410return;3411} else {3412struct wl_cursor *wl_cursor = thread->wl_cursors[shape];34133414if (!wl_cursor) {3415return;3416}34173418int frame_idx = 0;34193420if (wl_cursor->image_count > 1) {3421// The cursor is animated.3422frame_idx = wl_cursor_frame(wl_cursor, p_ss->cursor_time_ms);34233424if (!p_ss->cursor_frame_callback) {3425// Since it's animated, we'll re-update it the next frame.3426p_ss->cursor_frame_callback = wl_surface_frame(p_ss->cursor_surface);3427wl_callback_add_listener(p_ss->cursor_frame_callback, &cursor_frame_callback_listener, p_ss);3428}3429}34303431struct wl_cursor_image *wl_cursor_image = wl_cursor->images[frame_idx];34323433scale = thread->cursor_scale;34343435cursor_buffer = wl_cursor_image_get_buffer(wl_cursor_image);34363437// As the surface's buffer is scaled (thus the surface is smaller) and the3438// hotspot must be expressed in surface-local coordinates, we need to scale3439// it down accordingly.3440hotspot_x = wl_cursor_image->hotspot_x / scale;3441hotspot_y = wl_cursor_image->hotspot_y / scale;3442}3443}34443445wl_pointer_set_cursor(p_ss->wl_pointer, p_ss->pointer_enter_serial, p_ss->cursor_surface, hotspot_x, hotspot_y);3446wl_surface_set_buffer_scale(p_ss->cursor_surface, scale);3447wl_surface_attach(p_ss->cursor_surface, cursor_buffer, 0, 0);3448wl_surface_damage_buffer(p_ss->cursor_surface, 0, 0, INT_MAX, INT_MAX);34493450wl_surface_commit(p_ss->cursor_surface);3451}34523453void WaylandThread::seat_state_echo_keys(SeatState *p_ss) {3454ERR_FAIL_NULL(p_ss);34553456if (p_ss->wl_keyboard == nullptr) {3457return;3458}34593460// TODO: Comment and document out properly this block of code.3461// In short, this implements key repeating.3462if (p_ss->repeat_key_delay_msec && p_ss->repeating_keycode != XKB_KEYCODE_INVALID) {3463uint64_t current_ticks = OS::get_singleton()->get_ticks_msec();3464uint64_t delayed_start_ticks = p_ss->last_repeat_start_msec + p_ss->repeat_start_delay_msec;34653466if (p_ss->last_repeat_msec < delayed_start_ticks) {3467p_ss->last_repeat_msec = delayed_start_ticks;3468}34693470if (current_ticks >= delayed_start_ticks) {3471uint64_t ticks_delta = current_ticks - p_ss->last_repeat_msec;34723473int keys_amount = (ticks_delta / p_ss->repeat_key_delay_msec);34743475for (int i = 0; i < keys_amount; i++) {3476Ref<InputEventKey> k = _seat_state_get_key_event(p_ss, p_ss->repeating_keycode, true);3477if (k.is_null()) {3478continue;3479}34803481k->set_echo(true);34823483Ref<InputEventKey> uk = _seat_state_get_unstuck_key_event(p_ss, p_ss->repeating_keycode, true, k->get_keycode());3484if (uk.is_valid()) {3485Input::get_singleton()->parse_input_event(uk);3486}34873488Input::get_singleton()->parse_input_event(k);3489}34903491p_ss->last_repeat_msec += ticks_delta - (ticks_delta % p_ss->repeat_key_delay_msec);3492}3493}3494}34953496void WaylandThread::push_message(Ref<Message> message) {3497messages.push_back(message);3498}34993500bool WaylandThread::has_message() {3501return messages.front() != nullptr;3502}35033504Ref<WaylandThread::Message> WaylandThread::pop_message() {3505if (messages.front() != nullptr) {3506Ref<Message> msg = messages.front()->get();3507messages.pop_front();3508return msg;3509}35103511// This method should only be called if `has_messages` returns true but if3512// that isn't the case we'll just return an invalid `Ref`. After all, due to3513// its `InputEvent`-like interface, we still have to dynamically cast and check3514// the `Ref`'s validity anyways.3515return Ref<Message>();3516}35173518void WaylandThread::window_create(DisplayServer::WindowID p_window_id, int p_width, int p_height) {3519ERR_FAIL_COND(windows.has(p_window_id));3520WindowState &ws = windows[p_window_id];35213522ws.id = p_window_id;35233524ws.registry = ®istry;3525ws.wayland_thread = this;35263527ws.rect.size.width = p_width;3528ws.rect.size.height = p_height;35293530ws.wl_surface = wl_compositor_create_surface(registry.wl_compositor);3531wl_proxy_tag_godot((struct wl_proxy *)ws.wl_surface);3532wl_surface_add_listener(ws.wl_surface, &wl_surface_listener, &ws);35333534if (registry.wp_viewporter) {3535ws.wp_viewport = wp_viewporter_get_viewport(registry.wp_viewporter, ws.wl_surface);35363537if (registry.wp_fractional_scale_manager) {3538ws.wp_fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(registry.wp_fractional_scale_manager, ws.wl_surface);3539wp_fractional_scale_v1_add_listener(ws.wp_fractional_scale, &wp_fractional_scale_listener, &ws);3540}3541}35423543bool decorated = false;35443545#ifdef LIBDECOR_ENABLED3546if (!decorated && libdecor_context) {3547ws.libdecor_frame = libdecor_decorate(libdecor_context, ws.wl_surface, (struct libdecor_frame_interface *)&libdecor_frame_interface, &ws);3548libdecor_frame_map(ws.libdecor_frame);35493550if (registry.xdg_toplevel_icon_manager) {3551xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(ws.libdecor_frame);3552if (toplevel != nullptr) {3553xdg_toplevel_icon_manager_v1_set_icon(registry.xdg_toplevel_icon_manager, toplevel, xdg_icon);3554}3555}35563557decorated = true;3558}3559#endif35603561if (!decorated) {3562// libdecor has failed loading or is disabled, we shall handle xdg_toplevel3563// creation and decoration ourselves (and by decorating for now I just mean3564// asking for SSDs and hoping for the best).3565ws.xdg_surface = xdg_wm_base_get_xdg_surface(registry.xdg_wm_base, ws.wl_surface);3566xdg_surface_add_listener(ws.xdg_surface, &xdg_surface_listener, &ws);35673568ws.xdg_toplevel = xdg_surface_get_toplevel(ws.xdg_surface);3569xdg_toplevel_add_listener(ws.xdg_toplevel, &xdg_toplevel_listener, &ws);35703571if (registry.xdg_decoration_manager) {3572ws.xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(registry.xdg_decoration_manager, ws.xdg_toplevel);3573zxdg_toplevel_decoration_v1_add_listener(ws.xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, &ws);35743575decorated = true;3576}35773578if (registry.xdg_toplevel_icon_manager) {3579xdg_toplevel_icon_manager_v1_set_icon(registry.xdg_toplevel_icon_manager, ws.xdg_toplevel, xdg_icon);3580}3581}35823583ws.frame_callback = wl_surface_frame(ws.wl_surface);3584wl_callback_add_listener(ws.frame_callback, &frame_wl_callback_listener, &ws);35853586if (registry.xdg_exporter_v2) {3587ws.xdg_exported_v2 = zxdg_exporter_v2_export_toplevel(registry.xdg_exporter_v2, ws.wl_surface);3588zxdg_exported_v2_add_listener(ws.xdg_exported_v2, &xdg_exported_v2_listener, &ws);3589} else if (registry.xdg_exporter_v1) {3590ws.xdg_exported_v1 = zxdg_exporter_v1_export(registry.xdg_exporter_v1, ws.wl_surface);3591zxdg_exported_v1_add_listener(ws.xdg_exported_v1, &xdg_exported_v1_listener, &ws);3592}35933594wl_surface_commit(ws.wl_surface);35953596// Wait for the surface to be configured before continuing.3597wl_display_roundtrip(wl_display);35983599window_state_update_size(&ws, ws.rect.size.width, ws.rect.size.height);3600}36013602void WaylandThread::window_create_popup(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id, Rect2i p_rect) {3603ERR_FAIL_COND(windows.has(p_window_id));3604ERR_FAIL_COND(!windows.has(p_parent_id));36053606WindowState &ws = windows[p_window_id];3607WindowState &parent = windows[p_parent_id];36083609double parent_scale = window_state_get_scale_factor(&parent);36103611p_rect.position = scale_vector2i(p_rect.position, 1.0 / parent_scale);3612p_rect.size = scale_vector2i(p_rect.size, 1.0 / parent_scale);36133614ws.id = p_window_id;3615ws.parent_id = p_parent_id;3616ws.registry = ®istry;3617ws.wayland_thread = this;36183619ws.rect = p_rect;36203621ws.wl_surface = wl_compositor_create_surface(registry.wl_compositor);3622wl_proxy_tag_godot((struct wl_proxy *)ws.wl_surface);3623wl_surface_add_listener(ws.wl_surface, &wl_surface_listener, &ws);36243625if (registry.wp_viewporter) {3626ws.wp_viewport = wp_viewporter_get_viewport(registry.wp_viewporter, ws.wl_surface);36273628if (registry.wp_fractional_scale_manager) {3629ws.wp_fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(registry.wp_fractional_scale_manager, ws.wl_surface);3630wp_fractional_scale_v1_add_listener(ws.wp_fractional_scale, &wp_fractional_scale_listener, &ws);3631}3632}36333634ws.xdg_surface = xdg_wm_base_get_xdg_surface(registry.xdg_wm_base, ws.wl_surface);3635xdg_surface_add_listener(ws.xdg_surface, &xdg_surface_listener, &ws);36363637Rect2i positioner_rect;3638positioner_rect.size = parent.rect.size;3639struct xdg_surface *parent_xdg_surface = parent.xdg_surface;36403641Point2i offset = ws.rect.position - parent.rect.position;36423643#ifdef LIBDECOR_ENABLED3644if (!parent_xdg_surface && parent.libdecor_frame) {3645parent_xdg_surface = libdecor_frame_get_xdg_surface(parent.libdecor_frame);36463647int corner_x = 0;3648int corner_y = 0;3649libdecor_frame_translate_coordinate(parent.libdecor_frame, 0, 0, &corner_x, &corner_y);36503651positioner_rect.position.x = corner_x;3652positioner_rect.position.y = corner_y;36533654positioner_rect.size.width -= corner_x;3655positioner_rect.size.height -= corner_y;3656}3657#endif36583659ERR_FAIL_NULL(parent_xdg_surface);36603661struct xdg_positioner *xdg_positioner = xdg_wm_base_create_positioner(registry.xdg_wm_base);3662xdg_positioner_set_size(xdg_positioner, ws.rect.size.width, ws.rect.size.height);3663xdg_positioner_set_anchor(xdg_positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);3664xdg_positioner_set_gravity(xdg_positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);3665xdg_positioner_set_constraint_adjustment(xdg_positioner, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y);3666xdg_positioner_set_anchor_rect(xdg_positioner, positioner_rect.position.x, positioner_rect.position.y, positioner_rect.size.width, positioner_rect.size.height);3667xdg_positioner_set_offset(xdg_positioner, offset.x, offset.y);36683669ws.xdg_popup = xdg_surface_get_popup(ws.xdg_surface, parent_xdg_surface, xdg_positioner);3670xdg_popup_add_listener(ws.xdg_popup, &xdg_popup_listener, &ws);36713672xdg_positioner_destroy(xdg_positioner);36733674ws.frame_callback = wl_surface_frame(ws.wl_surface);3675wl_callback_add_listener(ws.frame_callback, &frame_wl_callback_listener, &ws);36763677wl_surface_commit(ws.wl_surface);36783679// Wait for the surface to be configured before continuing.3680wl_display_roundtrip(wl_display);3681}36823683void WaylandThread::window_destroy(DisplayServer::WindowID p_window_id) {3684ERR_FAIL_COND(!windows.has(p_window_id));3685WindowState &ws = windows[p_window_id];36863687if (ws.xdg_popup) {3688xdg_popup_destroy(ws.xdg_popup);3689}36903691if (ws.xdg_toplevel_decoration) {3692zxdg_toplevel_decoration_v1_destroy(ws.xdg_toplevel_decoration);3693}36943695if (ws.xdg_toplevel) {3696xdg_toplevel_destroy(ws.xdg_toplevel);3697}36983699#ifdef LIBDECOR_ENABLED3700if (ws.libdecor_frame) {3701libdecor_frame_unref(ws.libdecor_frame);3702}3703#endif // LIBDECOR_ENABLED37043705if (ws.wp_fractional_scale) {3706wp_fractional_scale_v1_destroy(ws.wp_fractional_scale);3707}37083709if (ws.wp_viewport) {3710wp_viewport_destroy(ws.wp_viewport);3711}37123713if (ws.frame_callback) {3714wl_callback_destroy(ws.frame_callback);3715}37163717if (ws.xdg_surface) {3718xdg_surface_destroy(ws.xdg_surface);3719}37203721if (ws.wl_surface) {3722wl_surface_destroy(ws.wl_surface);3723}37243725// Before continuing, let's handle any leftover event that might still refer to3726// this window.3727wl_display_roundtrip(wl_display);37283729// We can already clean up here, we're done.3730windows.erase(p_window_id);3731}37323733struct wl_surface *WaylandThread::window_get_wl_surface(DisplayServer::WindowID p_window_id) const {3734ERR_FAIL_COND_V(!windows.has(p_window_id), nullptr);3735const WindowState &ws = windows[p_window_id];37363737return ws.wl_surface;3738}37393740WaylandThread::WindowState *WaylandThread::window_get_state(DisplayServer::WindowID p_window_id) {3741return windows.getptr(p_window_id);3742}37433744void WaylandThread::beep() const {3745if (registry.xdg_system_bell) {3746xdg_system_bell_v1_ring(registry.xdg_system_bell, nullptr);3747}3748}37493750void WaylandThread::window_start_drag(DisplayServer::WindowID p_window_id) {3751ERR_FAIL_COND(!windows.has(p_window_id));3752WindowState &ws = windows[p_window_id];3753SeatState *ss = wl_seat_get_seat_state(wl_seat_current);37543755if (ss && ws.xdg_toplevel) {3756xdg_toplevel_move(ws.xdg_toplevel, ss->wl_seat, ss->pointer_data.button_serial);3757}37583759#ifdef LIBDECOR_ENABLED3760if (ws.libdecor_frame) {3761libdecor_frame_move(ws.libdecor_frame, ss->wl_seat, ss->pointer_data.button_serial);3762}3763#endif3764}37653766void WaylandThread::window_start_resize(DisplayServer::WindowResizeEdge p_edge, DisplayServer::WindowID p_window) {3767ERR_FAIL_COND(!windows.has(p_window));3768WindowState &ws = windows[p_window];3769SeatState *ss = wl_seat_get_seat_state(wl_seat_current);37703771if (ss && ws.xdg_toplevel) {3772xdg_toplevel_resize_edge edge = XDG_TOPLEVEL_RESIZE_EDGE_NONE;3773switch (p_edge) {3774case DisplayServer::WINDOW_EDGE_TOP_LEFT: {3775edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;3776} break;3777case DisplayServer::WINDOW_EDGE_TOP: {3778edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP;3779} break;3780case DisplayServer::WINDOW_EDGE_TOP_RIGHT: {3781edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;3782} break;3783case DisplayServer::WINDOW_EDGE_LEFT: {3784edge = XDG_TOPLEVEL_RESIZE_EDGE_LEFT;3785} break;3786case DisplayServer::WINDOW_EDGE_RIGHT: {3787edge = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT;3788} break;3789case DisplayServer::WINDOW_EDGE_BOTTOM_LEFT: {3790edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;3791} break;3792case DisplayServer::WINDOW_EDGE_BOTTOM: {3793edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;3794} break;3795case DisplayServer::WINDOW_EDGE_BOTTOM_RIGHT: {3796edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;3797} break;3798default:3799break;3800}3801xdg_toplevel_resize(ws.xdg_toplevel, ss->wl_seat, ss->pointer_data.button_serial, edge);3802}38033804#ifdef LIBDECOR_ENABLED3805if (ws.libdecor_frame) {3806libdecor_resize_edge edge = LIBDECOR_RESIZE_EDGE_NONE;3807switch (p_edge) {3808case DisplayServer::WINDOW_EDGE_TOP_LEFT: {3809edge = LIBDECOR_RESIZE_EDGE_TOP_LEFT;3810} break;3811case DisplayServer::WINDOW_EDGE_TOP: {3812edge = LIBDECOR_RESIZE_EDGE_TOP;3813} break;3814case DisplayServer::WINDOW_EDGE_TOP_RIGHT: {3815edge = LIBDECOR_RESIZE_EDGE_TOP_RIGHT;3816} break;3817case DisplayServer::WINDOW_EDGE_LEFT: {3818edge = LIBDECOR_RESIZE_EDGE_LEFT;3819} break;3820case DisplayServer::WINDOW_EDGE_RIGHT: {3821edge = LIBDECOR_RESIZE_EDGE_RIGHT;3822} break;3823case DisplayServer::WINDOW_EDGE_BOTTOM_LEFT: {3824edge = LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT;3825} break;3826case DisplayServer::WINDOW_EDGE_BOTTOM: {3827edge = LIBDECOR_RESIZE_EDGE_BOTTOM;3828} break;3829case DisplayServer::WINDOW_EDGE_BOTTOM_RIGHT: {3830edge = LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT;3831} break;3832default:3833break;3834}3835libdecor_frame_resize(ws.libdecor_frame, ss->wl_seat, ss->pointer_data.button_serial, edge);3836}3837#endif3838}38393840void WaylandThread::window_set_parent(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id) {3841ERR_FAIL_COND(!windows.has(p_window_id));3842ERR_FAIL_COND(!windows.has(p_parent_id));38433844WindowState &child = windows[p_window_id];3845child.parent_id = p_parent_id;38463847WindowState &parent = windows[p_parent_id];38483849// NOTE: We can't really unparent as, at the time of writing, libdecor3850// segfaults when trying to set a null parent. Hopefully unparenting is not3851// that common. Bummer.38523853#ifdef LIBDECOR_ENABLED3854if (child.libdecor_frame && parent.libdecor_frame) {3855libdecor_frame_set_parent(child.libdecor_frame, parent.libdecor_frame);3856return;3857}3858#endif38593860if (child.xdg_toplevel && parent.xdg_toplevel) {3861xdg_toplevel_set_parent(child.xdg_toplevel, parent.xdg_toplevel);3862}3863}38643865void WaylandThread::window_set_max_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {3866ERR_FAIL_COND(!windows.has(p_window_id));3867WindowState &ws = windows[p_window_id];38683869Vector2i logical_max_size = scale_vector2i(p_size, 1 / window_state_get_scale_factor(&ws));38703871if (ws.wl_surface && ws.xdg_toplevel) {3872xdg_toplevel_set_max_size(ws.xdg_toplevel, logical_max_size.width, logical_max_size.height);3873}38743875#ifdef LIBDECOR_ENABLED3876if (ws.libdecor_frame) {3877libdecor_frame_set_max_content_size(ws.libdecor_frame, logical_max_size.width, logical_max_size.height);3878}38793880// FIXME: I'm not sure whether we have to commit the surface for this to apply.3881#endif3882}38833884void WaylandThread::window_set_min_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {3885ERR_FAIL_COND(!windows.has(p_window_id));3886WindowState &ws = windows[p_window_id];38873888Size2i logical_min_size = scale_vector2i(p_size, 1 / window_state_get_scale_factor(&ws));38893890if (ws.wl_surface && ws.xdg_toplevel) {3891xdg_toplevel_set_min_size(ws.xdg_toplevel, logical_min_size.width, logical_min_size.height);3892}38933894#ifdef LIBDECOR_ENABLED3895if (ws.libdecor_frame) {3896libdecor_frame_set_min_content_size(ws.libdecor_frame, logical_min_size.width, logical_min_size.height);3897}38983899// FIXME: I'm not sure whether we have to commit the surface for this to apply.3900#endif3901}39023903bool WaylandThread::window_can_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) const {3904ERR_FAIL_COND_V(!windows.has(p_window_id), false);3905const WindowState &ws = windows[p_window_id];39063907switch (p_window_mode) {3908case DisplayServer::WINDOW_MODE_WINDOWED: {3909// Looks like it's guaranteed.3910return true;3911};39123913case DisplayServer::WINDOW_MODE_MINIMIZED: {3914#ifdef LIBDECOR_ENABLED3915if (ws.libdecor_frame) {3916return libdecor_frame_has_capability(ws.libdecor_frame, LIBDECOR_ACTION_MINIMIZE);3917}3918#endif // LIBDECOR_ENABLED39193920return ws.can_minimize;3921};39223923case DisplayServer::WINDOW_MODE_MAXIMIZED: {3924// NOTE: libdecor doesn't seem to have a maximize capability query?3925// The fact that there's a fullscreen one makes me suspicious.3926return ws.can_maximize;3927};39283929case DisplayServer::WINDOW_MODE_FULLSCREEN:3930case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: {3931#ifdef LIBDECOR_ENABLED3932if (ws.libdecor_frame) {3933return libdecor_frame_has_capability(ws.libdecor_frame, LIBDECOR_ACTION_FULLSCREEN);3934}3935#endif // LIBDECOR_ENABLED39363937return ws.can_fullscreen;3938};3939}39403941return false;3942}39433944void WaylandThread::window_try_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) {3945ERR_FAIL_COND(!windows.has(p_window_id));3946WindowState &ws = windows[p_window_id];39473948if (ws.mode == p_window_mode) {3949return;3950}39513952// Don't waste time with hidden windows and whatnot. Behave like it worked.3953#ifdef LIBDECOR_ENABLED3954if ((!ws.wl_surface || !ws.xdg_toplevel) && !ws.libdecor_frame) {3955#else3956if (!ws.wl_surface || !ws.xdg_toplevel) {3957#endif // LIBDECOR_ENABLED3958ws.mode = p_window_mode;3959return;3960}39613962// Return back to a windowed state so that we can apply what the user asked.3963switch (ws.mode) {3964case DisplayServer::WINDOW_MODE_WINDOWED: {3965// Do nothing.3966} break;39673968case DisplayServer::WINDOW_MODE_MINIMIZED: {3969// We can't do much according to the xdg_shell protocol. I have no idea3970// whether this implies that we should return or who knows what. For now3971// we'll do nothing.3972// TODO: Test this properly.3973} break;39743975case DisplayServer::WINDOW_MODE_MAXIMIZED: {3976// Try to unmaximize. This isn't garaunteed to work actually, so we'll have3977// to check whether something changed.3978if (ws.xdg_toplevel) {3979xdg_toplevel_unset_maximized(ws.xdg_toplevel);3980}39813982#ifdef LIBDECOR_ENABLED3983if (ws.libdecor_frame) {3984libdecor_frame_unset_maximized(ws.libdecor_frame);3985}3986#endif // LIBDECOR_ENABLED3987} break;39883989case DisplayServer::WINDOW_MODE_FULLSCREEN:3990case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: {3991// Same thing as above, unset fullscreen and check later if it worked.3992if (ws.xdg_toplevel) {3993xdg_toplevel_unset_fullscreen(ws.xdg_toplevel);3994}39953996#ifdef LIBDECOR_ENABLED3997if (ws.libdecor_frame) {3998libdecor_frame_unset_fullscreen(ws.libdecor_frame);3999}4000#endif // LIBDECOR_ENABLED4001} break;4002}40034004// Wait for a configure event and hope that something changed.4005wl_display_roundtrip(wl_display);40064007if (ws.mode != DisplayServer::WINDOW_MODE_WINDOWED) {4008// The compositor refused our "normalization" request. It'd be useless or4009// unpredictable to attempt setting a new state. We're done.4010return;4011}40124013// Ask the compositor to set the state indicated by the new mode.4014switch (p_window_mode) {4015case DisplayServer::WINDOW_MODE_WINDOWED: {4016// Do nothing. We're already windowed.4017} break;40184019case DisplayServer::WINDOW_MODE_MINIMIZED: {4020if (!window_can_set_mode(p_window_id, p_window_mode)) {4021// Minimization is special (read below). Better not mess with it if the4022// compositor explicitly announces that it doesn't support it.4023break;4024}40254026if (ws.xdg_toplevel) {4027xdg_toplevel_set_minimized(ws.xdg_toplevel);4028}40294030#ifdef LIBDECOR_ENABLED4031if (ws.libdecor_frame) {4032libdecor_frame_set_minimized(ws.libdecor_frame);4033}4034#endif // LIBDECOR_ENABLED4035// We have no way to actually detect this state, so we'll have to report it4036// manually to the engine (hoping that it worked). In the worst case it'll4037// get reset by the next configure event.4038ws.mode = DisplayServer::WINDOW_MODE_MINIMIZED;4039} break;40404041case DisplayServer::WINDOW_MODE_MAXIMIZED: {4042if (ws.xdg_toplevel) {4043xdg_toplevel_set_maximized(ws.xdg_toplevel);4044}40454046#ifdef LIBDECOR_ENABLED4047if (ws.libdecor_frame) {4048libdecor_frame_set_maximized(ws.libdecor_frame);4049}4050#endif // LIBDECOR_ENABLED4051} break;40524053case DisplayServer::WINDOW_MODE_FULLSCREEN:4054case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: {4055if (ws.xdg_toplevel) {4056xdg_toplevel_set_fullscreen(ws.xdg_toplevel, nullptr);4057}40584059#ifdef LIBDECOR_ENABLED4060if (ws.libdecor_frame) {4061libdecor_frame_set_fullscreen(ws.libdecor_frame, nullptr);4062}4063#endif // LIBDECOR_ENABLED4064} break;40654066default: {4067} break;4068}4069}40704071void WaylandThread::window_set_borderless(DisplayServer::WindowID p_window_id, bool p_borderless) {4072ERR_FAIL_COND(!windows.has(p_window_id));4073WindowState &ws = windows[p_window_id];40744075if (ws.xdg_toplevel_decoration) {4076if (p_borderless) {4077// We implement borderless windows by simply asking the compositor to let4078// us handle decorations (we don't).4079zxdg_toplevel_decoration_v1_set_mode(ws.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);4080} else {4081zxdg_toplevel_decoration_v1_set_mode(ws.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);4082}4083}40844085#ifdef LIBDECOR_ENABLED4086if (ws.libdecor_frame) {4087bool visible_current = libdecor_frame_is_visible(ws.libdecor_frame);4088bool visible_target = !p_borderless;40894090// NOTE: We have to do this otherwise we trip on a libdecor bug where it's4091// possible to destroy the frame more than once, by setting the visibility4092// to false multiple times and thus crashing.4093if (visible_current != visible_target) {4094print_verbose(vformat("Setting libdecor frame visibility to %d", visible_target));4095libdecor_frame_set_visibility(ws.libdecor_frame, visible_target);4096}4097}4098#endif // LIBDECOR_ENABLED4099}41004101void WaylandThread::window_set_title(DisplayServer::WindowID p_window_id, const String &p_title) {4102ERR_FAIL_COND(!windows.has(p_window_id));4103WindowState &ws = windows[p_window_id];41044105#ifdef LIBDECOR_ENABLED4106if (ws.libdecor_frame) {4107libdecor_frame_set_title(ws.libdecor_frame, p_title.utf8().get_data());4108}4109#endif // LIBDECOR_ENABLE41104111if (ws.xdg_toplevel) {4112xdg_toplevel_set_title(ws.xdg_toplevel, p_title.utf8().get_data());4113}4114}41154116void WaylandThread::window_set_app_id(DisplayServer::WindowID p_window_id, const String &p_app_id) {4117ERR_FAIL_COND(!windows.has(p_window_id));4118WindowState &ws = windows[p_window_id];41194120#ifdef LIBDECOR_ENABLED4121if (ws.libdecor_frame) {4122libdecor_frame_set_app_id(ws.libdecor_frame, p_app_id.utf8().get_data());4123return;4124}4125#endif // LIBDECOR_ENABLED41264127if (ws.xdg_toplevel) {4128xdg_toplevel_set_app_id(ws.xdg_toplevel, p_app_id.utf8().get_data());4129return;4130}4131}41324133void WaylandThread::set_icon(const Ref<Image> &p_icon) {4134ERR_FAIL_COND(p_icon.is_null());41354136Size2i icon_size = p_icon->get_size();4137ERR_FAIL_COND(icon_size.width != icon_size.height);41384139if (!registry.xdg_toplevel_icon_manager) {4140return;4141}41424143if (xdg_icon) {4144xdg_toplevel_icon_v1_destroy(xdg_icon);4145}41464147if (icon_buffer) {4148wl_buffer_destroy(icon_buffer);4149}41504151// NOTE: The stride is the width of the icon in bytes.4152uint32_t icon_stride = icon_size.width * 4;4153uint32_t data_size = icon_stride * icon_size.height;41544155// We need a shared memory object file descriptor in order to create a4156// wl_buffer through wl_shm.4157int fd = WaylandThread::_allocate_shm_file(data_size);4158ERR_FAIL_COND(fd == -1);41594160uint32_t *buffer_data = (uint32_t *)mmap(nullptr, data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);41614162// Create the Wayland buffer.4163struct wl_shm_pool *shm_pool = wl_shm_create_pool(registry.wl_shm, fd, data_size);4164icon_buffer = wl_shm_pool_create_buffer(shm_pool, 0, icon_size.width, icon_size.height, icon_stride, WL_SHM_FORMAT_ARGB8888);4165wl_shm_pool_destroy(shm_pool);41664167// Fill the cursor buffer with the image data.4168for (uint32_t index = 0; index < (uint32_t)(icon_size.width * icon_size.height); index++) {4169int row_index = index / icon_size.width;4170int column_index = (index % icon_size.width);41714172buffer_data[index] = p_icon->get_pixel(column_index, row_index).to_argb32();41734174// Wayland buffers, unless specified, require associated alpha, so we'll just4175// associate the alpha in-place.4176uint8_t *pixel_data = (uint8_t *)&buffer_data[index];4177pixel_data[0] = pixel_data[0] * pixel_data[3] / 255;4178pixel_data[1] = pixel_data[1] * pixel_data[3] / 255;4179pixel_data[2] = pixel_data[2] * pixel_data[3] / 255;4180}41814182xdg_icon = xdg_toplevel_icon_manager_v1_create_icon(registry.xdg_toplevel_icon_manager);4183xdg_toplevel_icon_v1_add_buffer(xdg_icon, icon_buffer, icon_size.width);41844185if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {4186// Setting a name allows the godot icon to be overridden by a system theme.4187// We only want the project manager and editor to get themed,4188// Games will get icons with the protocol and themed icons with .desktop entries.4189// NOTE: should be synced with the icon name in misc/dist/linuxbsd/Godot.desktop4190xdg_toplevel_icon_v1_set_name(xdg_icon, "godot");4191}41924193for (KeyValue<DisplayServer::WindowID, WindowState> &pair : windows) {4194WindowState &ws = pair.value;4195#ifdef LIBDECOR_ENABLED4196if (ws.libdecor_frame) {4197xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(ws.libdecor_frame);4198ERR_FAIL_NULL(toplevel);4199xdg_toplevel_icon_manager_v1_set_icon(registry.xdg_toplevel_icon_manager, toplevel, xdg_icon);4200}4201#endif4202if (ws.xdg_toplevel) {4203xdg_toplevel_icon_manager_v1_set_icon(registry.xdg_toplevel_icon_manager, ws.xdg_toplevel, xdg_icon);4204}4205}4206}42074208DisplayServer::WindowMode WaylandThread::window_get_mode(DisplayServer::WindowID p_window_id) const {4209ERR_FAIL_COND_V(!windows.has(p_window_id), DisplayServer::WINDOW_MODE_WINDOWED);4210const WindowState &ws = windows[p_window_id];42114212return ws.mode;4213}42144215void WaylandThread::window_request_attention(DisplayServer::WindowID p_window_id) {4216ERR_FAIL_COND(!windows.has(p_window_id));4217WindowState &ws = windows[p_window_id];42184219if (registry.xdg_activation) {4220// Window attention requests are done through the XDG activation protocol.4221xdg_activation_token_v1 *xdg_activation_token = xdg_activation_v1_get_activation_token(registry.xdg_activation);4222xdg_activation_token_v1_add_listener(xdg_activation_token, &xdg_activation_token_listener, &ws);4223xdg_activation_token_v1_commit(xdg_activation_token);4224}4225}42264227void WaylandThread::window_set_idle_inhibition(DisplayServer::WindowID p_window_id, bool p_enable) {4228ERR_FAIL_COND(!windows.has(p_window_id));4229WindowState &ws = windows[p_window_id];42304231if (p_enable) {4232if (ws.registry->wp_idle_inhibit_manager && !ws.wp_idle_inhibitor) {4233ERR_FAIL_NULL(ws.wl_surface);4234ws.wp_idle_inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(ws.registry->wp_idle_inhibit_manager, ws.wl_surface);4235}4236} else {4237if (ws.wp_idle_inhibitor) {4238zwp_idle_inhibitor_v1_destroy(ws.wp_idle_inhibitor);4239ws.wp_idle_inhibitor = nullptr;4240}4241}4242}42434244bool WaylandThread::window_get_idle_inhibition(DisplayServer::WindowID p_window_id) const {4245ERR_FAIL_COND_V(!windows.has(p_window_id), false);4246const WindowState &ws = windows[p_window_id];42474248return ws.wp_idle_inhibitor != nullptr;4249}42504251WaylandThread::ScreenData WaylandThread::screen_get_data(int p_screen) const {4252ERR_FAIL_INDEX_V(p_screen, registry.wl_outputs.size(), ScreenData());42534254return wl_output_get_screen_state(registry.wl_outputs.get(p_screen))->data;4255}42564257int WaylandThread::get_screen_count() const {4258return registry.wl_outputs.size();4259}42604261DisplayServer::WindowID WaylandThread::pointer_get_pointed_window_id() const {4262SeatState *ss = wl_seat_get_seat_state(wl_seat_current);42634264if (ss) {4265// Let's determine the most recently used tablet tool.4266TabletToolState *max_ts = nullptr;4267for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {4268TabletToolState *ts = wp_tablet_tool_get_state(tool);4269ERR_CONTINUE(ts == nullptr);42704271TabletToolData &td = ts->data;42724273if (!max_ts) {4274max_ts = ts;4275continue;4276}42774278if (MAX(td.button_time, td.motion_time) > MAX(max_ts->data.button_time, max_ts->data.motion_time)) {4279max_ts = ts;4280}4281}42824283const PointerData &pd = ss->pointer_data;42844285if (max_ts) {4286TabletToolData &td = max_ts->data;4287if (MAX(td.button_time, td.motion_time) > MAX(pd.button_time, pd.motion_time)) {4288return td.proximal_id;4289}4290}42914292return ss->pointer_data.pointed_id;4293}42944295return DisplayServer::INVALID_WINDOW_ID;4296}4297DisplayServer::WindowID WaylandThread::pointer_get_last_pointed_window_id() const {4298SeatState *ss = wl_seat_get_seat_state(wl_seat_current);42994300if (ss) {4301// Let's determine the most recently used tablet tool.4302TabletToolState *max_ts = nullptr;4303for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {4304TabletToolState *ts = wp_tablet_tool_get_state(tool);4305ERR_CONTINUE(ts == nullptr);43064307TabletToolData &td = ts->data;43084309if (!max_ts) {4310max_ts = ts;4311continue;4312}43134314if (MAX(td.button_time, td.motion_time) > MAX(max_ts->data.button_time, max_ts->data.motion_time)) {4315max_ts = ts;4316}4317}43184319const PointerData &pd = ss->pointer_data;43204321if (max_ts) {4322TabletToolData &td = max_ts->data;4323if (MAX(td.button_time, td.motion_time) > MAX(pd.button_time, pd.motion_time)) {4324return td.last_proximal_id;4325}4326}43274328return ss->pointer_data.last_pointed_id;4329}43304331return DisplayServer::INVALID_WINDOW_ID;4332}43334334void WaylandThread::pointer_set_constraint(PointerConstraint p_constraint) {4335SeatState *ss = wl_seat_get_seat_state(wl_seat_current);43364337if (ss) {4338seat_state_unlock_pointer(ss);43394340if (p_constraint == PointerConstraint::LOCKED) {4341seat_state_lock_pointer(ss);4342} else if (p_constraint == PointerConstraint::CONFINED) {4343seat_state_confine_pointer(ss);4344}4345}43464347pointer_constraint = p_constraint;4348}43494350void WaylandThread::pointer_set_hint(const Point2i &p_hint) {4351SeatState *ss = wl_seat_get_seat_state(wl_seat_current);4352if (!ss) {4353return;4354}43554356WindowState *ws = window_get_state(ss->pointer_data.pointed_id);43574358int hint_x = 0;4359int hint_y = 0;43604361if (ws) {4362// NOTE: It looks like it's not really recommended to convert from4363// "godot-space" to "wayland-space" and in general I received mixed feelings4364// discussing about this. I'm not really sure about the maths behind this but,4365// oh well, we're setting a cursor hint. ¯\_(ツ)_/¯4366// See: https://oftc.irclog.whitequark.org/wayland/2023-08-23#1692756914-16928168184367hint_x = std::round(p_hint.x / window_state_get_scale_factor(ws));4368hint_y = std::round(p_hint.y / window_state_get_scale_factor(ws));4369}43704371if (ss) {4372seat_state_set_hint(ss, hint_x, hint_y);4373}4374}43754376WaylandThread::PointerConstraint WaylandThread::pointer_get_constraint() const {4377return pointer_constraint;4378}43794380BitField<MouseButtonMask> WaylandThread::pointer_get_button_mask() const {4381SeatState *ss = wl_seat_get_seat_state(wl_seat_current);43824383if (ss) {4384return ss->pointer_data.pressed_button_mask;4385}43864387return BitField<MouseButtonMask>();4388}43894390Error WaylandThread::init() {4391#ifdef SOWRAP_ENABLED4392#ifdef DEBUG_ENABLED4393int dylibloader_verbose = 1;4394#else4395int dylibloader_verbose = 0;4396#endif // DEBUG_ENABLED43974398if (initialize_wayland_client(dylibloader_verbose) != 0) {4399WARN_PRINT("Can't load the Wayland client library.");4400return ERR_CANT_CREATE;4401}44024403if (initialize_wayland_cursor(dylibloader_verbose) != 0) {4404WARN_PRINT("Can't load the Wayland cursor library.");4405return ERR_CANT_CREATE;4406}44074408if (initialize_xkbcommon(dylibloader_verbose) != 0) {4409WARN_PRINT("Can't load the XKBcommon library.");4410return ERR_CANT_CREATE;4411}4412#endif // SOWRAP_ENABLED44134414KeyMappingXKB::initialize();44154416wl_display = wl_display_connect(nullptr);4417ERR_FAIL_NULL_V_MSG(wl_display, ERR_CANT_CREATE, "Can't connect to a Wayland display.");44184419thread_data.wl_display = wl_display;44204421events_thread.start(_poll_events_thread, &thread_data);44224423wl_registry = wl_display_get_registry(wl_display);44244425ERR_FAIL_NULL_V_MSG(wl_registry, ERR_UNAVAILABLE, "Can't obtain the Wayland registry global.");44264427registry.wayland_thread = this;44284429wl_registry_add_listener(wl_registry, &wl_registry_listener, ®istry);44304431// Wait for registry to get notified from the compositor.4432wl_display_roundtrip(wl_display);44334434ERR_FAIL_NULL_V_MSG(registry.wl_shm, ERR_UNAVAILABLE, "Can't obtain the Wayland shared memory global.");4435ERR_FAIL_NULL_V_MSG(registry.wl_compositor, ERR_UNAVAILABLE, "Can't obtain the Wayland compositor global.");4436ERR_FAIL_NULL_V_MSG(registry.xdg_wm_base, ERR_UNAVAILABLE, "Can't obtain the Wayland XDG shell global.");44374438if (!registry.xdg_decoration_manager) {4439#ifdef LIBDECOR_ENABLED4440WARN_PRINT("Can't obtain the XDG decoration manager. Libdecor will be used for drawing CSDs, if available.");4441#else4442WARN_PRINT("Can't obtain the XDG decoration manager. Decorations won't show up.");4443#endif // LIBDECOR_ENABLED4444}44454446if (!registry.xdg_activation) {4447WARN_PRINT("Can't obtain the XDG activation global. Attention requesting won't work!");4448}44494450#ifndef DBUS_ENABLED4451if (!registry.wp_idle_inhibit_manager) {4452WARN_PRINT("Can't obtain the idle inhibition manager. The screen might turn off even after calling screen_set_keep_on()!");4453}4454#endif // DBUS_ENABLED44554456if (!registry.wp_fifo_manager_name) {4457WARN_PRINT("FIFO protocol not found! Frame pacing will be degraded.");4458}44594460if (!registry.xdg_toplevel_icon_manager_name) {4461WARN_PRINT("xdg-toplevel-icon protocol not found! Cannot set window icon.");4462}44634464// Wait for seat capabilities.4465wl_display_roundtrip(wl_display);44664467#ifdef LIBDECOR_ENABLED4468bool libdecor_found = true;44694470#ifdef SOWRAP_ENABLED4471if (initialize_libdecor(dylibloader_verbose) != 0) {4472libdecor_found = false;4473}4474#endif // SOWRAP_ENABLED44754476if (libdecor_found) {4477libdecor_context = libdecor_new(wl_display, (struct libdecor_interface *)&libdecor_interface);4478} else {4479print_verbose("libdecor not found. Client-side decorations disabled.");4480}4481#endif // LIBDECOR_ENABLED44824483cursor_theme_name = OS::get_singleton()->get_environment("XCURSOR_THEME");44844485unscaled_cursor_size = OS::get_singleton()->get_environment("XCURSOR_SIZE").to_int();4486if (unscaled_cursor_size <= 0) {4487print_verbose("Detected invalid cursor size preference, defaulting to 24.");4488unscaled_cursor_size = 24;4489}44904491// NOTE: The scale is useful here as it might've been updated by _update_scale.4492bool cursor_theme_loaded = _load_cursor_theme(unscaled_cursor_size * cursor_scale);44934494if (!cursor_theme_loaded) {4495return ERR_CANT_CREATE;4496}44974498// Update the cursor.4499cursor_set_shape(DisplayServer::CURSOR_ARROW);45004501initialized = true;4502return OK;4503}45044505void WaylandThread::cursor_set_visible(bool p_visible) {4506cursor_visible = p_visible;45074508for (struct wl_seat *wl_seat : registry.wl_seats) {4509SeatState *ss = wl_seat_get_seat_state(wl_seat);4510ERR_FAIL_NULL(ss);45114512seat_state_update_cursor(ss);4513}4514}45154516void WaylandThread::cursor_set_shape(DisplayServer::CursorShape p_cursor_shape) {4517cursor_shape = p_cursor_shape;45184519for (struct wl_seat *wl_seat : registry.wl_seats) {4520SeatState *ss = wl_seat_get_seat_state(wl_seat);4521ERR_FAIL_NULL(ss);45224523seat_state_update_cursor(ss);4524}4525}45264527void WaylandThread::cursor_shape_set_custom_image(DisplayServer::CursorShape p_cursor_shape, Ref<Image> p_image, const Point2i &p_hotspot) {4528ERR_FAIL_COND(p_image.is_null());45294530Size2i image_size = p_image->get_size();45314532// NOTE: The stride is the width of the image in bytes.4533unsigned int image_stride = image_size.width * 4;4534unsigned int data_size = image_stride * image_size.height;45354536// We need a shared memory object file descriptor in order to create a4537// wl_buffer through wl_shm.4538int fd = WaylandThread::_allocate_shm_file(data_size);4539ERR_FAIL_COND(fd == -1);45404541CustomCursor &cursor = custom_cursors[p_cursor_shape];4542cursor.hotspot = p_hotspot;45434544if (cursor.wl_buffer) {4545// Clean up the old Wayland buffer.4546wl_buffer_destroy(cursor.wl_buffer);4547}45484549if (cursor.buffer_data) {4550// Clean up the old buffer data.4551munmap(cursor.buffer_data, cursor.buffer_data_size);4552}45534554cursor.buffer_data = (uint32_t *)mmap(nullptr, data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);4555cursor.buffer_data_size = data_size;45564557// Create the Wayland buffer.4558struct wl_shm_pool *wl_shm_pool = wl_shm_create_pool(registry.wl_shm, fd, data_size);4559// TODO: Make sure that WL_SHM_FORMAT_ARGB8888 format is supported. It4560// technically isn't garaunteed to be supported, but I think that'd be a4561// pretty unlikely thing to stumble upon.4562cursor.wl_buffer = wl_shm_pool_create_buffer(wl_shm_pool, 0, image_size.width, image_size.height, image_stride, WL_SHM_FORMAT_ARGB8888);4563wl_shm_pool_destroy(wl_shm_pool);45644565// Fill the cursor buffer with the image data.4566for (unsigned int index = 0; index < (unsigned int)(image_size.width * image_size.height); index++) {4567int row_index = std::floor(index / image_size.width);4568int column_index = (index % int(image_size.width));45694570cursor.buffer_data[index] = p_image->get_pixel(column_index, row_index).to_argb32();45714572// Wayland buffers, unless specified, require associated alpha, so we'll just4573// associate the alpha in-place.4574uint8_t *pixel_data = (uint8_t *)&cursor.buffer_data[index];4575pixel_data[0] = pixel_data[0] * pixel_data[3] / 255;4576pixel_data[1] = pixel_data[1] * pixel_data[3] / 255;4577pixel_data[2] = pixel_data[2] * pixel_data[3] / 255;4578}4579}45804581void WaylandThread::cursor_shape_clear_custom_image(DisplayServer::CursorShape p_cursor_shape) {4582if (custom_cursors.has(p_cursor_shape)) {4583CustomCursor cursor = custom_cursors[p_cursor_shape];4584custom_cursors.erase(p_cursor_shape);45854586if (cursor.wl_buffer) {4587wl_buffer_destroy(cursor.wl_buffer);4588}45894590if (cursor.buffer_data) {4591munmap(cursor.buffer_data, cursor.buffer_data_size);4592}4593}4594}45954596void WaylandThread::window_set_ime_active(const bool p_active, DisplayServer::WindowID p_window_id) {4597SeatState *ss = wl_seat_get_seat_state(wl_seat_current);45984599if (ss && ss->wp_text_input && ss->ime_enabled) {4600if (p_active) {4601ss->ime_active = true;4602zwp_text_input_v3_enable(ss->wp_text_input);4603zwp_text_input_v3_set_cursor_rectangle(ss->wp_text_input, ss->ime_rect.position.x, ss->ime_rect.position.y, ss->ime_rect.size.x, ss->ime_rect.size.y);4604} else {4605ss->ime_active = false;4606ss->ime_text = String();4607ss->ime_text_commit = String();4608ss->ime_cursor = Vector2i();4609zwp_text_input_v3_disable(ss->wp_text_input);4610}4611zwp_text_input_v3_commit(ss->wp_text_input);4612}4613}46144615void WaylandThread::window_set_ime_position(const Point2i &p_pos, DisplayServer::WindowID p_window_id) {4616SeatState *ss = wl_seat_get_seat_state(wl_seat_current);46174618if (ss && ss->wp_text_input && ss->ime_enabled) {4619ss->ime_rect = Rect2i(p_pos, Size2i(1, 10));4620zwp_text_input_v3_set_cursor_rectangle(ss->wp_text_input, ss->ime_rect.position.x, ss->ime_rect.position.y, ss->ime_rect.size.x, ss->ime_rect.size.y);4621zwp_text_input_v3_commit(ss->wp_text_input);4622}4623}46244625int WaylandThread::keyboard_get_layout_count() const {4626SeatState *ss = wl_seat_get_seat_state(wl_seat_current);46274628if (ss && ss->xkb_keymap) {4629return xkb_keymap_num_layouts(ss->xkb_keymap);4630}46314632return 0;4633}46344635int WaylandThread::keyboard_get_current_layout_index() const {4636SeatState *ss = wl_seat_get_seat_state(wl_seat_current);46374638if (ss) {4639return ss->current_layout_index;4640}46414642return 0;4643}46444645void WaylandThread::keyboard_set_current_layout_index(int p_index) {4646SeatState *ss = wl_seat_get_seat_state(wl_seat_current);46474648if (ss) {4649ss->current_layout_index = p_index;4650}4651}46524653String WaylandThread::keyboard_get_layout_name(int p_index) const {4654SeatState *ss = wl_seat_get_seat_state(wl_seat_current);46554656if (ss && ss->xkb_keymap) {4657return String::utf8(xkb_keymap_layout_get_name(ss->xkb_keymap, p_index));4658}46594660return "";4661}46624663Key WaylandThread::keyboard_get_key_from_physical(Key p_key) const {4664SeatState *ss = wl_seat_get_seat_state(wl_seat_current);46654666if (ss && ss->xkb_state) {4667xkb_keycode_t xkb_keycode = KeyMappingXKB::get_xkb_keycode(p_key);4668return KeyMappingXKB::get_keycode(xkb_state_key_get_one_sym(ss->xkb_state, xkb_keycode));4669}46704671return Key::NONE;4672}46734674void WaylandThread::keyboard_echo_keys() {4675SeatState *ss = wl_seat_get_seat_state(wl_seat_current);46764677if (ss) {4678seat_state_echo_keys(ss);4679}4680}46814682void WaylandThread::selection_set_text(const String &p_text) {4683SeatState *ss = wl_seat_get_seat_state(wl_seat_current);46844685if (registry.wl_data_device_manager == nullptr) {4686DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, wl_data_device_manager global not available.");4687return;4688}46894690if (ss == nullptr) {4691DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, current seat not set.");4692return;4693}46944695if (ss->wl_data_device == nullptr) {4696DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, seat doesn't have wl_data_device.");4697return;4698}46994700ss->selection_data = p_text.to_utf8_buffer();47014702if (ss->wl_data_source_selection == nullptr) {4703ss->wl_data_source_selection = wl_data_device_manager_create_data_source(registry.wl_data_device_manager);4704wl_data_source_add_listener(ss->wl_data_source_selection, &wl_data_source_listener, ss);4705wl_data_source_offer(ss->wl_data_source_selection, "text/plain;charset=utf-8");4706wl_data_source_offer(ss->wl_data_source_selection, "text/plain");47074708// TODO: Implement a good way of getting the latest serial from the user.4709wl_data_device_set_selection(ss->wl_data_device, ss->wl_data_source_selection, MAX(ss->pointer_data.button_serial, ss->last_key_pressed_serial));4710}47114712// Wait for the message to get to the server before continuing, otherwise the4713// clipboard update might come with a delay.4714wl_display_roundtrip(wl_display);4715}47164717bool WaylandThread::selection_has_mime(const String &p_mime) const {4718SeatState *ss = wl_seat_get_seat_state(wl_seat_current);47194720if (ss == nullptr) {4721DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");4722return false;4723}47244725OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_selection);4726if (!os) {4727return false;4728}47294730return os->mime_types.has(p_mime);4731}47324733Vector<uint8_t> WaylandThread::selection_get_mime(const String &p_mime) const {4734SeatState *ss = wl_seat_get_seat_state(wl_seat_current);4735if (ss == nullptr) {4736DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");4737return Vector<uint8_t>();4738}47394740if (ss->wl_data_source_selection) {4741// We have a source so the stuff we're pasting is ours. We'll have to pass the4742// data directly or we'd stall waiting for Godot (ourselves) to send us the4743// data :P47444745OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_selection);4746ERR_FAIL_NULL_V(os, Vector<uint8_t>());47474748if (os->mime_types.has(p_mime)) {4749// All righty, we're offering this type. Let's just return the data as is.4750return ss->selection_data;4751}47524753// ... we don't offer that type. Oh well.4754return Vector<uint8_t>();4755}47564757return _wl_data_offer_read(wl_display, p_mime.utf8().get_data(), ss->wl_data_offer_selection);4758}47594760bool WaylandThread::primary_has_mime(const String &p_mime) const {4761SeatState *ss = wl_seat_get_seat_state(wl_seat_current);47624763if (ss == nullptr) {4764DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");4765return false;4766}47674768OfferState *os = wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer);4769if (!os) {4770return false;4771}47724773return os->mime_types.has(p_mime);4774}47754776Vector<uint8_t> WaylandThread::primary_get_mime(const String &p_mime) const {4777SeatState *ss = wl_seat_get_seat_state(wl_seat_current);4778if (ss == nullptr) {4779DEBUG_LOG_WAYLAND_THREAD("Couldn't get primary, current seat not set.");4780return Vector<uint8_t>();4781}47824783if (ss->wp_primary_selection_source) {4784// We have a source so the stuff we're pasting is ours. We'll have to pass the4785// data directly or we'd stall waiting for Godot (ourselves) to send us the4786// data :P47874788OfferState *os = wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer);4789ERR_FAIL_NULL_V(os, Vector<uint8_t>());47904791if (os->mime_types.has(p_mime)) {4792// All righty, we're offering this type. Let's just return the data as is.4793return ss->selection_data;4794}47954796// ... we don't offer that type. Oh well.4797return Vector<uint8_t>();4798}47994800return _wp_primary_selection_offer_read(wl_display, p_mime.utf8().get_data(), ss->wp_primary_selection_offer);4801}48024803void WaylandThread::primary_set_text(const String &p_text) {4804SeatState *ss = wl_seat_get_seat_state(wl_seat_current);48054806if (registry.wp_primary_selection_device_manager == nullptr) {4807DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary, protocol not available.");4808return;4809}48104811if (ss == nullptr) {4812DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary, current seat not set.");4813return;4814}48154816if (ss->wp_primary_selection_device == nullptr) {4817DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary selection, seat doesn't have wp_primary_selection_device.");4818return;4819}48204821ss->primary_data = p_text.to_utf8_buffer();48224823if (ss->wp_primary_selection_source == nullptr) {4824ss->wp_primary_selection_source = zwp_primary_selection_device_manager_v1_create_source(registry.wp_primary_selection_device_manager);4825zwp_primary_selection_source_v1_add_listener(ss->wp_primary_selection_source, &wp_primary_selection_source_listener, ss);4826zwp_primary_selection_source_v1_offer(ss->wp_primary_selection_source, "text/plain;charset=utf-8");4827zwp_primary_selection_source_v1_offer(ss->wp_primary_selection_source, "text/plain");48284829// TODO: Implement a good way of getting the latest serial from the user.4830zwp_primary_selection_device_v1_set_selection(ss->wp_primary_selection_device, ss->wp_primary_selection_source, MAX(ss->pointer_data.button_serial, ss->last_key_pressed_serial));4831}48324833// Wait for the message to get to the server before continuing, otherwise the4834// clipboard update might come with a delay.4835wl_display_roundtrip(wl_display);4836}48374838void WaylandThread::commit_surfaces() {4839for (KeyValue<DisplayServer::WindowID, WindowState> &pair : windows) {4840wl_surface_commit(pair.value.wl_surface);4841}4842}48434844void WaylandThread::set_frame() {4845frame = true;4846}48474848bool WaylandThread::get_reset_frame() {4849bool old_frame = frame;4850frame = false;48514852return old_frame;4853}48544855// Dispatches events until a frame event is received, a window is reported as4856// suspended or the timeout expires.4857bool WaylandThread::wait_frame_suspend_ms(int p_timeout) {4858// This is a bit of a chicken and egg thing... Looks like the main event loop4859// has to call its rightfully forever-blocking poll right in between4860// `wl_display_prepare_read` and `wl_display_read`. This means, that it will4861// basically be guaranteed to stay stuck in a "prepare read" state, where it4862// will block any other attempt at reading the display fd, such as ours. The4863// solution? Let's make sure the mutex is locked (it should) and unblock the4864// main thread with a roundtrip!4865MutexLock mutex_lock(mutex);4866wl_display_roundtrip(wl_display);48674868if (is_suspended()) {4869// All windows are suspended! The compositor is telling us _explicitly_ that4870// we don't need to draw, without letting us guess through the frame event's4871// timing and stuff like that. Our job here is done.4872return false;4873}48744875if (frame) {4876// We already have a frame! Probably it got there while the caller locked :D4877frame = false;4878return true;4879}48804881struct pollfd poll_fd;4882poll_fd.fd = wl_display_get_fd(wl_display);4883poll_fd.events = POLLIN | POLLHUP;48844885int begin_ms = OS::get_singleton()->get_ticks_msec();4886int remaining_ms = p_timeout;48874888while (remaining_ms > 0) {4889// Empty the event queue while it's full.4890while (wl_display_prepare_read(wl_display) != 0) {4891if (wl_display_dispatch_pending(wl_display) == -1) {4892// Oh no. We'll check and handle any display error below.4893break;4894}48954896if (is_suspended()) {4897return false;4898}48994900if (frame) {4901// We had a frame event in the queue :D4902frame = false;4903return true;4904}4905}49064907int werror = wl_display_get_error(wl_display);49084909if (werror) {4910if (werror == EPROTO) {4911struct wl_interface *wl_interface = nullptr;4912uint32_t id = 0;49134914int error_code = wl_display_get_protocol_error(wl_display, (const struct wl_interface **)&wl_interface, &id);4915CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id));4916} else {4917CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror));4918}4919}49204921wl_display_flush(wl_display);49224923// Wait for the event file descriptor to have new data.4924poll(&poll_fd, 1, remaining_ms);49254926if (poll_fd.revents | POLLIN) {4927// Load the queues with fresh new data.4928wl_display_read_events(wl_display);4929} else {4930// Oh well... Stop signaling that we want to read.4931wl_display_cancel_read(wl_display);49324933// We've got no new events :(4934// We won't even bother with checking the frame flag.4935return false;4936}49374938// Let's try dispatching now...4939wl_display_dispatch_pending(wl_display);49404941if (is_suspended()) {4942return false;4943}49444945if (frame) {4946frame = false;4947return true;4948}49494950remaining_ms -= OS::get_singleton()->get_ticks_msec() - begin_ms;4951}49524953DEBUG_LOG_WAYLAND_THREAD("Frame timeout.");4954return false;4955}49564957uint64_t WaylandThread::window_get_last_frame_time(DisplayServer::WindowID p_window_id) const {4958ERR_FAIL_COND_V(!windows.has(p_window_id), false);4959return windows[p_window_id].last_frame_time;4960}49614962bool WaylandThread::window_is_suspended(DisplayServer::WindowID p_window_id) const {4963ERR_FAIL_COND_V(!windows.has(p_window_id), false);4964return windows[p_window_id].suspended;4965}49664967bool WaylandThread::is_fifo_available() const {4968return registry.wp_fifo_manager_name != 0;4969}49704971bool WaylandThread::is_suspended() const {4972for (const KeyValue<DisplayServer::WindowID, WindowState> &E : windows) {4973if (!E.value.suspended) {4974return false;4975}4976}49774978return true;4979}49804981void WaylandThread::destroy() {4982if (!initialized) {4983return;4984}49854986if (wl_display && events_thread.is_started()) {4987thread_data.thread_done.set();49884989// By sending a roundtrip message we're unblocking the polling thread so that4990// it can realize that it's done and also handle every event that's left.4991wl_display_roundtrip(wl_display);49924993events_thread.wait_to_finish();4994}49954996for (KeyValue<DisplayServer::WindowID, WindowState> &pair : windows) {4997WindowState &ws = pair.value;4998if (ws.wp_fractional_scale) {4999wp_fractional_scale_v1_destroy(ws.wp_fractional_scale);5000}50015002if (ws.wp_viewport) {5003wp_viewport_destroy(ws.wp_viewport);5004}50055006if (ws.frame_callback) {5007wl_callback_destroy(ws.frame_callback);5008}50095010#ifdef LIBDECOR_ENABLED5011if (ws.libdecor_frame) {5012libdecor_frame_close(ws.libdecor_frame);5013}5014#endif // LIBDECOR_ENABLED50155016if (ws.xdg_toplevel) {5017xdg_toplevel_destroy(ws.xdg_toplevel);5018}50195020if (ws.xdg_surface) {5021xdg_surface_destroy(ws.xdg_surface);5022}50235024if (ws.wl_surface) {5025wl_surface_destroy(ws.wl_surface);5026}5027}50285029for (struct wl_seat *wl_seat : registry.wl_seats) {5030SeatState *ss = wl_seat_get_seat_state(wl_seat);5031ERR_FAIL_NULL(ss);50325033wl_seat_destroy(wl_seat);50345035xkb_context_unref(ss->xkb_context);5036xkb_state_unref(ss->xkb_state);5037xkb_keymap_unref(ss->xkb_keymap);50385039if (ss->wl_keyboard) {5040wl_keyboard_destroy(ss->wl_keyboard);5041}50425043if (ss->keymap_buffer) {5044munmap((void *)ss->keymap_buffer, ss->keymap_buffer_size);5045}50465047if (ss->wl_pointer) {5048wl_pointer_destroy(ss->wl_pointer);5049}50505051if (ss->cursor_frame_callback) {5052// We don't need to set a null userdata for safety as the thread is done.5053wl_callback_destroy(ss->cursor_frame_callback);5054}50555056if (ss->cursor_surface) {5057wl_surface_destroy(ss->cursor_surface);5058}50595060if (ss->wl_data_device) {5061wl_data_device_destroy(ss->wl_data_device);5062}50635064if (ss->wp_cursor_shape_device) {5065wp_cursor_shape_device_v1_destroy(ss->wp_cursor_shape_device);5066}50675068if (ss->wp_relative_pointer) {5069zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);5070}50715072if (ss->wp_locked_pointer) {5073zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);5074}50755076if (ss->wp_confined_pointer) {5077zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);5078}50795080if (ss->wp_tablet_seat) {5081zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat);5082}50835084for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {5085TabletToolState *state = wp_tablet_tool_get_state(tool);5086if (state) {5087memdelete(state);5088}50895090zwp_tablet_tool_v2_destroy(tool);5091}50925093memdelete(ss);5094}50955096for (struct wl_output *wl_output : registry.wl_outputs) {5097ERR_FAIL_NULL(wl_output);50985099memdelete(wl_output_get_screen_state(wl_output));5100wl_output_destroy(wl_output);5101}51025103if (wl_cursor_theme) {5104wl_cursor_theme_destroy(wl_cursor_theme);5105}51065107if (registry.wp_idle_inhibit_manager) {5108zwp_idle_inhibit_manager_v1_destroy(registry.wp_idle_inhibit_manager);5109}51105111if (registry.wp_pointer_constraints) {5112zwp_pointer_constraints_v1_destroy(registry.wp_pointer_constraints);5113}51145115if (registry.wp_pointer_gestures) {5116zwp_pointer_gestures_v1_destroy(registry.wp_pointer_gestures);5117}51185119if (registry.wp_relative_pointer_manager) {5120zwp_relative_pointer_manager_v1_destroy(registry.wp_relative_pointer_manager);5121}51225123if (registry.xdg_activation) {5124xdg_activation_v1_destroy(registry.xdg_activation);5125}51265127if (registry.xdg_system_bell) {5128xdg_system_bell_v1_destroy(registry.xdg_system_bell);5129}51305131if (registry.xdg_toplevel_icon_manager) {5132xdg_toplevel_icon_manager_v1_destroy(registry.xdg_toplevel_icon_manager);51335134if (xdg_icon) {5135xdg_toplevel_icon_v1_destroy(xdg_icon);5136}51375138if (icon_buffer) {5139wl_buffer_destroy(icon_buffer);5140}5141}51425143if (registry.xdg_decoration_manager) {5144zxdg_decoration_manager_v1_destroy(registry.xdg_decoration_manager);5145}51465147if (registry.wp_cursor_shape_manager) {5148wp_cursor_shape_manager_v1_destroy(registry.wp_cursor_shape_manager);5149}51505151if (registry.wp_fractional_scale_manager) {5152wp_fractional_scale_manager_v1_destroy(registry.wp_fractional_scale_manager);5153}51545155if (registry.wp_viewporter) {5156wp_viewporter_destroy(registry.wp_viewporter);5157}51585159if (registry.xdg_wm_base) {5160xdg_wm_base_destroy(registry.xdg_wm_base);5161}51625163// NOTE: Deprecated.5164if (registry.xdg_exporter_v1) {5165zxdg_exporter_v1_destroy(registry.xdg_exporter_v1);5166}51675168if (registry.xdg_exporter_v2) {5169zxdg_exporter_v2_destroy(registry.xdg_exporter_v2);5170}5171if (registry.wl_shm) {5172wl_shm_destroy(registry.wl_shm);5173}51745175if (registry.wl_compositor) {5176wl_compositor_destroy(registry.wl_compositor);5177}51785179if (wl_registry) {5180wl_registry_destroy(wl_registry);5181}51825183if (wl_display) {5184wl_display_disconnect(wl_display);5185}5186}51875188#endif // WAYLAND_ENABLED518951905191