Path: blob/master/platform/linuxbsd/wayland/wayland_thread.cpp
10278 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#ifdef WAYLAND_ENABLED3334#ifdef __FreeBSD__35#include <dev/evdev/input-event-codes.h>36#else37// Assume Linux.38#include <linux/input-event-codes.h>39#endif4041// For the actual polling thread.42#include <poll.h>4344// For shared memory buffer creation.45#include <fcntl.h>46#include <sys/mman.h>47#include <unistd.h>4849// Fix the wl_array_for_each macro to work with C++. This is based on the50// original from `wayland-util.h` in the Wayland client library.51#undef wl_array_for_each52#define wl_array_for_each(pos, array) \53for (pos = (decltype(pos))(array)->data; (const char *)pos < ((const char *)(array)->data + (array)->size); (pos)++)5455#define WAYLAND_THREAD_DEBUG_LOGS_ENABLED56#ifdef WAYLAND_THREAD_DEBUG_LOGS_ENABLED57#define DEBUG_LOG_WAYLAND_THREAD(...) print_verbose(__VA_ARGS__)58#else59#define DEBUG_LOG_WAYLAND_THREAD(...)60#endif6162// Since we're never going to use this interface directly, it's not worth63// generating the whole deal.64#define FIFO_INTERFACE_NAME "wp_fifo_manager_v1"6566// Read the content pointed by fd into a Vector<uint8_t>.67Vector<uint8_t> WaylandThread::_read_fd(int fd) {68// This is pretty much an arbitrary size.69uint32_t chunk_size = 2048;7071LocalVector<uint8_t> data;72data.resize(chunk_size);7374uint32_t bytes_read = 0;7576while (true) {77ssize_t last_bytes_read = read(fd, data.ptr() + bytes_read, chunk_size);78if (last_bytes_read < 0) {79ERR_PRINT(vformat("Read error %d.", errno));8081data.clear();82break;83}8485if (last_bytes_read == 0) {86// We're done, we've reached the EOF.87DEBUG_LOG_WAYLAND_THREAD(vformat("Done reading %d bytes.", bytes_read));8889close(fd);9091data.resize(bytes_read);92break;93}9495DEBUG_LOG_WAYLAND_THREAD(vformat("Read chunk of %d bytes.", last_bytes_read));9697bytes_read += last_bytes_read;9899// Increase the buffer size by one chunk in preparation of the next read.100data.resize(bytes_read + chunk_size);101}102103return data;104}105106// Based on the wayland book's shared memory boilerplate (PD/CC0).107// See: https://wayland-book.com/surfaces/shared-memory.html108int WaylandThread::_allocate_shm_file(size_t size) {109int retries = 100;110111do {112// Generate a random name.113char name[] = "/wl_shm-godot-XXXXXX";114for (long unsigned int i = sizeof(name) - 7; i < sizeof(name) - 1; i++) {115name[i] = Math::random('A', 'Z');116}117118// Try to open a shared memory object with that name.119int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);120if (fd >= 0) {121// Success, unlink its name as we just need the file descriptor.122shm_unlink(name);123124// Resize the file to the requested length.125int ret;126do {127ret = ftruncate(fd, size);128} while (ret < 0 && errno == EINTR);129130if (ret < 0) {131close(fd);132return -1;133}134135return fd;136}137138retries--;139} while (retries > 0 && errno == EEXIST);140141return -1;142}143144// Return the content of a wl_data_offer.145Vector<uint8_t> WaylandThread::_wl_data_offer_read(struct wl_display *p_display, const char *p_mime, struct wl_data_offer *p_offer) {146if (!p_offer) {147return Vector<uint8_t>();148}149150int fds[2];151if (pipe(fds) == 0) {152wl_data_offer_receive(p_offer, p_mime, fds[1]);153154// Let the compositor know about the pipe.155// NOTE: It's important to just flush and not roundtrip here as we would risk156// running some cleanup event, like for example `wl_data_device::leave`. We're157// going to wait for the message anyways as the read will probably block if158// the compositor doesn't read from the other end of the pipe.159wl_display_flush(p_display);160161// Close the write end of the pipe, which we don't need and would otherwise162// just stall our next `read`s.163close(fds[1]);164165return _read_fd(fds[0]);166}167168return Vector<uint8_t>();169}170171// Read the content of a wp_primary_selection_offer.172Vector<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) {173if (!p_offer) {174return Vector<uint8_t>();175}176177int fds[2];178if (pipe(fds) == 0) {179zwp_primary_selection_offer_v1_receive(p_offer, p_mime, fds[1]);180181// NOTE: It's important to just flush and not roundtrip here as we would risk182// running some cleanup event, like for example `wl_data_device::leave`. We're183// going to wait for the message anyways as the read will probably block if184// the compositor doesn't read from the other end of the pipe.185wl_display_flush(p_display);186187// Close the write end of the pipe, which we don't need and would otherwise188// just stall our next `read`s.189close(fds[1]);190191return _read_fd(fds[0]);192}193194return Vector<uint8_t>();195}196197Ref<InputEventKey> WaylandThread::_seat_state_get_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed) {198Ref<InputEventKey> event;199200ERR_FAIL_NULL_V(p_ss, event);201202Key shifted_key = KeyMappingXKB::get_keycode(xkb_state_key_get_one_sym(p_ss->xkb_state, p_keycode));203204Key plain_key = Key::NONE;205// NOTE: xkbcommon's API really encourages to apply the modifier state but we206// only want a "plain" symbol so that we can convert it into a godot keycode.207const xkb_keysym_t *syms = nullptr;208int num_sys = xkb_keymap_key_get_syms_by_level(p_ss->xkb_keymap, p_keycode, p_ss->current_layout_index, 0, &syms);209if (num_sys > 0 && syms) {210plain_key = KeyMappingXKB::get_keycode(syms[0]);211}212213Key physical_keycode = KeyMappingXKB::get_scancode(p_keycode);214KeyLocation key_location = KeyMappingXKB::get_location(p_keycode);215uint32_t unicode = xkb_state_key_get_utf32(p_ss->xkb_state, p_keycode);216217Key keycode = Key::NONE;218219if ((shifted_key & Key::SPECIAL) != Key::NONE || (plain_key & Key::SPECIAL) != Key::NONE) {220keycode = shifted_key;221}222223if (keycode == Key::NONE) {224keycode = plain_key;225}226227if (keycode == Key::NONE) {228keycode = physical_keycode;229}230231if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {232keycode -= 'a' - 'A';233}234235if (physical_keycode == Key::NONE && keycode == Key::NONE && unicode == 0) {236return event;237}238239event.instantiate();240241event->set_window_id(p_ss->focused_id);242243// Set all pressed modifiers.244event->set_shift_pressed(p_ss->shift_pressed);245event->set_ctrl_pressed(p_ss->ctrl_pressed);246event->set_alt_pressed(p_ss->alt_pressed);247event->set_meta_pressed(p_ss->meta_pressed);248249event->set_pressed(p_pressed);250event->set_keycode(keycode);251event->set_physical_keycode(physical_keycode);252event->set_location(key_location);253254if (unicode != 0) {255event->set_key_label(fix_key_label(unicode, keycode));256} else {257event->set_key_label(keycode);258}259260if (p_pressed) {261event->set_unicode(fix_unicode(unicode));262}263264// Taken from DisplayServerX11.265if (event->get_keycode() == Key::BACKTAB) {266// Make it consistent across platforms.267event->set_keycode(Key::TAB);268event->set_physical_keycode(Key::TAB);269event->set_shift_pressed(true);270}271272return event;273}274275// NOTE: Due to the nature of the way keys are encoded, there's an ambiguity276// regarding "special" keys. In other words: there's no reliable way of277// switching between a special key and a character key if not marking a278// different Godot keycode, even if we're actually using the same XKB raw279// keycode. This means that, during this switch, the old key will get "stuck",280// as it will never receive a release event. This method returns the necessary281// event to fix this if needed.282Ref<InputEventKey> WaylandThread::_seat_state_get_unstuck_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed, Key p_key) {283Ref<InputEventKey> event;284285if (p_pressed) {286Key *old_key = p_ss->pressed_keycodes.getptr(p_keycode);287if (old_key != nullptr && *old_key != p_key) {288print_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)));289event = _seat_state_get_key_event(p_ss, p_keycode, false);290if (event.is_valid()) {291event->set_keycode(*old_key);292}293}294p_ss->pressed_keycodes[p_keycode] = p_key;295} else {296p_ss->pressed_keycodes.erase(p_keycode);297}298299return event;300}301302void WaylandThread::_set_current_seat(struct wl_seat *p_seat) {303if (p_seat == wl_seat_current) {304return;305}306307SeatState *old_state = wl_seat_get_seat_state(wl_seat_current);308309if (old_state) {310seat_state_unlock_pointer(old_state);311}312313SeatState *new_state = wl_seat_get_seat_state(p_seat);314seat_state_unlock_pointer(new_state);315316wl_seat_current = p_seat;317pointer_set_constraint(pointer_constraint);318}319320// Returns whether it loaded the theme or not.321bool WaylandThread::_load_cursor_theme(int p_cursor_size) {322if (wl_cursor_theme) {323wl_cursor_theme_destroy(wl_cursor_theme);324wl_cursor_theme = nullptr;325}326327if (cursor_theme_name.is_empty()) {328cursor_theme_name = "default";329}330331print_verbose(vformat("Loading cursor theme \"%s\" size %d.", cursor_theme_name, p_cursor_size));332333wl_cursor_theme = wl_cursor_theme_load(cursor_theme_name.utf8().get_data(), p_cursor_size, registry.wl_shm);334335ERR_FAIL_NULL_V_MSG(wl_cursor_theme, false, "Can't load any cursor theme.");336337static const char *cursor_names[] = {338"left_ptr",339"xterm",340"hand2",341"cross",342"watch",343"left_ptr_watch",344"fleur",345"dnd-move",346"crossed_circle",347"v_double_arrow",348"h_double_arrow",349"size_bdiag",350"size_fdiag",351"move",352"row_resize",353"col_resize",354"question_arrow"355};356357static const char *cursor_names_fallback[] = {358nullptr,359nullptr,360"pointer",361"cross",362"wait",363"progress",364"grabbing",365"hand1",366"forbidden",367"ns-resize",368"ew-resize",369"fd_double_arrow",370"bd_double_arrow",371"fleur",372"sb_v_double_arrow",373"sb_h_double_arrow",374"help"375};376377for (int i = 0; i < DisplayServer::CURSOR_MAX; i++) {378struct wl_cursor *cursor = wl_cursor_theme_get_cursor(wl_cursor_theme, cursor_names[i]);379380if (!cursor && cursor_names_fallback[i]) {381cursor = wl_cursor_theme_get_cursor(wl_cursor_theme, cursor_names_fallback[i]);382}383384if (cursor && cursor->image_count > 0) {385wl_cursors[i] = cursor;386} else {387wl_cursors[i] = nullptr;388print_verbose("Failed loading cursor: " + String(cursor_names[i]));389}390}391392return true;393}394395void WaylandThread::_update_scale(int p_scale) {396if (p_scale <= cursor_scale) {397return;398}399400print_verbose(vformat("Bumping cursor scale to %d", p_scale));401402// There's some display that's bigger than the cache, let's update it.403cursor_scale = p_scale;404405if (wl_cursor_theme == nullptr) {406// Ugh. Either we're still initializing (this must've been called from the407// first roundtrips) or we had some error while doing so. We'll trust that it408// will be updated for us if needed.409return;410}411412int cursor_size = unscaled_cursor_size * p_scale;413414if (_load_cursor_theme(cursor_size)) {415for (struct wl_seat *wl_seat : registry.wl_seats) {416SeatState *ss = wl_seat_get_seat_state(wl_seat);417ERR_FAIL_NULL(ss);418419seat_state_update_cursor(ss);420}421}422}423424void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) {425RegistryState *registry = (RegistryState *)data;426ERR_FAIL_NULL(registry);427428if (strcmp(interface, wl_shm_interface.name) == 0) {429registry->wl_shm = (struct wl_shm *)wl_registry_bind(wl_registry, name, &wl_shm_interface, 1);430registry->wl_shm_name = name;431return;432}433434// NOTE: Deprecated.435if (strcmp(interface, zxdg_exporter_v1_interface.name) == 0) {436registry->xdg_exporter_v1 = (struct zxdg_exporter_v1 *)wl_registry_bind(wl_registry, name, &zxdg_exporter_v1_interface, 1);437registry->xdg_exporter_v1_name = name;438return;439}440441if (strcmp(interface, zxdg_exporter_v2_interface.name) == 0) {442registry->xdg_exporter_v2 = (struct zxdg_exporter_v2 *)wl_registry_bind(wl_registry, name, &zxdg_exporter_v2_interface, 1);443registry->xdg_exporter_v2_name = name;444return;445}446447if (strcmp(interface, wl_compositor_interface.name) == 0) {448registry->wl_compositor = (struct wl_compositor *)wl_registry_bind(wl_registry, name, &wl_compositor_interface, CLAMP((int)version, 1, 6));449registry->wl_compositor_name = name;450return;451}452453if (strcmp(interface, wl_data_device_manager_interface.name) == 0) {454registry->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));455registry->wl_data_device_manager_name = name;456457// This global creates some seat data. Let's do that for the ones already available.458for (struct wl_seat *wl_seat : registry->wl_seats) {459SeatState *ss = wl_seat_get_seat_state(wl_seat);460ERR_FAIL_NULL(ss);461462if (ss->wl_data_device == nullptr) {463ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat);464wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss);465}466}467return;468}469470if (strcmp(interface, wl_output_interface.name) == 0) {471struct wl_output *wl_output = (struct wl_output *)wl_registry_bind(wl_registry, name, &wl_output_interface, CLAMP((int)version, 1, 4));472wl_proxy_tag_godot((struct wl_proxy *)wl_output);473474registry->wl_outputs.push_back(wl_output);475476ScreenState *ss = memnew(ScreenState);477ss->wl_output_name = name;478ss->wayland_thread = registry->wayland_thread;479480wl_proxy_tag_godot((struct wl_proxy *)wl_output);481wl_output_add_listener(wl_output, &wl_output_listener, ss);482return;483}484485if (strcmp(interface, wl_seat_interface.name) == 0) {486struct wl_seat *wl_seat = (struct wl_seat *)wl_registry_bind(wl_registry, name, &wl_seat_interface, CLAMP((int)version, 1, 9));487wl_proxy_tag_godot((struct wl_proxy *)wl_seat);488489SeatState *ss = memnew(SeatState);490ss->wl_seat = wl_seat;491ss->wl_seat_name = name;492493ss->registry = registry;494ss->wayland_thread = registry->wayland_thread;495496// Some extra stuff depends on other globals. We'll initialize them if the497// globals are already there, otherwise we'll have to do that once and if they498// get announced.499//500// NOTE: Don't forget to also bind/destroy with the respective global.501if (!ss->wl_data_device && registry->wl_data_device_manager) {502// Clipboard & DnD.503ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat);504wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss);505}506507if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) {508// Primary selection.509ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat);510zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss);511}512513if (!ss->wp_tablet_seat && registry->wp_tablet_manager) {514// Tablet.515ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat);516zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss);517}518519if (!ss->wp_text_input && registry->wp_text_input_manager) {520// IME.521ss->wp_text_input = zwp_text_input_manager_v3_get_text_input(registry->wp_text_input_manager, wl_seat);522zwp_text_input_v3_add_listener(ss->wp_text_input, &wp_text_input_listener, ss);523}524525registry->wl_seats.push_back(wl_seat);526527wl_seat_add_listener(wl_seat, &wl_seat_listener, ss);528529if (registry->wayland_thread->wl_seat_current == nullptr) {530registry->wayland_thread->_set_current_seat(wl_seat);531}532533return;534}535536if (strcmp(interface, xdg_wm_base_interface.name) == 0) {537registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, CLAMP((int)version, 1, 6));538registry->xdg_wm_base_name = name;539540xdg_wm_base_add_listener(registry->xdg_wm_base, &xdg_wm_base_listener, nullptr);541return;542}543544if (strcmp(interface, wp_viewporter_interface.name) == 0) {545registry->wp_viewporter = (struct wp_viewporter *)wl_registry_bind(wl_registry, name, &wp_viewporter_interface, 1);546registry->wp_viewporter_name = name;547}548549if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {550registry->wp_cursor_shape_manager = (struct wp_cursor_shape_manager_v1 *)wl_registry_bind(wl_registry, name, &wp_cursor_shape_manager_v1_interface, 1);551registry->wp_cursor_shape_manager_name = name;552return;553}554555if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) {556registry->wp_fractional_scale_manager = (struct wp_fractional_scale_manager_v1 *)wl_registry_bind(wl_registry, name, &wp_fractional_scale_manager_v1_interface, 1);557registry->wp_fractional_scale_manager_name = name;558559// NOTE: We're not mapping the fractional scale object here because this is560// supposed to be a "startup global". If for some reason this isn't true (who561// knows), add a conditional branch for creating the add-on object.562}563564if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {565registry->xdg_decoration_manager = (struct zxdg_decoration_manager_v1 *)wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1);566registry->xdg_decoration_manager_name = name;567return;568}569570if (strcmp(interface, xdg_system_bell_v1_interface.name) == 0) {571registry->xdg_system_bell = (struct xdg_system_bell_v1 *)wl_registry_bind(wl_registry, name, &xdg_system_bell_v1_interface, 1);572registry->xdg_system_bell_name = name;573return;574}575576if (strcmp(interface, xdg_activation_v1_interface.name) == 0) {577registry->xdg_activation = (struct xdg_activation_v1 *)wl_registry_bind(wl_registry, name, &xdg_activation_v1_interface, 1);578registry->xdg_activation_name = name;579return;580}581582if (strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0) {583registry->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);584585// This global creates some seat data. Let's do that for the ones already available.586for (struct wl_seat *wl_seat : registry->wl_seats) {587SeatState *ss = wl_seat_get_seat_state(wl_seat);588ERR_FAIL_NULL(ss);589590if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) {591ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat);592zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss);593}594}595}596597if (strcmp(interface, zwp_relative_pointer_manager_v1_interface.name) == 0) {598registry->wp_relative_pointer_manager = (struct zwp_relative_pointer_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_relative_pointer_manager_v1_interface, 1);599registry->wp_relative_pointer_manager_name = name;600return;601}602603if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) {604registry->wp_pointer_constraints = (struct zwp_pointer_constraints_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1);605registry->wp_pointer_constraints_name = name;606return;607}608609if (strcmp(interface, zwp_pointer_gestures_v1_interface.name) == 0) {610registry->wp_pointer_gestures = (struct zwp_pointer_gestures_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_gestures_v1_interface, 1);611registry->wp_pointer_gestures_name = name;612return;613}614615if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {616registry->wp_idle_inhibit_manager = (struct zwp_idle_inhibit_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_idle_inhibit_manager_v1_interface, 1);617registry->wp_idle_inhibit_manager_name = name;618return;619}620621if (strcmp(interface, zwp_tablet_manager_v2_interface.name) == 0) {622registry->wp_tablet_manager = (struct zwp_tablet_manager_v2 *)wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1);623registry->wp_tablet_manager_name = name;624625// This global creates some seat data. Let's do that for the ones already available.626for (struct wl_seat *wl_seat : registry->wl_seats) {627SeatState *ss = wl_seat_get_seat_state(wl_seat);628ERR_FAIL_NULL(ss);629630ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat);631zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss);632}633634return;635}636637if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) {638registry->wp_text_input_manager = (struct zwp_text_input_manager_v3 *)wl_registry_bind(wl_registry, name, &zwp_text_input_manager_v3_interface, 1);639registry->wp_text_input_manager_name = name;640641// This global creates some seat data. Let's do that for the ones already available.642for (struct wl_seat *wl_seat : registry->wl_seats) {643SeatState *ss = wl_seat_get_seat_state(wl_seat);644ERR_FAIL_NULL(ss);645646ss->wp_text_input = zwp_text_input_manager_v3_get_text_input(registry->wp_text_input_manager, wl_seat);647zwp_text_input_v3_add_listener(ss->wp_text_input, &wp_text_input_listener, ss);648}649650return;651}652653if (strcmp(interface, FIFO_INTERFACE_NAME) == 0) {654registry->wp_fifo_manager_name = name;655}656}657658void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) {659RegistryState *registry = (RegistryState *)data;660ERR_FAIL_NULL(registry);661662if (name == registry->wl_shm_name) {663if (registry->wl_shm) {664wl_shm_destroy(registry->wl_shm);665registry->wl_shm = nullptr;666}667668registry->wl_shm_name = 0;669670return;671}672673// NOTE: Deprecated.674if (name == registry->xdg_exporter_v1_name) {675if (registry->xdg_exporter_v1) {676zxdg_exporter_v1_destroy(registry->xdg_exporter_v1);677registry->xdg_exporter_v1 = nullptr;678}679680registry->xdg_exporter_v1_name = 0;681682return;683}684685if (name == registry->xdg_exporter_v2_name) {686if (registry->xdg_exporter_v2) {687zxdg_exporter_v2_destroy(registry->xdg_exporter_v2);688registry->xdg_exporter_v2 = nullptr;689}690691registry->xdg_exporter_v2_name = 0;692693return;694}695696if (name == registry->wl_compositor_name) {697if (registry->wl_compositor) {698wl_compositor_destroy(registry->wl_compositor);699registry->wl_compositor = nullptr;700}701702registry->wl_compositor_name = 0;703704return;705}706707if (name == registry->wl_data_device_manager_name) {708if (registry->wl_data_device_manager) {709wl_data_device_manager_destroy(registry->wl_data_device_manager);710registry->wl_data_device_manager = nullptr;711}712713registry->wl_data_device_manager_name = 0;714715// This global is used to create some seat data. Let's clean it.716for (struct wl_seat *wl_seat : registry->wl_seats) {717SeatState *ss = wl_seat_get_seat_state(wl_seat);718ERR_FAIL_NULL(ss);719720if (ss->wl_data_device) {721wl_data_device_destroy(ss->wl_data_device);722ss->wl_data_device = nullptr;723}724725ss->wl_data_device = nullptr;726}727728return;729}730731if (name == registry->xdg_wm_base_name) {732if (registry->xdg_wm_base) {733xdg_wm_base_destroy(registry->xdg_wm_base);734registry->xdg_wm_base = nullptr;735}736737registry->xdg_wm_base_name = 0;738739return;740}741742if (name == registry->wp_viewporter_name) {743for (KeyValue<DisplayServer::WindowID, WindowState> &pair : registry->wayland_thread->windows) {744WindowState ws = pair.value;745if (registry->wp_viewporter) {746wp_viewporter_destroy(registry->wp_viewporter);747registry->wp_viewporter = nullptr;748}749750if (ws.wp_viewport) {751wp_viewport_destroy(ws.wp_viewport);752ws.wp_viewport = nullptr;753}754}755756registry->wp_viewporter_name = 0;757758return;759}760761if (name == registry->wp_cursor_shape_manager_name) {762if (registry->wp_cursor_shape_manager) {763wp_cursor_shape_manager_v1_destroy(registry->wp_cursor_shape_manager);764registry->wp_cursor_shape_manager = nullptr;765}766767registry->wp_cursor_shape_manager_name = 0;768769for (struct wl_seat *wl_seat : registry->wl_seats) {770SeatState *ss = wl_seat_get_seat_state(wl_seat);771ERR_FAIL_NULL(ss);772773if (ss->wp_cursor_shape_device) {774wp_cursor_shape_device_v1_destroy(ss->wp_cursor_shape_device);775ss->wp_cursor_shape_device = nullptr;776}777}778}779780if (name == registry->wp_fractional_scale_manager_name) {781for (KeyValue<DisplayServer::WindowID, WindowState> &pair : registry->wayland_thread->windows) {782WindowState ws = pair.value;783784if (registry->wp_fractional_scale_manager) {785wp_fractional_scale_manager_v1_destroy(registry->wp_fractional_scale_manager);786registry->wp_fractional_scale_manager = nullptr;787}788789if (ws.wp_fractional_scale) {790wp_fractional_scale_v1_destroy(ws.wp_fractional_scale);791ws.wp_fractional_scale = nullptr;792}793}794795registry->wp_fractional_scale_manager_name = 0;796}797798if (name == registry->xdg_decoration_manager_name) {799if (registry->xdg_decoration_manager) {800zxdg_decoration_manager_v1_destroy(registry->xdg_decoration_manager);801registry->xdg_decoration_manager = nullptr;802}803804registry->xdg_decoration_manager_name = 0;805806return;807}808809if (name == registry->xdg_system_bell_name) {810if (registry->xdg_system_bell) {811xdg_system_bell_v1_destroy(registry->xdg_system_bell);812registry->xdg_system_bell = nullptr;813}814815registry->xdg_system_bell_name = 0;816817return;818}819820if (name == registry->xdg_activation_name) {821if (registry->xdg_activation) {822xdg_activation_v1_destroy(registry->xdg_activation);823registry->xdg_activation = nullptr;824}825826registry->xdg_activation_name = 0;827828return;829}830831if (name == registry->wp_primary_selection_device_manager_name) {832if (registry->wp_primary_selection_device_manager) {833zwp_primary_selection_device_manager_v1_destroy(registry->wp_primary_selection_device_manager);834registry->wp_primary_selection_device_manager = nullptr;835}836837registry->wp_primary_selection_device_manager_name = 0;838839// This global is used to create some seat data. Let's clean it.840for (struct wl_seat *wl_seat : registry->wl_seats) {841SeatState *ss = wl_seat_get_seat_state(wl_seat);842ERR_FAIL_NULL(ss);843844if (ss->wp_primary_selection_device) {845zwp_primary_selection_device_v1_destroy(ss->wp_primary_selection_device);846ss->wp_primary_selection_device = nullptr;847}848849if (ss->wp_primary_selection_source) {850zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source);851ss->wp_primary_selection_source = nullptr;852}853854if (ss->wp_primary_selection_offer) {855memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer));856zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer);857ss->wp_primary_selection_offer = nullptr;858}859}860861return;862}863864if (name == registry->wp_relative_pointer_manager_name) {865if (registry->wp_relative_pointer_manager) {866zwp_relative_pointer_manager_v1_destroy(registry->wp_relative_pointer_manager);867registry->wp_relative_pointer_manager = nullptr;868}869870registry->wp_relative_pointer_manager_name = 0;871872// This global is used to create some seat data. Let's clean it.873for (struct wl_seat *wl_seat : registry->wl_seats) {874SeatState *ss = wl_seat_get_seat_state(wl_seat);875ERR_FAIL_NULL(ss);876877if (ss->wp_relative_pointer) {878zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);879ss->wp_relative_pointer = nullptr;880}881}882883return;884}885886if (name == registry->wp_pointer_constraints_name) {887if (registry->wp_pointer_constraints) {888zwp_pointer_constraints_v1_destroy(registry->wp_pointer_constraints);889registry->wp_pointer_constraints = nullptr;890}891892registry->wp_pointer_constraints_name = 0;893894// This global is used to create some seat data. Let's clean it.895for (struct wl_seat *wl_seat : registry->wl_seats) {896SeatState *ss = wl_seat_get_seat_state(wl_seat);897ERR_FAIL_NULL(ss);898899if (ss->wp_relative_pointer) {900zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);901ss->wp_relative_pointer = nullptr;902}903904if (ss->wp_locked_pointer) {905zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);906ss->wp_locked_pointer = nullptr;907}908909if (ss->wp_confined_pointer) {910zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);911ss->wp_confined_pointer = nullptr;912}913}914915return;916}917918if (name == registry->wp_pointer_gestures_name) {919if (registry->wp_pointer_gestures) {920zwp_pointer_gestures_v1_destroy(registry->wp_pointer_gestures);921}922923registry->wp_pointer_gestures = nullptr;924registry->wp_pointer_gestures_name = 0;925926// This global is used to create some seat data. Let's clean it.927for (struct wl_seat *wl_seat : registry->wl_seats) {928SeatState *ss = wl_seat_get_seat_state(wl_seat);929ERR_FAIL_NULL(ss);930931if (ss->wp_pointer_gesture_pinch) {932zwp_pointer_gesture_pinch_v1_destroy(ss->wp_pointer_gesture_pinch);933ss->wp_pointer_gesture_pinch = nullptr;934}935}936937return;938}939940if (name == registry->wp_idle_inhibit_manager_name) {941if (registry->wp_idle_inhibit_manager) {942zwp_idle_inhibit_manager_v1_destroy(registry->wp_idle_inhibit_manager);943registry->wp_idle_inhibit_manager = nullptr;944}945946registry->wp_idle_inhibit_manager_name = 0;947948return;949}950951if (name == registry->wp_tablet_manager_name) {952if (registry->wp_tablet_manager) {953zwp_tablet_manager_v2_destroy(registry->wp_tablet_manager);954registry->wp_tablet_manager = nullptr;955}956957registry->wp_tablet_manager_name = 0;958959// This global is used to create some seat data. Let's clean it.960for (struct wl_seat *wl_seat : registry->wl_seats) {961SeatState *ss = wl_seat_get_seat_state(wl_seat);962ERR_FAIL_NULL(ss);963964for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {965TabletToolState *state = wp_tablet_tool_get_state(tool);966if (state) {967memdelete(state);968}969970zwp_tablet_tool_v2_destroy(tool);971}972973ss->tablet_tools.clear();974}975976return;977}978979if (name == registry->wp_text_input_manager_name) {980if (registry->wp_text_input_manager) {981zwp_text_input_manager_v3_destroy(registry->wp_text_input_manager);982registry->wp_text_input_manager = nullptr;983}984985registry->wp_text_input_manager_name = 0;986987for (struct wl_seat *wl_seat : registry->wl_seats) {988SeatState *ss = wl_seat_get_seat_state(wl_seat);989ERR_FAIL_NULL(ss);990991zwp_text_input_v3_destroy(ss->wp_text_input);992ss->wp_text_input = nullptr;993}994995return;996}997998{999// Iterate through all of the seats to find if any got removed.1000List<struct wl_seat *>::Element *E = registry->wl_seats.front();1001while (E) {1002struct wl_seat *wl_seat = E->get();1003List<struct wl_seat *>::Element *N = E->next();10041005SeatState *ss = wl_seat_get_seat_state(wl_seat);1006ERR_FAIL_NULL(ss);10071008if (ss->wl_seat_name == name) {1009if (wl_seat) {1010wl_seat_destroy(wl_seat);1011}10121013if (ss->wl_data_device) {1014wl_data_device_destroy(ss->wl_data_device);1015}10161017if (ss->wp_tablet_seat) {1018zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat);10191020for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {1021TabletToolState *state = wp_tablet_tool_get_state(tool);1022if (state) {1023memdelete(state);1024}10251026zwp_tablet_tool_v2_destroy(tool);1027}1028}10291030memdelete(ss);10311032registry->wl_seats.erase(E);1033return;1034}10351036E = N;1037}1038}10391040{1041// Iterate through all of the outputs to find if any got removed.1042// FIXME: This is a very bruteforce approach.1043List<struct wl_output *>::Element *it = registry->wl_outputs.front();1044while (it) {1045// Iterate through all of the screens to find if any got removed.1046struct wl_output *wl_output = it->get();1047ERR_FAIL_NULL(wl_output);10481049ScreenState *ss = wl_output_get_screen_state(wl_output);10501051if (ss->wl_output_name == name) {1052registry->wl_outputs.erase(it);10531054memdelete(ss);1055wl_output_destroy(wl_output);10561057return;1058}10591060it = it->next();1061}1062}10631064if (name == registry->wp_fifo_manager_name) {1065registry->wp_fifo_manager_name = 0;1066}1067}10681069void WaylandThread::_wl_surface_on_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {1070if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) {1071// This won't have the right data bound to it. Not worth it and would probably1072// just break everything.1073return;1074}10751076WindowState *ws = (WindowState *)data;1077ERR_FAIL_NULL(ws);10781079DEBUG_LOG_WAYLAND_THREAD(vformat("Window entered output %x.\n", (size_t)wl_output));10801081ws->wl_outputs.insert(wl_output);10821083// Workaround for buffer scaling as there's no guaranteed way of knowing the1084// preferred scale.1085// TODO: Skip this branch for newer `wl_surface`s once we add support for1086// `wl_surface::preferred_buffer_scale`1087if (ws->preferred_fractional_scale == 0) {1088window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height);1089}1090}10911092void WaylandThread::_frame_wl_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t callback_data) {1093wl_callback_destroy(wl_callback);10941095WindowState *ws = (WindowState *)data;1096ERR_FAIL_NULL(ws);1097ERR_FAIL_NULL(ws->wayland_thread);1098ERR_FAIL_NULL(ws->wl_surface);10991100ws->last_frame_time = OS::get_singleton()->get_ticks_usec();1101ws->wayland_thread->set_frame();11021103ws->frame_callback = wl_surface_frame(ws->wl_surface);1104wl_callback_add_listener(ws->frame_callback, &frame_wl_callback_listener, ws);11051106if (ws->wl_surface && ws->buffer_scale_changed) {1107// NOTE: We're only now setting the buffer scale as the idea is to get this1108// data committed together with the new frame, all by the rendering driver.1109// This is important because we might otherwise set an invalid combination of1110// buffer size and scale (e.g. odd size and 2x scale). We're pretty much1111// guaranteed to get a proper buffer in the next render loop as the rescaling1112// method also informs the engine of a "window rect change", triggering1113// rendering if needed.1114wl_surface_set_buffer_scale(ws->wl_surface, window_state_get_preferred_buffer_scale(ws));1115}1116}11171118void WaylandThread::_wl_surface_on_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {1119if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) {1120// This won't have the right data bound to it. Not worth it and would probably1121// just break everything.1122return;1123}11241125WindowState *ws = (WindowState *)data;1126ERR_FAIL_NULL(ws);11271128ws->wl_outputs.erase(wl_output);11291130DEBUG_LOG_WAYLAND_THREAD(vformat("Window left output %x.\n", (size_t)wl_output));1131}11321133// TODO: Add support to this event.1134void WaylandThread::_wl_surface_on_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor) {1135}11361137// TODO: Add support to this event.1138void WaylandThread::_wl_surface_on_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform) {1139}11401141void 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) {1142ScreenState *ss = (ScreenState *)data;1143ERR_FAIL_NULL(ss);11441145ss->pending_data.position.x = x;11461147ss->pending_data.position.x = x;1148ss->pending_data.position.y = y;11491150ss->pending_data.physical_size.width = physical_width;1151ss->pending_data.physical_size.height = physical_height;11521153ss->pending_data.make.clear();1154ss->pending_data.make.append_utf8(make);1155ss->pending_data.model.clear();1156ss->pending_data.model.append_utf8(model);11571158// `wl_output::done` is a version 2 addition. We'll directly update the data1159// for compatibility.1160if (wl_output_get_version(wl_output) == 1) {1161ss->data = ss->pending_data;1162}1163}11641165void WaylandThread::_wl_output_on_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {1166ScreenState *ss = (ScreenState *)data;1167ERR_FAIL_NULL(ss);11681169ss->pending_data.size.width = width;1170ss->pending_data.size.height = height;11711172ss->pending_data.refresh_rate = refresh ? refresh / 1000.0f : -1;11731174// `wl_output::done` is a version 2 addition. We'll directly update the data1175// for compatibility.1176if (wl_output_get_version(wl_output) == 1) {1177ss->data = ss->pending_data;1178}1179}11801181// NOTE: The following `wl_output` events are only for version 2 onwards, so we1182// can assume that they're "atomic" (i.e. rely on the `wl_output::done` event).11831184void WaylandThread::_wl_output_on_done(void *data, struct wl_output *wl_output) {1185ScreenState *ss = (ScreenState *)data;1186ERR_FAIL_NULL(ss);11871188ss->data = ss->pending_data;11891190ss->wayland_thread->_update_scale(ss->data.scale);11911192DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x done.", (size_t)wl_output));1193}11941195void WaylandThread::_wl_output_on_scale(void *data, struct wl_output *wl_output, int32_t factor) {1196ScreenState *ss = (ScreenState *)data;1197ERR_FAIL_NULL(ss);11981199ss->pending_data.scale = factor;12001201DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x scale %d", (size_t)wl_output, factor));1202}12031204void WaylandThread::_wl_output_on_name(void *data, struct wl_output *wl_output, const char *name) {1205}12061207void WaylandThread::_wl_output_on_description(void *data, struct wl_output *wl_output, const char *description) {1208}12091210void WaylandThread::_xdg_wm_base_on_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) {1211xdg_wm_base_pong(xdg_wm_base, serial);1212}12131214void WaylandThread::_xdg_surface_on_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) {1215xdg_surface_ack_configure(xdg_surface, serial);12161217WindowState *ws = (WindowState *)data;1218ERR_FAIL_NULL(ws);12191220DEBUG_LOG_WAYLAND_THREAD(vformat("xdg surface on configure rect %s", ws->rect));1221}12221223void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) {1224WindowState *ws = (WindowState *)data;1225ERR_FAIL_NULL(ws);12261227// Expect the window to be in a plain state. It will get properly set if the1228// compositor reports otherwise below.1229ws->mode = DisplayServer::WINDOW_MODE_WINDOWED;1230ws->suspended = false;12311232uint32_t *state = nullptr;1233wl_array_for_each(state, states) {1234switch (*state) {1235case XDG_TOPLEVEL_STATE_MAXIMIZED: {1236ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED;1237} break;12381239case XDG_TOPLEVEL_STATE_FULLSCREEN: {1240ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN;1241} break;12421243case XDG_TOPLEVEL_STATE_SUSPENDED: {1244ws->suspended = true;1245} break;12461247default: {1248// We don't care about the other states (for now).1249} break;1250}1251}12521253if (width != 0 && height != 0) {1254window_state_update_size(ws, width, height);1255}12561257DEBUG_LOG_WAYLAND_THREAD(vformat("XDG toplevel on configure width %d height %d.", width, height));1258}12591260void WaylandThread::_xdg_toplevel_on_close(void *data, struct xdg_toplevel *xdg_toplevel) {1261WindowState *ws = (WindowState *)data;1262ERR_FAIL_NULL(ws);12631264Ref<WindowEventMessage> msg;1265msg.instantiate();1266msg->id = ws->id;1267msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST;1268ws->wayland_thread->push_message(msg);1269}12701271void WaylandThread::_xdg_toplevel_on_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) {1272}12731274void WaylandThread::_xdg_toplevel_on_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities) {1275WindowState *ws = (WindowState *)data;1276ERR_FAIL_NULL(ws);12771278ws->can_maximize = false;1279ws->can_fullscreen = false;1280ws->can_minimize = false;12811282uint32_t *capability = nullptr;1283wl_array_for_each(capability, capabilities) {1284switch (*capability) {1285case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: {1286ws->can_maximize = true;1287} break;1288case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: {1289ws->can_fullscreen = true;1290} break;12911292case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: {1293ws->can_minimize = true;1294} break;12951296default: {1297} break;1298}1299}1300}13011302void WaylandThread::_xdg_popup_on_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height) {1303WindowState *ws = (WindowState *)data;1304ERR_FAIL_NULL(ws);13051306if (width != 0 && height != 0) {1307window_state_update_size(ws, width, height);1308}13091310WindowState *parent = ws->wayland_thread->window_get_state(ws->parent_id);1311ERR_FAIL_NULL(parent);13121313Point2i pos = Point2i(x, y);1314#ifdef LIBDECOR_ENABLED1315if (parent->libdecor_frame) {1316int translated_x = x;1317int translated_y = y;1318libdecor_frame_translate_coordinate(parent->libdecor_frame, x, y, &translated_x, &translated_y);13191320pos.x = translated_x;1321pos.y = translated_y;1322}1323#endif13241325// Looks like the position returned here is relative to the parent. We have to1326// accumulate it or there's gonna be a lot of confusion godot-side.1327pos += parent->rect.position;13281329if (ws->rect.position != pos) {1330DEBUG_LOG_WAYLAND_THREAD(vformat("Repositioning popup %d from %s to %s", ws->id, ws->rect.position, pos));13311332double parent_scale = window_state_get_scale_factor(parent);13331334ws->rect.position = pos;13351336Ref<WindowRectMessage> rect_msg;1337rect_msg.instantiate();1338rect_msg->id = ws->id;1339rect_msg->rect.position = scale_vector2i(ws->rect.position, parent_scale);1340rect_msg->rect.size = scale_vector2i(ws->rect.size, parent_scale);13411342ws->wayland_thread->push_message(rect_msg);1343}13441345DEBUG_LOG_WAYLAND_THREAD(vformat("xdg popup on configure x%d y%d w%d h%d", x, y, width, height));1346}13471348void WaylandThread::_xdg_popup_on_popup_done(void *data, struct xdg_popup *xdg_popup) {1349WindowState *ws = (WindowState *)data;1350ERR_FAIL_NULL(ws);13511352Ref<WindowEventMessage> ev_msg;1353ev_msg.instantiate();1354ev_msg->id = ws->id;1355ev_msg->event = DisplayServer::WINDOW_EVENT_FORCE_CLOSE;13561357ws->wayland_thread->push_message(ev_msg);1358}13591360void WaylandThread::_xdg_popup_on_repositioned(void *data, struct xdg_popup *xdg_popup, uint32_t token) {1361DEBUG_LOG_WAYLAND_THREAD(vformat("stub xdg popup repositioned %x", token));1362}13631364// NOTE: Deprecated.1365void WaylandThread::_xdg_exported_v1_on_handle(void *data, zxdg_exported_v1 *exported, const char *handle) {1366WindowState *ws = (WindowState *)data;1367ERR_FAIL_NULL(ws);13681369ws->exported_handle = vformat("wayland:%s", String::utf8(handle));1370}13711372void WaylandThread::_xdg_exported_v2_on_handle(void *data, zxdg_exported_v2 *exported, const char *handle) {1373WindowState *ws = (WindowState *)data;1374ERR_FAIL_NULL(ws);13751376ws->exported_handle = vformat("wayland:%s", String::utf8(handle));1377}13781379void WaylandThread::_xdg_toplevel_decoration_on_configure(void *data, struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration, uint32_t mode) {1380if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE) {1381#ifdef LIBDECOR_ENABLED1382WARN_PRINT_ONCE("Native client side decorations are not yet supported without libdecor!");1383#else1384WARN_PRINT_ONCE("Native client side decorations are not yet supported!");1385#endif // LIBDECOR_ENABLED1386}1387}13881389#ifdef LIBDECOR_ENABLED1390void WaylandThread::libdecor_on_error(struct libdecor *context, enum libdecor_error error, const char *message) {1391ERR_PRINT(vformat("libdecor error %d: %s", error, message));1392}13931394// NOTE: This is pretty much a reimplementation of _xdg_surface_on_configure1395// and _xdg_toplevel_on_configure. Libdecor really likes wrapping everything,1396// forcing us to do stuff like this.1397void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *user_data) {1398WindowState *ws = (WindowState *)user_data;1399ERR_FAIL_NULL(ws);14001401int width = 0;1402int height = 0;14031404ws->pending_libdecor_configuration = configuration;14051406if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {1407// The configuration doesn't have a size. We'll use the one already set in the window.1408width = ws->rect.size.width;1409height = ws->rect.size.height;1410}14111412ERR_FAIL_COND_MSG(width == 0 || height == 0, "Window has invalid size.");14131414libdecor_window_state window_state = LIBDECOR_WINDOW_STATE_NONE;14151416// Expect the window to be in a plain state. It will get properly set if the1417// compositor reports otherwise below.1418ws->mode = DisplayServer::WINDOW_MODE_WINDOWED;1419ws->suspended = false;14201421if (libdecor_configuration_get_window_state(configuration, &window_state)) {1422if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) {1423ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED;1424}14251426if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) {1427ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN;1428}14291430if (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) {1431ws->suspended = true;1432}1433}14341435window_state_update_size(ws, width, height);14361437DEBUG_LOG_WAYLAND_THREAD(vformat("libdecor frame on configure rect %s", ws->rect));1438}14391440void WaylandThread::libdecor_frame_on_close(struct libdecor_frame *frame, void *user_data) {1441WindowState *ws = (WindowState *)user_data;1442ERR_FAIL_NULL(ws);14431444Ref<WindowEventMessage> winevent_msg;1445winevent_msg.instantiate();1446winevent_msg->id = ws->id;1447winevent_msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST;14481449ws->wayland_thread->push_message(winevent_msg);14501451DEBUG_LOG_WAYLAND_THREAD("libdecor frame on close");1452}14531454void WaylandThread::libdecor_frame_on_commit(struct libdecor_frame *frame, void *user_data) {1455// We're skipping this as we don't really care about libdecor's commit for1456// atomicity reasons. See `_frame_wl_callback_on_done` for more info.14571458DEBUG_LOG_WAYLAND_THREAD("libdecor frame on commit");1459}14601461void WaylandThread::libdecor_frame_on_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data) {1462}1463#endif // LIBDECOR_ENABLED14641465void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) {1466SeatState *ss = (SeatState *)data;14671468ERR_FAIL_NULL(ss);14691470// TODO: Handle touch.14711472// Pointer handling.1473if (capabilities & WL_SEAT_CAPABILITY_POINTER) {1474if (!ss->wl_pointer) {1475ss->cursor_surface = wl_compositor_create_surface(ss->registry->wl_compositor);1476wl_surface_commit(ss->cursor_surface);14771478ss->wl_pointer = wl_seat_get_pointer(wl_seat);1479wl_pointer_add_listener(ss->wl_pointer, &wl_pointer_listener, ss);14801481if (ss->registry->wp_cursor_shape_manager) {1482ss->wp_cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(ss->registry->wp_cursor_shape_manager, ss->wl_pointer);1483}14841485if (ss->registry->wp_relative_pointer_manager) {1486ss->wp_relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(ss->registry->wp_relative_pointer_manager, ss->wl_pointer);1487zwp_relative_pointer_v1_add_listener(ss->wp_relative_pointer, &wp_relative_pointer_listener, ss);1488}14891490if (ss->registry->wp_pointer_gestures) {1491ss->wp_pointer_gesture_pinch = zwp_pointer_gestures_v1_get_pinch_gesture(ss->registry->wp_pointer_gestures, ss->wl_pointer);1492zwp_pointer_gesture_pinch_v1_add_listener(ss->wp_pointer_gesture_pinch, &wp_pointer_gesture_pinch_listener, ss);1493}14941495// TODO: Constrain new pointers if the global mouse mode is constrained.1496}1497} else {1498if (ss->cursor_frame_callback) {1499// Just in case. I got bitten by weird race-like conditions already.1500wl_callback_set_user_data(ss->cursor_frame_callback, nullptr);15011502wl_callback_destroy(ss->cursor_frame_callback);1503ss->cursor_frame_callback = nullptr;1504}15051506if (ss->cursor_surface) {1507wl_surface_destroy(ss->cursor_surface);1508ss->cursor_surface = nullptr;1509}15101511if (ss->wl_pointer) {1512wl_pointer_destroy(ss->wl_pointer);1513ss->wl_pointer = nullptr;1514}15151516if (ss->wp_cursor_shape_device) {1517wp_cursor_shape_device_v1_destroy(ss->wp_cursor_shape_device);1518ss->wp_cursor_shape_device = nullptr;1519}15201521if (ss->wp_relative_pointer) {1522zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);1523ss->wp_relative_pointer = nullptr;1524}15251526if (ss->wp_confined_pointer) {1527zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);1528ss->wp_confined_pointer = nullptr;1529}15301531if (ss->wp_locked_pointer) {1532zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);1533ss->wp_locked_pointer = nullptr;1534}1535}15361537// Keyboard handling.1538if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {1539if (!ss->wl_keyboard) {1540ss->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);1541ERR_FAIL_NULL(ss->xkb_context);15421543ss->wl_keyboard = wl_seat_get_keyboard(wl_seat);1544wl_keyboard_add_listener(ss->wl_keyboard, &wl_keyboard_listener, ss);1545}1546} else {1547if (ss->xkb_context) {1548xkb_context_unref(ss->xkb_context);1549ss->xkb_context = nullptr;1550}15511552if (ss->wl_keyboard) {1553wl_keyboard_destroy(ss->wl_keyboard);1554ss->wl_keyboard = nullptr;1555}1556}1557}15581559void WaylandThread::_wl_seat_on_name(void *data, struct wl_seat *wl_seat, const char *name) {1560}15611562void WaylandThread::_cursor_frame_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t time_ms) {1563wl_callback_destroy(wl_callback);15641565SeatState *ss = (SeatState *)data;1566ERR_FAIL_NULL(ss);15671568ss->cursor_frame_callback = nullptr;15691570ss->cursor_time_ms = time_ms;15711572seat_state_update_cursor(ss);1573}15741575void 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) {1576WindowState *ws = wl_surface_get_window_state(surface);1577if (!ws) {1578return;1579}15801581SeatState *ss = (SeatState *)data;1582ERR_FAIL_NULL(ss);15831584ERR_FAIL_NULL(ss->cursor_surface);15851586PointerData &pd = ss->pointer_data_buffer;15871588ss->pointer_enter_serial = serial;1589pd.pointed_id = ws->id;1590pd.last_pointed_id = ws->id;1591pd.position.x = wl_fixed_to_double(surface_x);1592pd.position.y = wl_fixed_to_double(surface_y);15931594seat_state_update_cursor(ss);15951596DEBUG_LOG_WAYLAND_THREAD(vformat("Pointer entered window %d.", ws->id));1597}15981599void WaylandThread::_wl_pointer_on_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) {1600// NOTE: `surface` will probably be null when the surface is destroyed.1601// See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/3661602// See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/46516031604SeatState *ss = (SeatState *)data;1605ERR_FAIL_NULL(ss);16061607PointerData &pd = ss->pointer_data_buffer;16081609if (pd.pointed_id == DisplayServer::INVALID_WINDOW_ID) {1610// We're probably on a decoration or some other third-party thing.1611return;1612}16131614DisplayServer::WindowID id = pd.pointed_id;16151616pd.pointed_id = DisplayServer::INVALID_WINDOW_ID;1617pd.pressed_button_mask.clear();16181619DEBUG_LOG_WAYLAND_THREAD(vformat("Pointer left window %d.", id));1620}16211622void 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) {1623SeatState *ss = (SeatState *)data;1624ERR_FAIL_NULL(ss);16251626PointerData &pd = ss->pointer_data_buffer;16271628pd.position.x = wl_fixed_to_double(surface_x);1629pd.position.y = wl_fixed_to_double(surface_y);16301631pd.motion_time = time;1632}16331634void WaylandThread::_wl_pointer_on_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {1635SeatState *ss = (SeatState *)data;1636ERR_FAIL_NULL(ss);16371638PointerData &pd = ss->pointer_data_buffer;16391640MouseButton button_pressed = MouseButton::NONE;16411642switch (button) {1643case BTN_LEFT:1644button_pressed = MouseButton::LEFT;1645break;16461647case BTN_MIDDLE:1648button_pressed = MouseButton::MIDDLE;1649break;16501651case BTN_RIGHT:1652button_pressed = MouseButton::RIGHT;1653break;16541655case BTN_EXTRA:1656button_pressed = MouseButton::MB_XBUTTON1;1657break;16581659case BTN_SIDE:1660button_pressed = MouseButton::MB_XBUTTON2;1661break;16621663default: {1664}1665}16661667MouseButtonMask mask = mouse_button_to_mask(button_pressed);16681669if (state & WL_POINTER_BUTTON_STATE_PRESSED) {1670pd.pressed_button_mask.set_flag(mask);1671pd.last_button_pressed = button_pressed;1672pd.double_click_begun = true;1673} else {1674pd.pressed_button_mask.clear_flag(mask);1675}16761677pd.button_time = time;1678pd.button_serial = serial;1679}16801681void WaylandThread::_wl_pointer_on_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {1682SeatState *ss = (SeatState *)data;1683ERR_FAIL_NULL(ss);16841685PointerData &pd = ss->pointer_data_buffer;16861687switch (axis) {1688case WL_POINTER_AXIS_VERTICAL_SCROLL: {1689pd.scroll_vector.y = wl_fixed_to_double(value);1690} break;16911692case WL_POINTER_AXIS_HORIZONTAL_SCROLL: {1693pd.scroll_vector.x = wl_fixed_to_double(value);1694} break;1695}16961697pd.button_time = time;1698}16991700void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_pointer) {1701SeatState *ss = (SeatState *)data;1702ERR_FAIL_NULL(ss);17031704WaylandThread *wayland_thread = ss->wayland_thread;1705ERR_FAIL_NULL(wayland_thread);17061707PointerData &old_pd = ss->pointer_data;1708PointerData &pd = ss->pointer_data_buffer;17091710if (pd.pointed_id != old_pd.pointed_id) {1711if (old_pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {1712Ref<WindowEventMessage> msg;1713msg.instantiate();1714msg->id = old_pd.pointed_id;1715msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;17161717wayland_thread->push_message(msg);1718}17191720if (pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {1721Ref<WindowEventMessage> msg;1722msg.instantiate();1723msg->id = pd.pointed_id;1724msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;17251726wayland_thread->push_message(msg);1727}1728}17291730WindowState *ws = nullptr;17311732// NOTE: At least on sway, with wl_pointer version 5 or greater,1733// wl_pointer::leave might be emitted with other events (like1734// wl_pointer::button) within the same wl_pointer::frame. Because of this, we1735// need to account for when the currently pointed window might be invalid1736// (third-party or even none) and fall back to the old one.1737if (pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {1738ws = ss->wayland_thread->window_get_state(pd.pointed_id);1739ERR_FAIL_NULL(ws);1740} else if (old_pd.pointed_id != DisplayServer::INVALID_WINDOW_ID) {1741ws = ss->wayland_thread->window_get_state(old_pd.pointed_id);1742ERR_FAIL_NULL(ws);1743}17441745if (ws == nullptr) {1746// We're probably on a decoration or some other third-party thing. Let's1747// "commit" the data and call it a day.1748old_pd = pd;1749return;1750}17511752double scale = window_state_get_scale_factor(ws);17531754wayland_thread->_set_current_seat(ss->wl_seat);17551756if (old_pd.motion_time != pd.motion_time || old_pd.relative_motion_time != pd.relative_motion_time) {1757Ref<InputEventMouseMotion> mm;1758mm.instantiate();17591760// Set all pressed modifiers.1761mm->set_shift_pressed(ss->shift_pressed);1762mm->set_ctrl_pressed(ss->ctrl_pressed);1763mm->set_alt_pressed(ss->alt_pressed);1764mm->set_meta_pressed(ss->meta_pressed);17651766mm->set_window_id(ws->id);17671768mm->set_button_mask(pd.pressed_button_mask);17691770mm->set_position(pd.position * scale);1771mm->set_global_position(pd.position * scale);17721773Vector2 pos_delta = (pd.position - old_pd.position) * scale;17741775if (old_pd.relative_motion_time != pd.relative_motion_time) {1776uint32_t time_delta = pd.relative_motion_time - old_pd.relative_motion_time;17771778mm->set_relative(pd.relative_motion * scale);1779mm->set_velocity((Vector2)pos_delta / time_delta);1780} else {1781// The spec includes the possibility of having motion events without an1782// associated relative motion event. If that's the case, fallback to a1783// simple delta of the position. The captured mouse won't report the1784// relative speed anymore though.1785uint32_t time_delta = pd.motion_time - old_pd.motion_time;17861787mm->set_relative(pos_delta);1788mm->set_velocity((Vector2)pos_delta / time_delta);1789}1790mm->set_relative_screen_position(mm->get_relative());1791mm->set_screen_velocity(mm->get_velocity());17921793Ref<InputEventMessage> msg;1794msg.instantiate();17951796msg->event = mm;17971798wayland_thread->push_message(msg);1799}18001801if (pd.discrete_scroll_vector_120 - old_pd.discrete_scroll_vector_120 != Vector2i()) {1802// This is a discrete scroll (eg. from a scroll wheel), so we'll just emit1803// scroll wheel buttons.1804if (pd.scroll_vector.y != 0) {1805MouseButton button = pd.scroll_vector.y > 0 ? MouseButton::WHEEL_DOWN : MouseButton::WHEEL_UP;1806pd.pressed_button_mask.set_flag(mouse_button_to_mask(button));1807}18081809if (pd.scroll_vector.x != 0) {1810MouseButton button = pd.scroll_vector.x > 0 ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT;1811pd.pressed_button_mask.set_flag(mouse_button_to_mask(button));1812}1813} else {1814if (pd.scroll_vector - old_pd.scroll_vector != Vector2()) {1815// This is a continuous scroll, so we'll emit a pan gesture.1816Ref<InputEventPanGesture> pg;1817pg.instantiate();18181819// Set all pressed modifiers.1820pg->set_shift_pressed(ss->shift_pressed);1821pg->set_ctrl_pressed(ss->ctrl_pressed);1822pg->set_alt_pressed(ss->alt_pressed);1823pg->set_meta_pressed(ss->meta_pressed);18241825pg->set_position(pd.position * scale);18261827pg->set_window_id(ws->id);18281829pg->set_delta(pd.scroll_vector);18301831Ref<InputEventMessage> msg;1832msg.instantiate();18331834msg->event = pg;18351836wayland_thread->push_message(msg);1837}1838}18391840if (old_pd.pressed_button_mask != pd.pressed_button_mask) {1841BitField<MouseButtonMask> pressed_mask_delta = old_pd.pressed_button_mask.get_different(pd.pressed_button_mask);18421843const MouseButton buttons_to_test[] = {1844MouseButton::LEFT,1845MouseButton::MIDDLE,1846MouseButton::RIGHT,1847MouseButton::WHEEL_UP,1848MouseButton::WHEEL_DOWN,1849MouseButton::WHEEL_LEFT,1850MouseButton::WHEEL_RIGHT,1851MouseButton::MB_XBUTTON1,1852MouseButton::MB_XBUTTON2,1853};18541855for (MouseButton test_button : buttons_to_test) {1856MouseButtonMask test_button_mask = mouse_button_to_mask(test_button);1857if (pressed_mask_delta.has_flag(test_button_mask)) {1858Ref<InputEventMouseButton> mb;1859mb.instantiate();18601861// Set all pressed modifiers.1862mb->set_shift_pressed(ss->shift_pressed);1863mb->set_ctrl_pressed(ss->ctrl_pressed);1864mb->set_alt_pressed(ss->alt_pressed);1865mb->set_meta_pressed(ss->meta_pressed);18661867mb->set_window_id(ws->id);1868mb->set_position(pd.position * scale);1869mb->set_global_position(pd.position * scale);18701871if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN) {1872// If this is a discrete scroll, specify how many "clicks" it did for this1873// pointer frame.1874mb->set_factor(Math::abs(pd.discrete_scroll_vector_120.y / (float)120));1875}18761877if (test_button == MouseButton::WHEEL_RIGHT || test_button == MouseButton::WHEEL_LEFT) {1878// If this is a discrete scroll, specify how many "clicks" it did for this1879// pointer frame.1880mb->set_factor(std::abs(pd.discrete_scroll_vector_120.x / (float)120));1881}18821883mb->set_button_mask(pd.pressed_button_mask);18841885mb->set_button_index(test_button);1886mb->set_pressed(pd.pressed_button_mask.has_flag(test_button_mask));18871888// We have to set the last position pressed here as we can't take for1889// granted what the individual events might have seen due to them not having1890// a guaranteed order.1891if (mb->is_pressed()) {1892pd.last_pressed_position = pd.position;1893}18941895if (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) {1896pd.double_click_begun = false;1897mb->set_double_click(true);1898}18991900Ref<InputEventMessage> msg;1901msg.instantiate();19021903msg->event = mb;19041905wayland_thread->push_message(msg);19061907// Send an event resetting immediately the wheel key.1908// Wayland specification defines axis_stop events as optional and says to1909// treat all axis events as unterminated. As such, we have to manually do1910// it ourselves.1911if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN || test_button == MouseButton::WHEEL_LEFT || test_button == MouseButton::WHEEL_RIGHT) {1912// FIXME: This is ugly, I can't find a clean way to clone an InputEvent.1913// This works for now, despite being horrible.1914Ref<InputEventMouseButton> wh_up;1915wh_up.instantiate();19161917wh_up->set_window_id(ws->id);1918wh_up->set_position(pd.position * scale);1919wh_up->set_global_position(pd.position * scale);19201921// We have to unset the button to avoid it getting stuck.1922pd.pressed_button_mask.clear_flag(test_button_mask);1923wh_up->set_button_mask(pd.pressed_button_mask);19241925wh_up->set_button_index(test_button);1926wh_up->set_pressed(false);19271928Ref<InputEventMessage> msg_up;1929msg_up.instantiate();1930msg_up->event = wh_up;1931wayland_thread->push_message(msg_up);1932}1933}1934}1935}19361937// Reset the scroll vectors as we already handled them.1938pd.scroll_vector = Vector2();1939pd.discrete_scroll_vector_120 = Vector2i();19401941// Update the data all getters read. Wayland's specification requires us to do1942// this, since all pointer actions are sent in individual events.1943old_pd = pd;1944}19451946void WaylandThread::_wl_pointer_on_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source) {1947SeatState *ss = (SeatState *)data;1948ERR_FAIL_NULL(ss);19491950ss->pointer_data_buffer.scroll_type = axis_source;1951}19521953void WaylandThread::_wl_pointer_on_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) {1954}19551956// NOTE: This event is deprecated since version 8 and superseded by1957// `wl_pointer::axis_value120`. This thus converts the data to its1958// fraction-of-120 format.1959void WaylandThread::_wl_pointer_on_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) {1960SeatState *ss = (SeatState *)data;1961ERR_FAIL_NULL(ss);19621963PointerData &pd = ss->pointer_data_buffer;19641965// NOTE: We can allow ourselves to not accumulate this data (and thus just1966// assign it) as the spec guarantees only one event per axis type.19671968if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {1969pd.discrete_scroll_vector_120.y = discrete * 120;1970}19711972if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {1973pd.discrete_scroll_vector_120.x = discrete * 120;1974}1975}19761977// Supersedes `wl_pointer::axis_discrete` Since version 8.1978void WaylandThread::_wl_pointer_on_axis_value120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) {1979SeatState *ss = (SeatState *)data;1980ERR_FAIL_NULL(ss);19811982PointerData &pd = ss->pointer_data_buffer;19831984if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {1985pd.discrete_scroll_vector_120.y += value120;1986}19871988if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {1989pd.discrete_scroll_vector_120.x += value120;1990}1991}19921993// TODO: Add support to this event.1994void WaylandThread::_wl_pointer_on_axis_relative_direction(void *data, struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction) {1995}19961997void WaylandThread::_wl_keyboard_on_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) {1998ERR_FAIL_COND_MSG(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, "Unsupported keymap format announced from the Wayland compositor.");19992000SeatState *ss = (SeatState *)data;2001ERR_FAIL_NULL(ss);20022003if (ss->keymap_buffer) {2004// We have already a mapped buffer, so we unmap it. There's no need to reset2005// its pointer or size, as we're gonna set them below.2006munmap((void *)ss->keymap_buffer, ss->keymap_buffer_size);2007ss->keymap_buffer = nullptr;2008}20092010ss->keymap_buffer = (const char *)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);2011ss->keymap_buffer_size = size;20122013xkb_keymap_unref(ss->xkb_keymap);2014ss->xkb_keymap = xkb_keymap_new_from_string(ss->xkb_context, ss->keymap_buffer,2015XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);20162017xkb_state_unref(ss->xkb_state);2018ss->xkb_state = xkb_state_new(ss->xkb_keymap);2019}20202021void WaylandThread::_wl_keyboard_on_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {2022WindowState *ws = wl_surface_get_window_state(surface);2023if (!ws) {2024return;2025}20262027SeatState *ss = (SeatState *)data;2028ERR_FAIL_NULL(ss);20292030WaylandThread *wayland_thread = ss->wayland_thread;2031ERR_FAIL_NULL(wayland_thread);20322033ss->focused_id = ws->id;20342035wayland_thread->_set_current_seat(ss->wl_seat);20362037Ref<WindowEventMessage> msg;2038msg.instantiate();2039msg->id = ws->id;2040msg->event = DisplayServer::WINDOW_EVENT_FOCUS_IN;2041wayland_thread->push_message(msg);20422043DEBUG_LOG_WAYLAND_THREAD(vformat("Keyboard focused window %d.", ws->id));2044}20452046void WaylandThread::_wl_keyboard_on_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) {2047// NOTE: `surface` will probably be null when the surface is destroyed.2048// See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/3662049// See: https://gitlab.freedesktop.org/wayland/wayland/-/issues/46520502051if (surface && !wl_proxy_is_godot((struct wl_proxy *)surface)) {2052return;2053}20542055SeatState *ss = (SeatState *)data;2056ERR_FAIL_NULL(ss);20572058WaylandThread *wayland_thread = ss->wayland_thread;2059ERR_FAIL_NULL(wayland_thread);20602061ss->repeating_keycode = XKB_KEYCODE_INVALID;20622063if (ss->focused_id == DisplayServer::INVALID_WINDOW_ID) {2064// We're probably on a decoration or some other third-party thing.2065return;2066}20672068WindowState *ws = wayland_thread->window_get_state(ss->focused_id);2069ERR_FAIL_NULL(ws);20702071ss->focused_id = DisplayServer::INVALID_WINDOW_ID;20722073Ref<WindowEventMessage> msg;2074msg.instantiate();2075msg->id = ws->id;2076msg->event = DisplayServer::WINDOW_EVENT_FOCUS_OUT;2077wayland_thread->push_message(msg);20782079DEBUG_LOG_WAYLAND_THREAD(vformat("Keyboard unfocused window %d.", ws->id));2080}20812082void WaylandThread::_wl_keyboard_on_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {2083SeatState *ss = (SeatState *)data;2084ERR_FAIL_NULL(ss);20852086if (ss->focused_id == DisplayServer::INVALID_WINDOW_ID) {2087return;2088}20892090WaylandThread *wayland_thread = ss->wayland_thread;2091ERR_FAIL_NULL(wayland_thread);20922093// We have to add 8 to the scancode to get an XKB-compatible keycode.2094xkb_keycode_t xkb_keycode = key + 8;20952096bool pressed = state & WL_KEYBOARD_KEY_STATE_PRESSED;20972098if (pressed) {2099if (xkb_keymap_key_repeats(ss->xkb_keymap, xkb_keycode)) {2100ss->last_repeat_start_msec = OS::get_singleton()->get_ticks_msec();2101ss->repeating_keycode = xkb_keycode;2102}21032104ss->last_key_pressed_serial = serial;2105} else if (ss->repeating_keycode == xkb_keycode) {2106ss->repeating_keycode = XKB_KEYCODE_INVALID;2107}21082109Ref<InputEventKey> k = _seat_state_get_key_event(ss, xkb_keycode, pressed);2110if (k.is_null()) {2111return;2112}21132114Ref<InputEventKey> uk = _seat_state_get_unstuck_key_event(ss, xkb_keycode, pressed, k->get_keycode());2115if (uk.is_valid()) {2116Ref<InputEventMessage> u_msg;2117u_msg.instantiate();2118u_msg->event = uk;2119wayland_thread->push_message(u_msg);2120}21212122Ref<InputEventMessage> msg;2123msg.instantiate();2124msg->event = k;2125wayland_thread->push_message(msg);2126}21272128void 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) {2129SeatState *ss = (SeatState *)data;2130ERR_FAIL_NULL(ss);21312132xkb_state_update_mask(ss->xkb_state, mods_depressed, mods_latched, mods_locked, ss->current_layout_index, ss->current_layout_index, group);21332134ss->shift_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED);2135ss->ctrl_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED);2136ss->alt_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_DEPRESSED);2137ss->meta_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_DEPRESSED);21382139ss->current_layout_index = group;2140}21412142void WaylandThread::_wl_keyboard_on_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) {2143SeatState *ss = (SeatState *)data;2144ERR_FAIL_NULL(ss);21452146ss->repeat_key_delay_msec = 1000 / rate;2147ss->repeat_start_delay_msec = delay;2148}21492150// NOTE: Don't forget to `memfree` the offer's state.2151void WaylandThread::_wl_data_device_on_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {2152wl_proxy_tag_godot((struct wl_proxy *)id);2153wl_data_offer_add_listener(id, &wl_data_offer_listener, memnew(OfferState));2154}21552156void 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) {2157WindowState *ws = wl_surface_get_window_state(surface);2158if (!ws) {2159return;2160}21612162SeatState *ss = (SeatState *)data;2163ERR_FAIL_NULL(ss);21642165ss->dnd_id = ws->id;21662167ss->dnd_enter_serial = serial;2168ss->wl_data_offer_dnd = id;21692170// Godot only supports DnD file copying for now.2171wl_data_offer_accept(id, serial, "text/uri-list");2172wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);2173}21742175void WaylandThread::_wl_data_device_on_leave(void *data, struct wl_data_device *wl_data_device) {2176SeatState *ss = (SeatState *)data;2177ERR_FAIL_NULL(ss);21782179if (ss->wl_data_offer_dnd) {2180memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd));2181wl_data_offer_destroy(ss->wl_data_offer_dnd);2182ss->wl_data_offer_dnd = nullptr;2183ss->dnd_id = DisplayServer::INVALID_WINDOW_ID;2184}2185}21862187void 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) {2188}21892190void WaylandThread::_wl_data_device_on_drop(void *data, struct wl_data_device *wl_data_device) {2191SeatState *ss = (SeatState *)data;2192ERR_FAIL_NULL(ss);21932194WaylandThread *wayland_thread = ss->wayland_thread;2195ERR_FAIL_NULL(wayland_thread);21962197OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_dnd);2198ERR_FAIL_NULL(os);21992200if (os) {2201Ref<DropFilesEventMessage> msg;2202msg.instantiate();2203msg->id = ss->dnd_id;22042205Vector<uint8_t> list_data = _wl_data_offer_read(wayland_thread->wl_display, "text/uri-list", ss->wl_data_offer_dnd);22062207msg->files = String::utf8((const char *)list_data.ptr(), list_data.size()).split("\r\n", false);2208for (int i = 0; i < msg->files.size(); i++) {2209msg->files.write[i] = msg->files[i].replace("file://", "").uri_file_decode();2210}22112212wayland_thread->push_message(msg);22132214wl_data_offer_finish(ss->wl_data_offer_dnd);2215}22162217memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd));2218wl_data_offer_destroy(ss->wl_data_offer_dnd);2219ss->wl_data_offer_dnd = nullptr;2220ss->dnd_id = DisplayServer::INVALID_WINDOW_ID;2221}22222223void WaylandThread::_wl_data_device_on_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {2224SeatState *ss = (SeatState *)data;2225ERR_FAIL_NULL(ss);22262227if (ss->wl_data_offer_selection) {2228memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_selection));2229wl_data_offer_destroy(ss->wl_data_offer_selection);2230}22312232ss->wl_data_offer_selection = id;2233}22342235void WaylandThread::_wl_data_offer_on_offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type) {2236OfferState *os = (OfferState *)data;2237ERR_FAIL_NULL(os);22382239if (os) {2240os->mime_types.insert(String::utf8(mime_type));2241}2242}22432244void WaylandThread::_wl_data_offer_on_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions) {2245}22462247void WaylandThread::_wl_data_offer_on_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) {2248}22492250void WaylandThread::_wl_data_source_on_target(void *data, struct wl_data_source *wl_data_source, const char *mime_type) {2251}22522253void WaylandThread::_wl_data_source_on_send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd) {2254SeatState *ss = (SeatState *)data;2255ERR_FAIL_NULL(ss);22562257Vector<uint8_t> *data_to_send = nullptr;22582259if (wl_data_source == ss->wl_data_source_selection) {2260data_to_send = &ss->selection_data;2261DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested selection.");2262}22632264if (data_to_send) {2265ssize_t written_bytes = 0;22662267bool valid_mime = false;22682269if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) {2270valid_mime = true;2271} else if (strcmp(mime_type, "text/plain") == 0) {2272valid_mime = true;2273}22742275if (valid_mime) {2276written_bytes = write(fd, data_to_send->ptr(), data_to_send->size());2277}22782279if (written_bytes > 0) {2280DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes));2281} else if (written_bytes == 0) {2282DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent.");2283} else {2284ERR_PRINT(vformat("Clipboard: write error %d.", errno));2285}2286}22872288close(fd);2289}22902291void WaylandThread::_wl_data_source_on_cancelled(void *data, struct wl_data_source *wl_data_source) {2292SeatState *ss = (SeatState *)data;2293ERR_FAIL_NULL(ss);22942295wl_data_source_destroy(wl_data_source);22962297if (wl_data_source == ss->wl_data_source_selection) {2298ss->wl_data_source_selection = nullptr;2299ss->selection_data.clear();23002301DEBUG_LOG_WAYLAND_THREAD("Clipboard: selection set by another program.");2302return;2303}2304}23052306void WaylandThread::_wl_data_source_on_dnd_drop_performed(void *data, struct wl_data_source *wl_data_source) {2307}23082309void WaylandThread::_wl_data_source_on_dnd_finished(void *data, struct wl_data_source *wl_data_source) {2310}23112312void WaylandThread::_wl_data_source_on_action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action) {2313}23142315void WaylandThread::_wp_fractional_scale_on_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale) {2316WindowState *ws = (WindowState *)data;2317ERR_FAIL_NULL(ws);23182319ws->preferred_fractional_scale = (double)scale / 120;23202321window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height);2322}23232324void 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) {2325SeatState *ss = (SeatState *)data;2326ERR_FAIL_NULL(ss);23272328PointerData &pd = ss->pointer_data_buffer;23292330pd.relative_motion.x = wl_fixed_to_double(dx);2331pd.relative_motion.y = wl_fixed_to_double(dy);23322333pd.relative_motion_time = uptime_lo;2334}23352336void 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) {2337SeatState *ss = (SeatState *)data;2338ERR_FAIL_NULL(ss);23392340if (fingers == 2) {2341ss->old_pinch_scale = wl_fixed_from_int(1);2342ss->active_gesture = Gesture::MAGNIFY;2343}2344}23452346void 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) {2347SeatState *ss = (SeatState *)data;2348ERR_FAIL_NULL(ss);23492350// NOTE: From what I can tell, this and all other pointer gestures are separate2351// from the "frame" mechanism of regular pointers. Thus, let's just assume we2352// can read from the "committed" state.2353const PointerData &pd = ss->pointer_data;23542355WaylandThread *wayland_thread = ss->wayland_thread;2356ERR_FAIL_NULL(wayland_thread);23572358WindowState *ws = wayland_thread->window_get_state(pd.pointed_id);2359ERR_FAIL_NULL(ws);23602361double win_scale = window_state_get_scale_factor(ws);23622363if (ss->active_gesture == Gesture::MAGNIFY) {2364Ref<InputEventMagnifyGesture> mg;2365mg.instantiate();23662367mg->set_window_id(pd.pointed_id);23682369if (ws) {2370mg->set_window_id(ws->id);2371}23722373// Set all pressed modifiers.2374mg->set_shift_pressed(ss->shift_pressed);2375mg->set_ctrl_pressed(ss->ctrl_pressed);2376mg->set_alt_pressed(ss->alt_pressed);2377mg->set_meta_pressed(ss->meta_pressed);23782379mg->set_position(pd.position * win_scale);23802381wl_fixed_t scale_delta = scale - ss->old_pinch_scale;2382mg->set_factor(1 + wl_fixed_to_double(scale_delta));23832384Ref<InputEventMessage> magnify_msg;2385magnify_msg.instantiate();2386magnify_msg->event = mg;23872388// Since Wayland allows only one gesture at a time and godot instead expects2389// both of them, we'll have to create two separate input events: one for2390// magnification and one for panning.23912392Ref<InputEventPanGesture> pg;2393pg.instantiate();23942395// Set all pressed modifiers.2396pg->set_shift_pressed(ss->shift_pressed);2397pg->set_ctrl_pressed(ss->ctrl_pressed);2398pg->set_alt_pressed(ss->alt_pressed);2399pg->set_meta_pressed(ss->meta_pressed);24002401pg->set_position(pd.position * win_scale);2402pg->set_delta(Vector2(wl_fixed_to_double(dx), wl_fixed_to_double(dy)));24032404Ref<InputEventMessage> pan_msg;2405pan_msg.instantiate();2406pan_msg->event = pg;24072408wayland_thread->push_message(magnify_msg);2409wayland_thread->push_message(pan_msg);24102411ss->old_pinch_scale = scale;2412}2413}24142415void 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) {2416SeatState *ss = (SeatState *)data;2417ERR_FAIL_NULL(ss);24182419ss->active_gesture = Gesture::NONE;2420}24212422// NOTE: Don't forget to `memfree` the offer's state.2423void 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) {2424wl_proxy_tag_godot((struct wl_proxy *)offer);2425zwp_primary_selection_offer_v1_add_listener(offer, &wp_primary_selection_offer_listener, memnew(OfferState));2426}24272428void 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) {2429SeatState *ss = (SeatState *)data;2430ERR_FAIL_NULL(ss);24312432if (ss->wp_primary_selection_offer) {2433memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer));2434zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer);2435}24362437ss->wp_primary_selection_offer = id;2438}24392440void WaylandThread::_wp_primary_selection_offer_on_offer(void *data, struct zwp_primary_selection_offer_v1 *wp_primary_selection_offer_v1, const char *mime_type) {2441OfferState *os = (OfferState *)data;2442ERR_FAIL_NULL(os);24432444if (os) {2445os->mime_types.insert(String::utf8(mime_type));2446}2447}24482449void 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) {2450SeatState *ss = (SeatState *)data;2451ERR_FAIL_NULL(ss);24522453Vector<uint8_t> *data_to_send = nullptr;24542455if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) {2456data_to_send = &ss->primary_data;2457DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested primary selection.");2458}24592460if (data_to_send) {2461ssize_t written_bytes = 0;24622463if (strcmp(mime_type, "text/plain") == 0) {2464written_bytes = write(fd, data_to_send->ptr(), data_to_send->size());2465}24662467if (written_bytes > 0) {2468DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes));2469} else if (written_bytes == 0) {2470DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent.");2471} else {2472ERR_PRINT(vformat("Clipboard: write error %d.", errno));2473}2474}24752476close(fd);2477}24782479void WaylandThread::_wp_primary_selection_source_on_cancelled(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1) {2480SeatState *ss = (SeatState *)data;2481ERR_FAIL_NULL(ss);24822483if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) {2484zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source);2485ss->wp_primary_selection_source = nullptr;24862487ss->primary_data.clear();24882489DEBUG_LOG_WAYLAND_THREAD("Clipboard: primary selection set by another program.");2490return;2491}2492}24932494void WaylandThread::_wp_tablet_seat_on_tablet_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_v2 *id) {2495}24962497void WaylandThread::_wp_tablet_seat_on_tool_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_tool_v2 *id) {2498SeatState *ss = (SeatState *)data;2499ERR_FAIL_NULL(ss);25002501TabletToolState *state = memnew(TabletToolState);2502state->wl_seat = ss->wl_seat;25032504wl_proxy_tag_godot((struct wl_proxy *)id);2505zwp_tablet_tool_v2_add_listener(id, &wp_tablet_tool_listener, state);2506ss->tablet_tools.push_back(id);2507}25082509void WaylandThread::_wp_tablet_seat_on_pad_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_pad_v2 *id) {2510}25112512void WaylandThread::_wp_tablet_tool_on_type(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t tool_type) {2513TabletToolState *state = wp_tablet_tool_get_state(wp_tablet_tool_v2);25142515if (state && tool_type == ZWP_TABLET_TOOL_V2_TYPE_ERASER) {2516state->is_eraser = true;2517}2518}25192520void 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) {2521}25222523void 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) {2524}25252526void WaylandThread::_wp_tablet_tool_on_capability(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t capability) {2527}25282529void WaylandThread::_wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {2530}25312532void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {2533TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2534if (!ts) {2535return;2536}25372538SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);2539if (!ss) {2540return;2541}25422543List<struct zwp_tablet_tool_v2 *>::Element *E = ss->tablet_tools.find(wp_tablet_tool_v2);25442545if (E && E->get()) {2546struct zwp_tablet_tool_v2 *tool = E->get();2547TabletToolState *state = wp_tablet_tool_get_state(tool);2548if (state) {2549memdelete(state);2550}25512552zwp_tablet_tool_v2_destroy(tool);2553ss->tablet_tools.erase(E);2554}2555}25562557void 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) {2558// NOTE: Works pretty much like wl_pointer::enter.25592560WindowState *ws = wl_surface_get_window_state(surface);2561if (!ws) {2562return;2563}25642565TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2566ERR_FAIL_NULL(ts);25672568ts->data_pending.proximity_serial = serial;2569ts->data_pending.proximal_id = ws->id;2570ts->data_pending.last_proximal_id = ws->id;25712572DEBUG_LOG_WAYLAND_THREAD(vformat("Tablet tool entered window %d.", ts->data_pending.proximal_id));2573}25742575void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {2576// NOTE: Works pretty much like wl_pointer::leave.25772578TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2579ERR_FAIL_NULL(ts);25802581if (ts->data_pending.proximal_id == DisplayServer::INVALID_WINDOW_ID) {2582// We're probably on a decoration or some other third-party thing.2583return;2584}25852586DisplayServer::WindowID id = ts->data_pending.proximal_id;25872588ts->data_pending.proximal_id = DisplayServer::INVALID_WINDOW_ID;2589ts->data_pending.pressed_button_mask.clear();25902591DEBUG_LOG_WAYLAND_THREAD(vformat("Tablet tool left window %d.", id));2592}25932594void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial) {2595// NOTE: Works pretty much like wl_pointer::button but only for a pressed left2596// button.25972598TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2599ERR_FAIL_NULL(ts);26002601TabletToolData &td = ts->data_pending;26022603td.pressed_button_mask.set_flag(mouse_button_to_mask(MouseButton::LEFT));2604td.last_button_pressed = MouseButton::LEFT;2605td.double_click_begun = true;26062607// The protocol doesn't cover this, but we can use this funky hack to make2608// double clicking work.2609td.button_time = OS::get_singleton()->get_ticks_msec();2610}26112612void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {2613// NOTE: Works pretty much like wl_pointer::button but only for a released left2614// button.26152616TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2617ERR_FAIL_NULL(ts);26182619TabletToolData &td = ts->data_pending;26202621td.pressed_button_mask.clear_flag(mouse_button_to_mask(MouseButton::LEFT));26222623// The protocol doesn't cover this, but we can use this funky hack to make2624// double clicking work.2625td.button_time = OS::get_singleton()->get_ticks_msec();2626}26272628void 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) {2629// NOTE: Works pretty much like wl_pointer::motion.26302631TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2632ERR_FAIL_NULL(ts);26332634TabletToolData &td = ts->data_pending;26352636td.position.x = wl_fixed_to_double(x);2637td.position.y = wl_fixed_to_double(y);2638}26392640void WaylandThread::_wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t pressure) {2641TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2642ERR_FAIL_NULL(ts);26432644ts->data_pending.pressure = pressure;2645}26462647void WaylandThread::_wp_tablet_tool_on_distance(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t distance) {2648// Unsupported2649}26502651void 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) {2652TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2653ERR_FAIL_NULL(ts);26542655TabletToolData &td = ts->data_pending;26562657td.tilt.x = wl_fixed_to_double(tilt_x);2658td.tilt.y = wl_fixed_to_double(tilt_y);2659}26602661void WaylandThread::_wp_tablet_tool_on_rotation(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t degrees) {2662// Unsupported.2663}26642665void WaylandThread::_wp_tablet_tool_on_slider(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, int32_t position) {2666// Unsupported.2667}26682669void WaylandThread::_wp_tablet_tool_on_wheel(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t degrees, int32_t clicks) {2670// TODO2671}26722673void 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) {2674// NOTE: Works pretty much like wl_pointer::button.26752676TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2677ERR_FAIL_NULL(ts);26782679TabletToolData &td = ts->data_pending;26802681MouseButton mouse_button = MouseButton::NONE;26822683if (button == BTN_STYLUS) {2684mouse_button = MouseButton::LEFT;2685}26862687if (button == BTN_STYLUS2) {2688mouse_button = MouseButton::RIGHT;2689}26902691if (mouse_button != MouseButton::NONE) {2692MouseButtonMask mask = mouse_button_to_mask(mouse_button);26932694if (state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_PRESSED) {2695td.pressed_button_mask.set_flag(mask);2696td.last_button_pressed = mouse_button;2697td.double_click_begun = true;2698} else {2699td.pressed_button_mask.clear_flag(mask);2700}27012702// The protocol doesn't cover this, but we can use this funky hack to make2703// double clicking work.2704td.button_time = OS::get_singleton()->get_ticks_msec();2705}2706}27072708void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t time) {2709// NOTE: Works pretty much like wl_pointer::frame.27102711TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);2712ERR_FAIL_NULL(ts);27132714SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);2715ERR_FAIL_NULL(ss);27162717WaylandThread *wayland_thread = ss->wayland_thread;2718ERR_FAIL_NULL(wayland_thread);27192720TabletToolData &old_td = ts->data;2721TabletToolData &td = ts->data_pending;27222723if (td.proximal_id != old_td.proximal_id) {2724if (old_td.proximal_id != DisplayServer::INVALID_WINDOW_ID) {2725Ref<WindowEventMessage> msg;2726msg.instantiate();2727msg->id = old_td.proximal_id;2728msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;27292730wayland_thread->push_message(msg);2731}27322733if (td.proximal_id != DisplayServer::INVALID_WINDOW_ID) {2734Ref<WindowEventMessage> msg;2735msg.instantiate();2736msg->id = td.proximal_id;2737msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;27382739wayland_thread->push_message(msg);2740}2741}27422743if (td.proximal_id == DisplayServer::INVALID_WINDOW_ID) {2744// We're probably on a decoration or some other third-party thing. Let's2745// "commit" the data and call it a day.2746old_td = td;2747return;2748}27492750WindowState *ws = wayland_thread->window_get_state(td.proximal_id);2751ERR_FAIL_NULL(ws);27522753double scale = window_state_get_scale_factor(ws);2754if (old_td.position != td.position || old_td.tilt != td.tilt || old_td.pressure != td.pressure) {2755td.motion_time = time;27562757Ref<InputEventMouseMotion> mm;2758mm.instantiate();27592760mm->set_window_id(td.proximal_id);27612762// Set all pressed modifiers.2763mm->set_shift_pressed(ss->shift_pressed);2764mm->set_ctrl_pressed(ss->ctrl_pressed);2765mm->set_alt_pressed(ss->alt_pressed);2766mm->set_meta_pressed(ss->meta_pressed);27672768mm->set_button_mask(td.pressed_button_mask);27692770mm->set_global_position(td.position * scale);2771mm->set_position(td.position * scale);27722773// NOTE: The Godot API expects normalized values and we store them raw,2774// straight from the compositor, so we have to normalize them here.27752776// According to the tablet proto spec, tilt is expressed in degrees relative2777// to the Z axis of the tablet, so it shouldn't go over 90 degrees either way,2778// I think. We'll clamp it just in case.2779td.tilt = td.tilt.clampf(-90, 90);27802781mm->set_tilt(td.tilt / 90);27822783// The tablet proto spec explicitly says that pressure is defined as a value2784// between 0 to 65535.2785mm->set_pressure(td.pressure / (float)65535);27862787mm->set_pen_inverted(ts->is_eraser);27882789Vector2 pos_delta = (td.position - old_td.position) * scale;27902791mm->set_relative(pos_delta);2792mm->set_relative_screen_position(pos_delta);27932794uint32_t time_delta = td.motion_time - old_td.motion_time;2795mm->set_velocity((Vector2)pos_delta / time_delta);27962797Ref<InputEventMessage> inputev_msg;2798inputev_msg.instantiate();27992800inputev_msg->event = mm;28012802wayland_thread->push_message(inputev_msg);2803}28042805if (old_td.pressed_button_mask != td.pressed_button_mask) {2806td.button_time = time;28072808BitField<MouseButtonMask> pressed_mask_delta = old_td.pressed_button_mask.get_different(td.pressed_button_mask);28092810for (MouseButton test_button : { MouseButton::LEFT, MouseButton::RIGHT }) {2811MouseButtonMask test_button_mask = mouse_button_to_mask(test_button);28122813if (pressed_mask_delta.has_flag(test_button_mask)) {2814Ref<InputEventMouseButton> mb;2815mb.instantiate();28162817// Set all pressed modifiers.2818mb->set_shift_pressed(ss->shift_pressed);2819mb->set_ctrl_pressed(ss->ctrl_pressed);2820mb->set_alt_pressed(ss->alt_pressed);2821mb->set_meta_pressed(ss->meta_pressed);28222823mb->set_window_id(td.proximal_id);2824mb->set_position(td.position * scale);2825mb->set_global_position(td.position * scale);28262827mb->set_button_mask(td.pressed_button_mask);2828mb->set_button_index(test_button);2829mb->set_pressed(td.pressed_button_mask.has_flag(test_button_mask));28302831// We have to set the last position pressed here as we can't take for2832// granted what the individual events might have seen due to them not having2833// a garaunteed order.2834if (mb->is_pressed()) {2835td.last_pressed_position = td.position;2836}28372838if (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) {2839td.double_click_begun = false;2840mb->set_double_click(true);2841}28422843Ref<InputEventMessage> msg;2844msg.instantiate();28452846msg->event = mb;28472848wayland_thread->push_message(msg);2849}2850}2851}28522853old_td = td;2854}28552856void WaylandThread::_wp_text_input_on_enter(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface) {2857SeatState *ss = (SeatState *)data;2858if (!ss) {2859return;2860}28612862WindowState *ws = wl_surface_get_window_state(surface);2863if (!ws) {2864return;2865}28662867ss->ime_window_id = ws->id;2868ss->ime_enabled = true;2869}28702871void WaylandThread::_wp_text_input_on_leave(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface) {2872SeatState *ss = (SeatState *)data;2873if (!ss) {2874return;2875}28762877Ref<IMEUpdateEventMessage> msg;2878msg.instantiate();2879msg->id = ss->ime_window_id;2880msg->text = String();2881msg->selection = Vector2i();2882ss->wayland_thread->push_message(msg);28832884ss->ime_window_id = DisplayServer::INVALID_WINDOW_ID;2885ss->ime_enabled = false;2886ss->ime_active = false;2887ss->ime_text = String();2888ss->ime_text_commit = String();2889ss->ime_cursor = Vector2i();2890}28912892void 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) {2893SeatState *ss = (SeatState *)data;2894if (!ss) {2895return;2896}28972898ss->ime_text = String::utf8(text);28992900// Convert cursor positions from UTF-8 to UTF-32 offset.2901int32_t cursor_begin_utf32 = 0;2902int32_t cursor_end_utf32 = 0;2903for (int i = 0; i < ss->ime_text.length(); i++) {2904uint32_t c = ss->ime_text[i];2905if (c <= 0x7f) { // 7 bits.2906cursor_begin -= 1;2907cursor_end -= 1;2908} else if (c <= 0x7ff) { // 11 bits2909cursor_begin -= 2;2910cursor_end -= 2;2911} else if (c <= 0xffff) { // 16 bits2912cursor_begin -= 3;2913cursor_end -= 3;2914} else if (c <= 0x001fffff) { // 21 bits2915cursor_begin -= 4;2916cursor_end -= 4;2917} else if (c <= 0x03ffffff) { // 26 bits2918cursor_begin -= 5;2919cursor_end -= 5;2920} else if (c <= 0x7fffffff) { // 31 bits2921cursor_begin -= 6;2922cursor_end -= 6;2923} else {2924cursor_begin -= 1;2925cursor_end -= 1;2926}2927if (cursor_begin == 0) {2928cursor_begin_utf32 = i + 1;2929}2930if (cursor_end == 0) {2931cursor_end_utf32 = i + 1;2932}2933if (cursor_begin <= 0 && cursor_end <= 0) {2934break;2935}2936}2937ss->ime_cursor = Vector2i(cursor_begin_utf32, cursor_end_utf32 - cursor_begin_utf32);2938}29392940void WaylandThread::_wp_text_input_on_commit_string(void *data, struct zwp_text_input_v3 *wp_text_input_v3, const char *text) {2941SeatState *ss = (SeatState *)data;2942if (!ss) {2943return;2944}29452946ss->ime_text_commit = String::utf8(text);2947}29482949void 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) {2950// Not implemented.2951}29522953void WaylandThread::_wp_text_input_on_done(void *data, struct zwp_text_input_v3 *wp_text_input_v3, uint32_t serial) {2954SeatState *ss = (SeatState *)data;2955if (!ss) {2956return;2957}29582959if (!ss->ime_text_commit.is_empty()) {2960Ref<IMECommitEventMessage> msg;2961msg.instantiate();2962msg->id = ss->ime_window_id;2963msg->text = ss->ime_text_commit;2964ss->wayland_thread->push_message(msg);2965} else {2966Ref<IMEUpdateEventMessage> msg;2967msg.instantiate();2968msg->id = ss->ime_window_id;2969msg->text = ss->ime_text;2970msg->selection = ss->ime_cursor;2971ss->wayland_thread->push_message(msg);2972}29732974ss->ime_text = String();2975ss->ime_text_commit = String();2976ss->ime_cursor = Vector2i();2977}29782979void WaylandThread::_xdg_activation_token_on_done(void *data, struct xdg_activation_token_v1 *xdg_activation_token, const char *token) {2980WindowState *ws = (WindowState *)data;2981ERR_FAIL_NULL(ws);2982ERR_FAIL_NULL(ws->wayland_thread);2983ERR_FAIL_NULL(ws->wl_surface);29842985xdg_activation_v1_activate(ws->wayland_thread->registry.xdg_activation, token, ws->wl_surface);2986xdg_activation_token_v1_destroy(xdg_activation_token);29872988DEBUG_LOG_WAYLAND_THREAD(vformat("Received activation token and requested window activation."));2989}29902991// NOTE: This must be started after a valid wl_display is loaded.2992void WaylandThread::_poll_events_thread(void *p_data) {2993ThreadData *data = (ThreadData *)p_data;2994ERR_FAIL_NULL(data);2995ERR_FAIL_NULL(data->wl_display);29962997struct pollfd poll_fd;2998poll_fd.fd = wl_display_get_fd(data->wl_display);2999poll_fd.events = POLLIN | POLLHUP;30003001while (true) {3002// Empty the event queue while it's full.3003while (wl_display_prepare_read(data->wl_display) != 0) {3004// We aren't using wl_display_dispatch(), instead "manually" handling events3005// through wl_display_dispatch_pending so that we can use a global mutex and3006// be sure that this and the main thread won't race over stuff, as long as3007// the main thread locks it too.3008//3009// Note that the main thread can still call wl_display_roundtrip as that3010// method directly handles all events, effectively bypassing this polling3011// loop and thus the mutex locking, avoiding a deadlock.3012MutexLock mutex_lock(data->mutex);30133014if (wl_display_dispatch_pending(data->wl_display) == -1) {3015// Oh no. We'll check and handle any display error below.3016break;3017}3018}30193020int werror = wl_display_get_error(data->wl_display);30213022if (werror) {3023if (werror == EPROTO) {3024struct wl_interface *wl_interface = nullptr;3025uint32_t id = 0;30263027int error_code = wl_display_get_protocol_error(data->wl_display, (const struct wl_interface **)&wl_interface, &id);3028CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id));3029} else {3030CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror));3031}3032}30333034wl_display_flush(data->wl_display);30353036// Wait for the event file descriptor to have new data.3037poll(&poll_fd, 1, -1);30383039if (data->thread_done.is_set()) {3040wl_display_cancel_read(data->wl_display);3041break;3042}30433044if (poll_fd.revents | POLLIN) {3045// Load the queues with fresh new data.3046wl_display_read_events(data->wl_display);3047} else {3048// Oh well... Stop signaling that we want to read.3049wl_display_cancel_read(data->wl_display);3050}30513052// The docs advise to redispatch unconditionally and it looks like that if we3053// don't do this we can't catch protocol errors, which is bad.3054MutexLock mutex_lock(data->mutex);3055wl_display_dispatch_pending(data->wl_display);3056}3057}30583059struct wl_display *WaylandThread::get_wl_display() const {3060return wl_display;3061}30623063// NOTE: Stuff like libdecor can (and will) register foreign proxies which3064// aren't formatted as we like. This method is needed to detect whether a proxy3065// has our tag. Also, be careful! The proxy has to be manually tagged or it3066// won't be recognized.3067bool WaylandThread::wl_proxy_is_godot(struct wl_proxy *p_proxy) {3068ERR_FAIL_NULL_V(p_proxy, false);30693070return wl_proxy_get_tag(p_proxy) == &proxy_tag;3071}30723073void WaylandThread::wl_proxy_tag_godot(struct wl_proxy *p_proxy) {3074ERR_FAIL_NULL(p_proxy);30753076wl_proxy_set_tag(p_proxy, &proxy_tag);3077}30783079// Returns the wl_surface's `WindowState`, otherwise `nullptr`.3080// NOTE: This will fail if the surface isn't tagged as ours.3081WaylandThread::WindowState *WaylandThread::wl_surface_get_window_state(struct wl_surface *p_surface) {3082if (p_surface && wl_proxy_is_godot((wl_proxy *)p_surface)) {3083return (WindowState *)wl_surface_get_user_data(p_surface);3084}30853086return nullptr;3087}30883089// Returns the wl_outputs's `ScreenState`, otherwise `nullptr`.3090// NOTE: This will fail if the output isn't tagged as ours.3091WaylandThread::ScreenState *WaylandThread::wl_output_get_screen_state(struct wl_output *p_output) {3092if (p_output && wl_proxy_is_godot((wl_proxy *)p_output)) {3093return (ScreenState *)wl_output_get_user_data(p_output);3094}30953096return nullptr;3097}30983099// Returns the wl_seat's `SeatState`, otherwise `nullptr`.3100// NOTE: This will fail if the output isn't tagged as ours.3101WaylandThread::SeatState *WaylandThread::wl_seat_get_seat_state(struct wl_seat *p_seat) {3102if (p_seat && wl_proxy_is_godot((wl_proxy *)p_seat)) {3103return (SeatState *)wl_seat_get_user_data(p_seat);3104}31053106return nullptr;3107}31083109// Returns the wp_tablet_tool's `TabletToolState`, otherwise `nullptr`.3110// NOTE: This will fail if the output isn't tagged as ours.3111WaylandThread::TabletToolState *WaylandThread::wp_tablet_tool_get_state(struct zwp_tablet_tool_v2 *p_tool) {3112if (p_tool && wl_proxy_is_godot((wl_proxy *)p_tool)) {3113return (TabletToolState *)zwp_tablet_tool_v2_get_user_data(p_tool);3114}31153116return nullptr;3117}3118// Returns the wl_data_offer's `OfferState`, otherwise `nullptr`.3119// NOTE: This will fail if the output isn't tagged as ours.3120WaylandThread::OfferState *WaylandThread::wl_data_offer_get_offer_state(struct wl_data_offer *p_offer) {3121if (p_offer && wl_proxy_is_godot((wl_proxy *)p_offer)) {3122return (OfferState *)wl_data_offer_get_user_data(p_offer);3123}31243125return nullptr;3126}31273128// Returns the wl_data_offer's `OfferState`, otherwise `nullptr`.3129// NOTE: This will fail if the output isn't tagged as ours.3130WaylandThread::OfferState *WaylandThread::wp_primary_selection_offer_get_offer_state(struct zwp_primary_selection_offer_v1 *p_offer) {3131if (p_offer && wl_proxy_is_godot((wl_proxy *)p_offer)) {3132return (OfferState *)zwp_primary_selection_offer_v1_get_user_data(p_offer);3133}31343135return nullptr;3136}31373138// This is implemented as a method because this is the simplest way of3139// accounting for dynamic output scale changes.3140int WaylandThread::window_state_get_preferred_buffer_scale(WindowState *p_ws) {3141ERR_FAIL_NULL_V(p_ws, 1);31423143if (p_ws->preferred_fractional_scale > 0) {3144// We're scaling fractionally. Per spec, the buffer scale is always 1.3145return 1;3146}31473148if (p_ws->wl_outputs.is_empty()) {3149DEBUG_LOG_WAYLAND_THREAD("Window has no output associated, returning buffer scale of 1.");3150return 1;3151}31523153// TODO: Cache value?3154int max_size = 1;31553156// ================================ IMPORTANT =================================3157// NOTE: Due to a Godot limitation, we can't really rescale the whole UI yet.3158// Because of this reason, all platforms have resorted to forcing the highest3159// scale possible of a system on any window, despite of what screen it's onto.3160// On this backend everything's already in place for dynamic window scale3161// handling, but in the meantime we'll just select the biggest _global_ output.3162// To restore dynamic scale selection, simply iterate over `p_ws->wl_outputs`3163// instead.3164for (struct wl_output *wl_output : p_ws->registry->wl_outputs) {3165ScreenState *ss = wl_output_get_screen_state(wl_output);31663167if (ss && ss->pending_data.scale > max_size) {3168// NOTE: For some mystical reason, wl_output.done is emitted _after_ windows3169// get resized but the scale event gets sent _before_ that. I'm still leaning3170// towards the idea that rescaling when a window gets a resolution change is a3171// pretty good approach, but this means that we'll have to use the screen data3172// before it's "committed".3173// FIXME: Use the committed data. Somehow.3174max_size = ss->pending_data.scale;3175}3176}31773178return max_size;3179}31803181double WaylandThread::window_state_get_scale_factor(WindowState *p_ws) {3182ERR_FAIL_NULL_V(p_ws, 1);31833184if (p_ws->fractional_scale > 0) {3185// The fractional scale amount takes priority.3186return p_ws->fractional_scale;3187}31883189return p_ws->buffer_scale;3190}31913192void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int p_height) {3193ERR_FAIL_NULL(p_ws);31943195int preferred_buffer_scale = window_state_get_preferred_buffer_scale(p_ws);3196bool using_fractional = p_ws->preferred_fractional_scale > 0;31973198// If neither is true we no-op.3199bool scale_changed = true;3200bool size_changed = true;32013202if (p_ws->rect.size.width != p_width || p_ws->rect.size.height != p_height) {3203p_ws->rect.size.width = p_width;3204p_ws->rect.size.height = p_height;32053206size_changed = true;3207}32083209if (using_fractional && p_ws->fractional_scale != p_ws->preferred_fractional_scale) {3210p_ws->fractional_scale = p_ws->preferred_fractional_scale;3211scale_changed = true;3212}32133214if (p_ws->buffer_scale != preferred_buffer_scale) {3215// The buffer scale is always important, even if we use frac scaling.3216p_ws->buffer_scale = preferred_buffer_scale;3217p_ws->buffer_scale_changed = true;32183219if (!using_fractional) {3220// We don't bother updating everything else if it's turned on though.3221scale_changed = true;3222}3223}32243225if (p_ws->wl_surface) {3226if (p_ws->wp_viewport) {3227wp_viewport_set_destination(p_ws->wp_viewport, p_width, p_height);3228}32293230if (p_ws->xdg_surface) {3231xdg_surface_set_window_geometry(p_ws->xdg_surface, 0, 0, p_width, p_height);3232}3233}32343235#ifdef LIBDECOR_ENABLED3236if (p_ws->libdecor_frame) {3237struct libdecor_state *state = libdecor_state_new(p_width, p_height);3238libdecor_frame_commit(p_ws->libdecor_frame, state, p_ws->pending_libdecor_configuration);3239libdecor_state_free(state);3240p_ws->pending_libdecor_configuration = nullptr;3241}3242#endif32433244if (size_changed || scale_changed) {3245double win_scale = window_state_get_scale_factor(p_ws);3246Size2i scaled_size = scale_vector2i(p_ws->rect.size, win_scale);32473248if (using_fractional) {3249DEBUG_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));3250} else {3251DEBUG_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));3252}32533254// FIXME: Actually resize the hint instead of centering it.3255p_ws->wayland_thread->pointer_set_hint(scaled_size / 2);32563257Ref<WindowRectMessage> rect_msg;3258rect_msg.instantiate();3259rect_msg->id = p_ws->id;3260rect_msg->rect.position = scale_vector2i(p_ws->rect.position, win_scale);3261rect_msg->rect.size = scaled_size;3262p_ws->wayland_thread->push_message(rect_msg);3263}32643265if (scale_changed) {3266Ref<WindowEventMessage> dpi_msg;3267dpi_msg.instantiate();3268dpi_msg->id = p_ws->id;3269dpi_msg->event = DisplayServer::WINDOW_EVENT_DPI_CHANGE;3270p_ws->wayland_thread->push_message(dpi_msg);3271}3272}32733274// Scales a vector according to wp_fractional_scale's rules, where coordinates3275// must be scaled with away from zero half-rounding.3276Vector2i WaylandThread::scale_vector2i(const Vector2i &p_vector, double p_amount) {3277// This snippet is tiny, I know, but this is done a lot.3278int x = std::round(p_vector.x * p_amount);3279int y = std::round(p_vector.y * p_amount);32803281return Vector2i(x, y);3282}32833284void WaylandThread::seat_state_unlock_pointer(SeatState *p_ss) {3285ERR_FAIL_NULL(p_ss);32863287if (p_ss->wl_pointer == nullptr) {3288return;3289}32903291if (p_ss->wp_locked_pointer) {3292zwp_locked_pointer_v1_destroy(p_ss->wp_locked_pointer);3293p_ss->wp_locked_pointer = nullptr;3294}32953296if (p_ss->wp_confined_pointer) {3297zwp_confined_pointer_v1_destroy(p_ss->wp_confined_pointer);3298p_ss->wp_confined_pointer = nullptr;3299}3300}33013302void WaylandThread::seat_state_lock_pointer(SeatState *p_ss) {3303ERR_FAIL_NULL(p_ss);33043305if (p_ss->wl_pointer == nullptr) {3306return;3307}33083309if (registry.wp_pointer_constraints == nullptr) {3310return;3311}33123313if (p_ss->wp_locked_pointer == nullptr) {3314struct wl_surface *locked_surface = window_get_wl_surface(p_ss->pointer_data.last_pointed_id);3315ERR_FAIL_NULL(locked_surface);33163317p_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);3318}3319}33203321void WaylandThread::seat_state_set_hint(SeatState *p_ss, int p_x, int p_y) {3322if (p_ss->wp_locked_pointer == nullptr) {3323return;3324}33253326zwp_locked_pointer_v1_set_cursor_position_hint(p_ss->wp_locked_pointer, wl_fixed_from_int(p_x), wl_fixed_from_int(p_y));3327}33283329void WaylandThread::seat_state_confine_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_confined_pointer == nullptr) {3341struct wl_surface *confined_surface = window_get_wl_surface(p_ss->pointer_data.last_pointed_id);3342ERR_FAIL_NULL(confined_surface);33433344p_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);3345}3346}33473348void WaylandThread::seat_state_update_cursor(SeatState *p_ss) {3349ERR_FAIL_NULL(p_ss);33503351WaylandThread *thread = p_ss->wayland_thread;3352ERR_FAIL_NULL(p_ss->wayland_thread);33533354if (!p_ss->wl_pointer || !p_ss->cursor_surface) {3355return;3356}33573358// NOTE: Those values are valid by default and will hide the cursor when3359// unchanged.3360struct wl_buffer *cursor_buffer = nullptr;3361uint32_t hotspot_x = 0;3362uint32_t hotspot_y = 0;3363int scale = 1;33643365if (thread->cursor_visible) {3366DisplayServer::CursorShape shape = thread->cursor_shape;33673368struct CustomCursor *custom_cursor = thread->custom_cursors.getptr(shape);33693370if (custom_cursor) {3371cursor_buffer = custom_cursor->wl_buffer;3372hotspot_x = custom_cursor->hotspot.x;3373hotspot_y = custom_cursor->hotspot.y;33743375// We can't really reasonably scale custom cursors, so we'll let the3376// compositor do it for us (badly).3377scale = 1;3378} else if (thread->registry.wp_cursor_shape_manager) {3379wp_cursor_shape_device_v1_shape wp_shape = thread->standard_cursors[shape];3380wp_cursor_shape_device_v1_set_shape(p_ss->wp_cursor_shape_device, p_ss->pointer_enter_serial, wp_shape);33813382// We should avoid calling the `wl_pointer_set_cursor` at the end of this method.3383return;3384} else {3385struct wl_cursor *wl_cursor = thread->wl_cursors[shape];33863387if (!wl_cursor) {3388return;3389}33903391int frame_idx = 0;33923393if (wl_cursor->image_count > 1) {3394// The cursor is animated.3395frame_idx = wl_cursor_frame(wl_cursor, p_ss->cursor_time_ms);33963397if (!p_ss->cursor_frame_callback) {3398// Since it's animated, we'll re-update it the next frame.3399p_ss->cursor_frame_callback = wl_surface_frame(p_ss->cursor_surface);3400wl_callback_add_listener(p_ss->cursor_frame_callback, &cursor_frame_callback_listener, p_ss);3401}3402}34033404struct wl_cursor_image *wl_cursor_image = wl_cursor->images[frame_idx];34053406scale = thread->cursor_scale;34073408cursor_buffer = wl_cursor_image_get_buffer(wl_cursor_image);34093410// As the surface's buffer is scaled (thus the surface is smaller) and the3411// hotspot must be expressed in surface-local coordinates, we need to scale3412// it down accordingly.3413hotspot_x = wl_cursor_image->hotspot_x / scale;3414hotspot_y = wl_cursor_image->hotspot_y / scale;3415}3416}34173418wl_pointer_set_cursor(p_ss->wl_pointer, p_ss->pointer_enter_serial, p_ss->cursor_surface, hotspot_x, hotspot_y);3419wl_surface_set_buffer_scale(p_ss->cursor_surface, scale);3420wl_surface_attach(p_ss->cursor_surface, cursor_buffer, 0, 0);3421wl_surface_damage_buffer(p_ss->cursor_surface, 0, 0, INT_MAX, INT_MAX);34223423wl_surface_commit(p_ss->cursor_surface);3424}34253426void WaylandThread::seat_state_echo_keys(SeatState *p_ss) {3427ERR_FAIL_NULL(p_ss);34283429if (p_ss->wl_keyboard == nullptr) {3430return;3431}34323433// TODO: Comment and document out properly this block of code.3434// In short, this implements key repeating.3435if (p_ss->repeat_key_delay_msec && p_ss->repeating_keycode != XKB_KEYCODE_INVALID) {3436uint64_t current_ticks = OS::get_singleton()->get_ticks_msec();3437uint64_t delayed_start_ticks = p_ss->last_repeat_start_msec + p_ss->repeat_start_delay_msec;34383439if (p_ss->last_repeat_msec < delayed_start_ticks) {3440p_ss->last_repeat_msec = delayed_start_ticks;3441}34423443if (current_ticks >= delayed_start_ticks) {3444uint64_t ticks_delta = current_ticks - p_ss->last_repeat_msec;34453446int keys_amount = (ticks_delta / p_ss->repeat_key_delay_msec);34473448for (int i = 0; i < keys_amount; i++) {3449Ref<InputEventKey> k = _seat_state_get_key_event(p_ss, p_ss->repeating_keycode, true);3450if (k.is_null()) {3451continue;3452}34533454k->set_echo(true);34553456Ref<InputEventKey> uk = _seat_state_get_unstuck_key_event(p_ss, p_ss->repeating_keycode, true, k->get_keycode());3457if (uk.is_valid()) {3458Input::get_singleton()->parse_input_event(uk);3459}34603461Input::get_singleton()->parse_input_event(k);3462}34633464p_ss->last_repeat_msec += ticks_delta - (ticks_delta % p_ss->repeat_key_delay_msec);3465}3466}3467}34683469void WaylandThread::push_message(Ref<Message> message) {3470messages.push_back(message);3471}34723473bool WaylandThread::has_message() {3474return messages.front() != nullptr;3475}34763477Ref<WaylandThread::Message> WaylandThread::pop_message() {3478if (messages.front() != nullptr) {3479Ref<Message> msg = messages.front()->get();3480messages.pop_front();3481return msg;3482}34833484// This method should only be called if `has_messages` returns true but if3485// that isn't the case we'll just return an invalid `Ref`. After all, due to3486// its `InputEvent`-like interface, we still have to dynamically cast and check3487// the `Ref`'s validity anyways.3488return Ref<Message>();3489}34903491void WaylandThread::window_create(DisplayServer::WindowID p_window_id, int p_width, int p_height) {3492ERR_FAIL_COND(windows.has(p_window_id));3493WindowState &ws = windows[p_window_id];34943495ws.id = p_window_id;34963497ws.registry = ®istry;3498ws.wayland_thread = this;34993500ws.rect.size.width = p_width;3501ws.rect.size.height = p_height;35023503ws.wl_surface = wl_compositor_create_surface(registry.wl_compositor);3504wl_proxy_tag_godot((struct wl_proxy *)ws.wl_surface);3505wl_surface_add_listener(ws.wl_surface, &wl_surface_listener, &ws);35063507if (registry.wp_viewporter) {3508ws.wp_viewport = wp_viewporter_get_viewport(registry.wp_viewporter, ws.wl_surface);35093510if (registry.wp_fractional_scale_manager) {3511ws.wp_fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(registry.wp_fractional_scale_manager, ws.wl_surface);3512wp_fractional_scale_v1_add_listener(ws.wp_fractional_scale, &wp_fractional_scale_listener, &ws);3513}3514}35153516bool decorated = false;35173518#ifdef LIBDECOR_ENABLED3519if (!decorated && libdecor_context) {3520ws.libdecor_frame = libdecor_decorate(libdecor_context, ws.wl_surface, (struct libdecor_frame_interface *)&libdecor_frame_interface, &ws);3521libdecor_frame_map(ws.libdecor_frame);35223523decorated = true;3524}3525#endif35263527if (!decorated) {3528// libdecor has failed loading or is disabled, we shall handle xdg_toplevel3529// creation and decoration ourselves (and by decorating for now I just mean3530// asking for SSDs and hoping for the best).3531ws.xdg_surface = xdg_wm_base_get_xdg_surface(registry.xdg_wm_base, ws.wl_surface);3532xdg_surface_add_listener(ws.xdg_surface, &xdg_surface_listener, &ws);35333534ws.xdg_toplevel = xdg_surface_get_toplevel(ws.xdg_surface);3535xdg_toplevel_add_listener(ws.xdg_toplevel, &xdg_toplevel_listener, &ws);35363537if (registry.xdg_decoration_manager) {3538ws.xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(registry.xdg_decoration_manager, ws.xdg_toplevel);3539zxdg_toplevel_decoration_v1_add_listener(ws.xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, &ws);35403541decorated = true;3542}3543}35443545ws.frame_callback = wl_surface_frame(ws.wl_surface);3546wl_callback_add_listener(ws.frame_callback, &frame_wl_callback_listener, &ws);35473548if (registry.xdg_exporter_v2) {3549ws.xdg_exported_v2 = zxdg_exporter_v2_export_toplevel(registry.xdg_exporter_v2, ws.wl_surface);3550zxdg_exported_v2_add_listener(ws.xdg_exported_v2, &xdg_exported_v2_listener, &ws);3551} else if (registry.xdg_exporter_v1) {3552ws.xdg_exported_v1 = zxdg_exporter_v1_export(registry.xdg_exporter_v1, ws.wl_surface);3553zxdg_exported_v1_add_listener(ws.xdg_exported_v1, &xdg_exported_v1_listener, &ws);3554}35553556wl_surface_commit(ws.wl_surface);35573558// Wait for the surface to be configured before continuing.3559wl_display_roundtrip(wl_display);35603561window_state_update_size(&ws, ws.rect.size.width, ws.rect.size.height);3562}35633564void WaylandThread::window_create_popup(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id, Rect2i p_rect) {3565ERR_FAIL_COND(windows.has(p_window_id));3566ERR_FAIL_COND(!windows.has(p_parent_id));35673568WindowState &ws = windows[p_window_id];3569WindowState &parent = windows[p_parent_id];35703571double parent_scale = window_state_get_scale_factor(&parent);35723573p_rect.position = scale_vector2i(p_rect.position, 1.0 / parent_scale);3574p_rect.size = scale_vector2i(p_rect.size, 1.0 / parent_scale);35753576ws.id = p_window_id;3577ws.parent_id = p_parent_id;3578ws.registry = ®istry;3579ws.wayland_thread = this;35803581ws.rect = p_rect;35823583ws.wl_surface = wl_compositor_create_surface(registry.wl_compositor);3584wl_proxy_tag_godot((struct wl_proxy *)ws.wl_surface);3585wl_surface_add_listener(ws.wl_surface, &wl_surface_listener, &ws);35863587if (registry.wp_viewporter) {3588ws.wp_viewport = wp_viewporter_get_viewport(registry.wp_viewporter, ws.wl_surface);35893590if (registry.wp_fractional_scale_manager) {3591ws.wp_fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(registry.wp_fractional_scale_manager, ws.wl_surface);3592wp_fractional_scale_v1_add_listener(ws.wp_fractional_scale, &wp_fractional_scale_listener, &ws);3593}3594}35953596ws.xdg_surface = xdg_wm_base_get_xdg_surface(registry.xdg_wm_base, ws.wl_surface);3597xdg_surface_add_listener(ws.xdg_surface, &xdg_surface_listener, &ws);35983599Rect2i positioner_rect;3600positioner_rect.size = parent.rect.size;3601struct xdg_surface *parent_xdg_surface = parent.xdg_surface;36023603Point2i offset = ws.rect.position - parent.rect.position;36043605#ifdef LIBDECOR_ENABLED3606if (!parent_xdg_surface && parent.libdecor_frame) {3607parent_xdg_surface = libdecor_frame_get_xdg_surface(parent.libdecor_frame);36083609int corner_x = 0;3610int corner_y = 0;3611libdecor_frame_translate_coordinate(parent.libdecor_frame, 0, 0, &corner_x, &corner_y);36123613positioner_rect.position.x = corner_x;3614positioner_rect.position.y = corner_y;36153616positioner_rect.size.width -= corner_x;3617positioner_rect.size.height -= corner_y;3618}3619#endif36203621ERR_FAIL_NULL(parent_xdg_surface);36223623struct xdg_positioner *xdg_positioner = xdg_wm_base_create_positioner(registry.xdg_wm_base);3624xdg_positioner_set_size(xdg_positioner, ws.rect.size.width, ws.rect.size.height);3625xdg_positioner_set_anchor(xdg_positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);3626xdg_positioner_set_gravity(xdg_positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);3627xdg_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);3628xdg_positioner_set_anchor_rect(xdg_positioner, positioner_rect.position.x, positioner_rect.position.y, positioner_rect.size.width, positioner_rect.size.height);3629xdg_positioner_set_offset(xdg_positioner, offset.x, offset.y);36303631ws.xdg_popup = xdg_surface_get_popup(ws.xdg_surface, parent_xdg_surface, xdg_positioner);3632xdg_popup_add_listener(ws.xdg_popup, &xdg_popup_listener, &ws);36333634xdg_positioner_destroy(xdg_positioner);36353636ws.frame_callback = wl_surface_frame(ws.wl_surface);3637wl_callback_add_listener(ws.frame_callback, &frame_wl_callback_listener, &ws);36383639wl_surface_commit(ws.wl_surface);36403641// Wait for the surface to be configured before continuing.3642wl_display_roundtrip(wl_display);3643}36443645void WaylandThread::window_destroy(DisplayServer::WindowID p_window_id) {3646ERR_FAIL_COND(!windows.has(p_window_id));3647WindowState &ws = windows[p_window_id];36483649if (ws.xdg_popup) {3650xdg_popup_destroy(ws.xdg_popup);3651}36523653if (ws.xdg_toplevel_decoration) {3654zxdg_toplevel_decoration_v1_destroy(ws.xdg_toplevel_decoration);3655}36563657if (ws.xdg_toplevel) {3658xdg_toplevel_destroy(ws.xdg_toplevel);3659}36603661#ifdef LIBDECOR_ENABLED3662if (ws.libdecor_frame) {3663libdecor_frame_unref(ws.libdecor_frame);3664}3665#endif // LIBDECOR_ENABLED36663667if (ws.wp_fractional_scale) {3668wp_fractional_scale_v1_destroy(ws.wp_fractional_scale);3669}36703671if (ws.wp_viewport) {3672wp_viewport_destroy(ws.wp_viewport);3673}36743675if (ws.frame_callback) {3676wl_callback_destroy(ws.frame_callback);3677}36783679if (ws.xdg_surface) {3680xdg_surface_destroy(ws.xdg_surface);3681}36823683if (ws.wl_surface) {3684wl_surface_destroy(ws.wl_surface);3685}36863687// Before continuing, let's handle any leftover event that might still refer to3688// this window.3689wl_display_roundtrip(wl_display);36903691// We can already clean up here, we're done.3692windows.erase(p_window_id);3693}36943695struct wl_surface *WaylandThread::window_get_wl_surface(DisplayServer::WindowID p_window_id) const {3696ERR_FAIL_COND_V(!windows.has(p_window_id), nullptr);3697const WindowState &ws = windows[p_window_id];36983699return ws.wl_surface;3700}37013702WaylandThread::WindowState *WaylandThread::window_get_state(DisplayServer::WindowID p_window_id) {3703return windows.getptr(p_window_id);3704}37053706void WaylandThread::beep() const {3707if (registry.xdg_system_bell) {3708xdg_system_bell_v1_ring(registry.xdg_system_bell, nullptr);3709}3710}37113712void WaylandThread::window_start_drag(DisplayServer::WindowID p_window_id) {3713ERR_FAIL_COND(!windows.has(p_window_id));3714WindowState &ws = windows[p_window_id];3715SeatState *ss = wl_seat_get_seat_state(wl_seat_current);37163717if (ss && ws.xdg_toplevel) {3718xdg_toplevel_move(ws.xdg_toplevel, ss->wl_seat, ss->pointer_data.button_serial);3719}37203721#ifdef LIBDECOR_ENABLED3722if (ws.libdecor_frame) {3723libdecor_frame_move(ws.libdecor_frame, ss->wl_seat, ss->pointer_data.button_serial);3724}3725#endif3726}37273728void WaylandThread::window_start_resize(DisplayServer::WindowResizeEdge p_edge, DisplayServer::WindowID p_window) {3729ERR_FAIL_COND(!windows.has(p_window));3730WindowState &ws = windows[p_window];3731SeatState *ss = wl_seat_get_seat_state(wl_seat_current);37323733if (ss && ws.xdg_toplevel) {3734xdg_toplevel_resize_edge edge = XDG_TOPLEVEL_RESIZE_EDGE_NONE;3735switch (p_edge) {3736case DisplayServer::WINDOW_EDGE_TOP_LEFT: {3737edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;3738} break;3739case DisplayServer::WINDOW_EDGE_TOP: {3740edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP;3741} break;3742case DisplayServer::WINDOW_EDGE_TOP_RIGHT: {3743edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;3744} break;3745case DisplayServer::WINDOW_EDGE_LEFT: {3746edge = XDG_TOPLEVEL_RESIZE_EDGE_LEFT;3747} break;3748case DisplayServer::WINDOW_EDGE_RIGHT: {3749edge = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT;3750} break;3751case DisplayServer::WINDOW_EDGE_BOTTOM_LEFT: {3752edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;3753} break;3754case DisplayServer::WINDOW_EDGE_BOTTOM: {3755edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;3756} break;3757case DisplayServer::WINDOW_EDGE_BOTTOM_RIGHT: {3758edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;3759} break;3760default:3761break;3762}3763xdg_toplevel_resize(ws.xdg_toplevel, ss->wl_seat, ss->pointer_data.button_serial, edge);3764}37653766#ifdef LIBDECOR_ENABLED3767if (ws.libdecor_frame) {3768libdecor_resize_edge edge = LIBDECOR_RESIZE_EDGE_NONE;3769switch (p_edge) {3770case DisplayServer::WINDOW_EDGE_TOP_LEFT: {3771edge = LIBDECOR_RESIZE_EDGE_TOP_LEFT;3772} break;3773case DisplayServer::WINDOW_EDGE_TOP: {3774edge = LIBDECOR_RESIZE_EDGE_TOP;3775} break;3776case DisplayServer::WINDOW_EDGE_TOP_RIGHT: {3777edge = LIBDECOR_RESIZE_EDGE_TOP_RIGHT;3778} break;3779case DisplayServer::WINDOW_EDGE_LEFT: {3780edge = LIBDECOR_RESIZE_EDGE_LEFT;3781} break;3782case DisplayServer::WINDOW_EDGE_RIGHT: {3783edge = LIBDECOR_RESIZE_EDGE_RIGHT;3784} break;3785case DisplayServer::WINDOW_EDGE_BOTTOM_LEFT: {3786edge = LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT;3787} break;3788case DisplayServer::WINDOW_EDGE_BOTTOM: {3789edge = LIBDECOR_RESIZE_EDGE_BOTTOM;3790} break;3791case DisplayServer::WINDOW_EDGE_BOTTOM_RIGHT: {3792edge = LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT;3793} break;3794default:3795break;3796}3797libdecor_frame_resize(ws.libdecor_frame, ss->wl_seat, ss->pointer_data.button_serial, edge);3798}3799#endif3800}38013802void WaylandThread::window_set_parent(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id) {3803ERR_FAIL_COND(!windows.has(p_window_id));3804ERR_FAIL_COND(!windows.has(p_parent_id));38053806WindowState &child = windows[p_window_id];3807child.parent_id = p_parent_id;38083809WindowState &parent = windows[p_parent_id];38103811// NOTE: We can't really unparent as, at the time of writing, libdecor3812// segfaults when trying to set a null parent. Hopefully unparenting is not3813// that common. Bummer.38143815#ifdef LIBDECOR_ENABLED3816if (child.libdecor_frame && parent.libdecor_frame) {3817libdecor_frame_set_parent(child.libdecor_frame, parent.libdecor_frame);3818return;3819}3820#endif38213822if (child.xdg_toplevel && parent.xdg_toplevel) {3823xdg_toplevel_set_parent(child.xdg_toplevel, parent.xdg_toplevel);3824}3825}38263827void WaylandThread::window_set_max_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {3828ERR_FAIL_COND(!windows.has(p_window_id));3829WindowState &ws = windows[p_window_id];38303831Vector2i logical_max_size = scale_vector2i(p_size, 1 / window_state_get_scale_factor(&ws));38323833if (ws.wl_surface && ws.xdg_toplevel) {3834xdg_toplevel_set_max_size(ws.xdg_toplevel, logical_max_size.width, logical_max_size.height);3835}38363837#ifdef LIBDECOR_ENABLED3838if (ws.libdecor_frame) {3839libdecor_frame_set_max_content_size(ws.libdecor_frame, logical_max_size.width, logical_max_size.height);3840}38413842// FIXME: I'm not sure whether we have to commit the surface for this to apply.3843#endif3844}38453846void WaylandThread::window_set_min_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {3847ERR_FAIL_COND(!windows.has(p_window_id));3848WindowState &ws = windows[p_window_id];38493850Size2i logical_min_size = scale_vector2i(p_size, 1 / window_state_get_scale_factor(&ws));38513852if (ws.wl_surface && ws.xdg_toplevel) {3853xdg_toplevel_set_min_size(ws.xdg_toplevel, logical_min_size.width, logical_min_size.height);3854}38553856#ifdef LIBDECOR_ENABLED3857if (ws.libdecor_frame) {3858libdecor_frame_set_min_content_size(ws.libdecor_frame, logical_min_size.width, logical_min_size.height);3859}38603861// FIXME: I'm not sure whether we have to commit the surface for this to apply.3862#endif3863}38643865bool WaylandThread::window_can_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) const {3866ERR_FAIL_COND_V(!windows.has(p_window_id), false);3867const WindowState &ws = windows[p_window_id];38683869switch (p_window_mode) {3870case DisplayServer::WINDOW_MODE_WINDOWED: {3871// Looks like it's guaranteed.3872return true;3873};38743875case DisplayServer::WINDOW_MODE_MINIMIZED: {3876#ifdef LIBDECOR_ENABLED3877if (ws.libdecor_frame) {3878return libdecor_frame_has_capability(ws.libdecor_frame, LIBDECOR_ACTION_MINIMIZE);3879}3880#endif // LIBDECOR_ENABLED38813882return ws.can_minimize;3883};38843885case DisplayServer::WINDOW_MODE_MAXIMIZED: {3886// NOTE: libdecor doesn't seem to have a maximize capability query?3887// The fact that there's a fullscreen one makes me suspicious.3888return ws.can_maximize;3889};38903891case DisplayServer::WINDOW_MODE_FULLSCREEN:3892case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: {3893#ifdef LIBDECOR_ENABLED3894if (ws.libdecor_frame) {3895return libdecor_frame_has_capability(ws.libdecor_frame, LIBDECOR_ACTION_FULLSCREEN);3896}3897#endif // LIBDECOR_ENABLED38983899return ws.can_fullscreen;3900};3901}39023903return false;3904}39053906void WaylandThread::window_try_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) {3907ERR_FAIL_COND(!windows.has(p_window_id));3908WindowState &ws = windows[p_window_id];39093910if (ws.mode == p_window_mode) {3911return;3912}39133914// Don't waste time with hidden windows and whatnot. Behave like it worked.3915#ifdef LIBDECOR_ENABLED3916if ((!ws.wl_surface || !ws.xdg_toplevel) && !ws.libdecor_frame) {3917#else3918if (!ws.wl_surface || !ws.xdg_toplevel) {3919#endif // LIBDECOR_ENABLED3920ws.mode = p_window_mode;3921return;3922}39233924// Return back to a windowed state so that we can apply what the user asked.3925switch (ws.mode) {3926case DisplayServer::WINDOW_MODE_WINDOWED: {3927// Do nothing.3928} break;39293930case DisplayServer::WINDOW_MODE_MINIMIZED: {3931// We can't do much according to the xdg_shell protocol. I have no idea3932// whether this implies that we should return or who knows what. For now3933// we'll do nothing.3934// TODO: Test this properly.3935} break;39363937case DisplayServer::WINDOW_MODE_MAXIMIZED: {3938// Try to unmaximize. This isn't garaunteed to work actually, so we'll have3939// to check whether something changed.3940if (ws.xdg_toplevel) {3941xdg_toplevel_unset_maximized(ws.xdg_toplevel);3942}39433944#ifdef LIBDECOR_ENABLED3945if (ws.libdecor_frame) {3946libdecor_frame_unset_maximized(ws.libdecor_frame);3947}3948#endif // LIBDECOR_ENABLED3949} break;39503951case DisplayServer::WINDOW_MODE_FULLSCREEN:3952case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: {3953// Same thing as above, unset fullscreen and check later if it worked.3954if (ws.xdg_toplevel) {3955xdg_toplevel_unset_fullscreen(ws.xdg_toplevel);3956}39573958#ifdef LIBDECOR_ENABLED3959if (ws.libdecor_frame) {3960libdecor_frame_unset_fullscreen(ws.libdecor_frame);3961}3962#endif // LIBDECOR_ENABLED3963} break;3964}39653966// Wait for a configure event and hope that something changed.3967wl_display_roundtrip(wl_display);39683969if (ws.mode != DisplayServer::WINDOW_MODE_WINDOWED) {3970// The compositor refused our "normalization" request. It'd be useless or3971// unpredictable to attempt setting a new state. We're done.3972return;3973}39743975// Ask the compositor to set the state indicated by the new mode.3976switch (p_window_mode) {3977case DisplayServer::WINDOW_MODE_WINDOWED: {3978// Do nothing. We're already windowed.3979} break;39803981case DisplayServer::WINDOW_MODE_MINIMIZED: {3982if (!window_can_set_mode(p_window_id, p_window_mode)) {3983// Minimization is special (read below). Better not mess with it if the3984// compositor explicitly announces that it doesn't support it.3985break;3986}39873988if (ws.xdg_toplevel) {3989xdg_toplevel_set_minimized(ws.xdg_toplevel);3990}39913992#ifdef LIBDECOR_ENABLED3993if (ws.libdecor_frame) {3994libdecor_frame_set_minimized(ws.libdecor_frame);3995}3996#endif // LIBDECOR_ENABLED3997// We have no way to actually detect this state, so we'll have to report it3998// manually to the engine (hoping that it worked). In the worst case it'll3999// get reset by the next configure event.4000ws.mode = DisplayServer::WINDOW_MODE_MINIMIZED;4001} break;40024003case DisplayServer::WINDOW_MODE_MAXIMIZED: {4004if (ws.xdg_toplevel) {4005xdg_toplevel_set_maximized(ws.xdg_toplevel);4006}40074008#ifdef LIBDECOR_ENABLED4009if (ws.libdecor_frame) {4010libdecor_frame_set_maximized(ws.libdecor_frame);4011}4012#endif // LIBDECOR_ENABLED4013} break;40144015case DisplayServer::WINDOW_MODE_FULLSCREEN:4016case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: {4017if (ws.xdg_toplevel) {4018xdg_toplevel_set_fullscreen(ws.xdg_toplevel, nullptr);4019}40204021#ifdef LIBDECOR_ENABLED4022if (ws.libdecor_frame) {4023libdecor_frame_set_fullscreen(ws.libdecor_frame, nullptr);4024}4025#endif // LIBDECOR_ENABLED4026} break;40274028default: {4029} break;4030}4031}40324033void WaylandThread::window_set_borderless(DisplayServer::WindowID p_window_id, bool p_borderless) {4034ERR_FAIL_COND(!windows.has(p_window_id));4035WindowState &ws = windows[p_window_id];40364037if (ws.xdg_toplevel_decoration) {4038if (p_borderless) {4039// We implement borderless windows by simply asking the compositor to let4040// us handle decorations (we don't).4041zxdg_toplevel_decoration_v1_set_mode(ws.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);4042} else {4043zxdg_toplevel_decoration_v1_set_mode(ws.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);4044}4045}40464047#ifdef LIBDECOR_ENABLED4048if (ws.libdecor_frame) {4049bool visible_current = libdecor_frame_is_visible(ws.libdecor_frame);4050bool visible_target = !p_borderless;40514052// NOTE: We have to do this otherwise we trip on a libdecor bug where it's4053// possible to destroy the frame more than once, by setting the visibility4054// to false multiple times and thus crashing.4055if (visible_current != visible_target) {4056print_verbose(vformat("Setting libdecor frame visibility to %d", visible_target));4057libdecor_frame_set_visibility(ws.libdecor_frame, visible_target);4058}4059}4060#endif // LIBDECOR_ENABLED4061}40624063void WaylandThread::window_set_title(DisplayServer::WindowID p_window_id, const String &p_title) {4064ERR_FAIL_COND(!windows.has(p_window_id));4065WindowState &ws = windows[p_window_id];40664067#ifdef LIBDECOR_ENABLED4068if (ws.libdecor_frame) {4069libdecor_frame_set_title(ws.libdecor_frame, p_title.utf8().get_data());4070}4071#endif // LIBDECOR_ENABLE40724073if (ws.xdg_toplevel) {4074xdg_toplevel_set_title(ws.xdg_toplevel, p_title.utf8().get_data());4075}4076}40774078void WaylandThread::window_set_app_id(DisplayServer::WindowID p_window_id, const String &p_app_id) {4079ERR_FAIL_COND(!windows.has(p_window_id));4080WindowState &ws = windows[p_window_id];40814082#ifdef LIBDECOR_ENABLED4083if (ws.libdecor_frame) {4084libdecor_frame_set_app_id(ws.libdecor_frame, p_app_id.utf8().get_data());4085return;4086}4087#endif // LIBDECOR_ENABLED40884089if (ws.xdg_toplevel) {4090xdg_toplevel_set_app_id(ws.xdg_toplevel, p_app_id.utf8().get_data());4091return;4092}4093}40944095DisplayServer::WindowMode WaylandThread::window_get_mode(DisplayServer::WindowID p_window_id) const {4096ERR_FAIL_COND_V(!windows.has(p_window_id), DisplayServer::WINDOW_MODE_WINDOWED);4097const WindowState &ws = windows[p_window_id];40984099return ws.mode;4100}41014102void WaylandThread::window_request_attention(DisplayServer::WindowID p_window_id) {4103ERR_FAIL_COND(!windows.has(p_window_id));4104WindowState &ws = windows[p_window_id];41054106if (registry.xdg_activation) {4107// Window attention requests are done through the XDG activation protocol.4108xdg_activation_token_v1 *xdg_activation_token = xdg_activation_v1_get_activation_token(registry.xdg_activation);4109xdg_activation_token_v1_add_listener(xdg_activation_token, &xdg_activation_token_listener, &ws);4110xdg_activation_token_v1_commit(xdg_activation_token);4111}4112}41134114void WaylandThread::window_set_idle_inhibition(DisplayServer::WindowID p_window_id, bool p_enable) {4115ERR_FAIL_COND(!windows.has(p_window_id));4116WindowState &ws = windows[p_window_id];41174118if (p_enable) {4119if (ws.registry->wp_idle_inhibit_manager && !ws.wp_idle_inhibitor) {4120ERR_FAIL_NULL(ws.wl_surface);4121ws.wp_idle_inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(ws.registry->wp_idle_inhibit_manager, ws.wl_surface);4122}4123} else {4124if (ws.wp_idle_inhibitor) {4125zwp_idle_inhibitor_v1_destroy(ws.wp_idle_inhibitor);4126ws.wp_idle_inhibitor = nullptr;4127}4128}4129}41304131bool WaylandThread::window_get_idle_inhibition(DisplayServer::WindowID p_window_id) const {4132ERR_FAIL_COND_V(!windows.has(p_window_id), false);4133const WindowState &ws = windows[p_window_id];41344135return ws.wp_idle_inhibitor != nullptr;4136}41374138WaylandThread::ScreenData WaylandThread::screen_get_data(int p_screen) const {4139ERR_FAIL_INDEX_V(p_screen, registry.wl_outputs.size(), ScreenData());41404141return wl_output_get_screen_state(registry.wl_outputs.get(p_screen))->data;4142}41434144int WaylandThread::get_screen_count() const {4145return registry.wl_outputs.size();4146}41474148DisplayServer::WindowID WaylandThread::pointer_get_pointed_window_id() const {4149SeatState *ss = wl_seat_get_seat_state(wl_seat_current);41504151if (ss) {4152// Let's determine the most recently used tablet tool.4153TabletToolState *max_ts = nullptr;4154for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {4155TabletToolState *ts = wp_tablet_tool_get_state(tool);4156ERR_CONTINUE(ts == nullptr);41574158TabletToolData &td = ts->data;41594160if (!max_ts) {4161max_ts = ts;4162continue;4163}41644165if (MAX(td.button_time, td.motion_time) > MAX(max_ts->data.button_time, max_ts->data.motion_time)) {4166max_ts = ts;4167}4168}41694170const PointerData &pd = ss->pointer_data;41714172if (max_ts) {4173TabletToolData &td = max_ts->data;4174if (MAX(td.button_time, td.motion_time) > MAX(pd.button_time, pd.motion_time)) {4175return td.proximal_id;4176}4177}41784179return ss->pointer_data.pointed_id;4180}41814182return DisplayServer::INVALID_WINDOW_ID;4183}4184DisplayServer::WindowID WaylandThread::pointer_get_last_pointed_window_id() const {4185SeatState *ss = wl_seat_get_seat_state(wl_seat_current);41864187if (ss) {4188// Let's determine the most recently used tablet tool.4189TabletToolState *max_ts = nullptr;4190for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {4191TabletToolState *ts = wp_tablet_tool_get_state(tool);4192ERR_CONTINUE(ts == nullptr);41934194TabletToolData &td = ts->data;41954196if (!max_ts) {4197max_ts = ts;4198continue;4199}42004201if (MAX(td.button_time, td.motion_time) > MAX(max_ts->data.button_time, max_ts->data.motion_time)) {4202max_ts = ts;4203}4204}42054206const PointerData &pd = ss->pointer_data;42074208if (max_ts) {4209TabletToolData &td = max_ts->data;4210if (MAX(td.button_time, td.motion_time) > MAX(pd.button_time, pd.motion_time)) {4211return td.last_proximal_id;4212}4213}42144215return ss->pointer_data.last_pointed_id;4216}42174218return DisplayServer::INVALID_WINDOW_ID;4219}42204221void WaylandThread::pointer_set_constraint(PointerConstraint p_constraint) {4222SeatState *ss = wl_seat_get_seat_state(wl_seat_current);42234224if (ss) {4225seat_state_unlock_pointer(ss);42264227if (p_constraint == PointerConstraint::LOCKED) {4228seat_state_lock_pointer(ss);4229} else if (p_constraint == PointerConstraint::CONFINED) {4230seat_state_confine_pointer(ss);4231}4232}42334234pointer_constraint = p_constraint;4235}42364237void WaylandThread::pointer_set_hint(const Point2i &p_hint) {4238SeatState *ss = wl_seat_get_seat_state(wl_seat_current);4239if (!ss) {4240return;4241}42424243WindowState *ws = window_get_state(ss->pointer_data.pointed_id);42444245int hint_x = 0;4246int hint_y = 0;42474248if (ws) {4249// NOTE: It looks like it's not really recommended to convert from4250// "godot-space" to "wayland-space" and in general I received mixed feelings4251// discussing about this. I'm not really sure about the maths behind this but,4252// oh well, we're setting a cursor hint. ¯\_(ツ)_/¯4253// See: https://oftc.irclog.whitequark.org/wayland/2023-08-23#1692756914-16928168184254hint_x = std::round(p_hint.x / window_state_get_scale_factor(ws));4255hint_y = std::round(p_hint.y / window_state_get_scale_factor(ws));4256}42574258if (ss) {4259seat_state_set_hint(ss, hint_x, hint_y);4260}4261}42624263WaylandThread::PointerConstraint WaylandThread::pointer_get_constraint() const {4264return pointer_constraint;4265}42664267BitField<MouseButtonMask> WaylandThread::pointer_get_button_mask() const {4268SeatState *ss = wl_seat_get_seat_state(wl_seat_current);42694270if (ss) {4271return ss->pointer_data.pressed_button_mask;4272}42734274return BitField<MouseButtonMask>();4275}42764277Error WaylandThread::init() {4278#ifdef SOWRAP_ENABLED4279#ifdef DEBUG_ENABLED4280int dylibloader_verbose = 1;4281#else4282int dylibloader_verbose = 0;4283#endif // DEBUG_ENABLED42844285if (initialize_wayland_client(dylibloader_verbose) != 0) {4286WARN_PRINT("Can't load the Wayland client library.");4287return ERR_CANT_CREATE;4288}42894290if (initialize_wayland_cursor(dylibloader_verbose) != 0) {4291WARN_PRINT("Can't load the Wayland cursor library.");4292return ERR_CANT_CREATE;4293}42944295if (initialize_xkbcommon(dylibloader_verbose) != 0) {4296WARN_PRINT("Can't load the XKBcommon library.");4297return ERR_CANT_CREATE;4298}4299#endif // SOWRAP_ENABLED43004301KeyMappingXKB::initialize();43024303wl_display = wl_display_connect(nullptr);4304ERR_FAIL_NULL_V_MSG(wl_display, ERR_CANT_CREATE, "Can't connect to a Wayland display.");43054306thread_data.wl_display = wl_display;43074308events_thread.start(_poll_events_thread, &thread_data);43094310wl_registry = wl_display_get_registry(wl_display);43114312ERR_FAIL_NULL_V_MSG(wl_registry, ERR_UNAVAILABLE, "Can't obtain the Wayland registry global.");43134314registry.wayland_thread = this;43154316wl_registry_add_listener(wl_registry, &wl_registry_listener, ®istry);43174318// Wait for registry to get notified from the compositor.4319wl_display_roundtrip(wl_display);43204321ERR_FAIL_NULL_V_MSG(registry.wl_shm, ERR_UNAVAILABLE, "Can't obtain the Wayland shared memory global.");4322ERR_FAIL_NULL_V_MSG(registry.wl_compositor, ERR_UNAVAILABLE, "Can't obtain the Wayland compositor global.");4323ERR_FAIL_NULL_V_MSG(registry.xdg_wm_base, ERR_UNAVAILABLE, "Can't obtain the Wayland XDG shell global.");43244325if (!registry.xdg_decoration_manager) {4326#ifdef LIBDECOR_ENABLED4327WARN_PRINT("Can't obtain the XDG decoration manager. Libdecor will be used for drawing CSDs, if available.");4328#else4329WARN_PRINT("Can't obtain the XDG decoration manager. Decorations won't show up.");4330#endif // LIBDECOR_ENABLED4331}43324333if (!registry.xdg_activation) {4334WARN_PRINT("Can't obtain the XDG activation global. Attention requesting won't work!");4335}43364337#ifndef DBUS_ENABLED4338if (!registry.wp_idle_inhibit_manager) {4339WARN_PRINT("Can't obtain the idle inhibition manager. The screen might turn off even after calling screen_set_keep_on()!");4340}4341#endif // DBUS_ENABLED43424343if (!registry.wp_fifo_manager_name) {4344WARN_PRINT("FIFO protocol not found! Frame pacing will be degraded.");4345}43464347// Wait for seat capabilities.4348wl_display_roundtrip(wl_display);43494350#ifdef LIBDECOR_ENABLED4351bool libdecor_found = true;43524353#ifdef SOWRAP_ENABLED4354if (initialize_libdecor(dylibloader_verbose) != 0) {4355libdecor_found = false;4356}4357#endif // SOWRAP_ENABLED43584359if (libdecor_found) {4360libdecor_context = libdecor_new(wl_display, (struct libdecor_interface *)&libdecor_interface);4361} else {4362print_verbose("libdecor not found. Client-side decorations disabled.");4363}4364#endif // LIBDECOR_ENABLED43654366cursor_theme_name = OS::get_singleton()->get_environment("XCURSOR_THEME");43674368unscaled_cursor_size = OS::get_singleton()->get_environment("XCURSOR_SIZE").to_int();4369if (unscaled_cursor_size <= 0) {4370print_verbose("Detected invalid cursor size preference, defaulting to 24.");4371unscaled_cursor_size = 24;4372}43734374// NOTE: The scale is useful here as it might've been updated by _update_scale.4375bool cursor_theme_loaded = _load_cursor_theme(unscaled_cursor_size * cursor_scale);43764377if (!cursor_theme_loaded) {4378return ERR_CANT_CREATE;4379}43804381// Update the cursor.4382cursor_set_shape(DisplayServer::CURSOR_ARROW);43834384initialized = true;4385return OK;4386}43874388void WaylandThread::cursor_set_visible(bool p_visible) {4389cursor_visible = p_visible;43904391for (struct wl_seat *wl_seat : registry.wl_seats) {4392SeatState *ss = wl_seat_get_seat_state(wl_seat);4393ERR_FAIL_NULL(ss);43944395seat_state_update_cursor(ss);4396}4397}43984399void WaylandThread::cursor_set_shape(DisplayServer::CursorShape p_cursor_shape) {4400cursor_shape = p_cursor_shape;44014402for (struct wl_seat *wl_seat : registry.wl_seats) {4403SeatState *ss = wl_seat_get_seat_state(wl_seat);4404ERR_FAIL_NULL(ss);44054406seat_state_update_cursor(ss);4407}4408}44094410void WaylandThread::cursor_shape_set_custom_image(DisplayServer::CursorShape p_cursor_shape, Ref<Image> p_image, const Point2i &p_hotspot) {4411ERR_FAIL_COND(p_image.is_null());44124413Size2i image_size = p_image->get_size();44144415// NOTE: The stride is the width of the image in bytes.4416unsigned int image_stride = image_size.width * 4;4417unsigned int data_size = image_stride * image_size.height;44184419// We need a shared memory object file descriptor in order to create a4420// wl_buffer through wl_shm.4421int fd = WaylandThread::_allocate_shm_file(data_size);4422ERR_FAIL_COND(fd == -1);44234424CustomCursor &cursor = custom_cursors[p_cursor_shape];4425cursor.hotspot = p_hotspot;44264427if (cursor.wl_buffer) {4428// Clean up the old Wayland buffer.4429wl_buffer_destroy(cursor.wl_buffer);4430}44314432if (cursor.buffer_data) {4433// Clean up the old buffer data.4434munmap(cursor.buffer_data, cursor.buffer_data_size);4435}44364437cursor.buffer_data = (uint32_t *)mmap(nullptr, data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);4438cursor.buffer_data_size = data_size;44394440// Create the Wayland buffer.4441struct wl_shm_pool *wl_shm_pool = wl_shm_create_pool(registry.wl_shm, fd, data_size);4442// TODO: Make sure that WL_SHM_FORMAT_ARGB8888 format is supported. It4443// technically isn't garaunteed to be supported, but I think that'd be a4444// pretty unlikely thing to stumble upon.4445cursor.wl_buffer = wl_shm_pool_create_buffer(wl_shm_pool, 0, image_size.width, image_size.height, image_stride, WL_SHM_FORMAT_ARGB8888);4446wl_shm_pool_destroy(wl_shm_pool);44474448// Fill the cursor buffer with the image data.4449for (unsigned int index = 0; index < (unsigned int)(image_size.width * image_size.height); index++) {4450int row_index = std::floor(index / image_size.width);4451int column_index = (index % int(image_size.width));44524453cursor.buffer_data[index] = p_image->get_pixel(column_index, row_index).to_argb32();44544455// Wayland buffers, unless specified, require associated alpha, so we'll just4456// associate the alpha in-place.4457uint8_t *pixel_data = (uint8_t *)&cursor.buffer_data[index];4458pixel_data[0] = pixel_data[0] * pixel_data[3] / 255;4459pixel_data[1] = pixel_data[1] * pixel_data[3] / 255;4460pixel_data[2] = pixel_data[2] * pixel_data[3] / 255;4461}4462}44634464void WaylandThread::cursor_shape_clear_custom_image(DisplayServer::CursorShape p_cursor_shape) {4465if (custom_cursors.has(p_cursor_shape)) {4466CustomCursor cursor = custom_cursors[p_cursor_shape];4467custom_cursors.erase(p_cursor_shape);44684469if (cursor.wl_buffer) {4470wl_buffer_destroy(cursor.wl_buffer);4471}44724473if (cursor.buffer_data) {4474munmap(cursor.buffer_data, cursor.buffer_data_size);4475}4476}4477}44784479void WaylandThread::window_set_ime_active(const bool p_active, DisplayServer::WindowID p_window_id) {4480SeatState *ss = wl_seat_get_seat_state(wl_seat_current);44814482if (ss && ss->wp_text_input && ss->ime_enabled) {4483if (p_active) {4484ss->ime_active = true;4485zwp_text_input_v3_enable(ss->wp_text_input);4486zwp_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);4487} else {4488ss->ime_active = false;4489ss->ime_text = String();4490ss->ime_text_commit = String();4491ss->ime_cursor = Vector2i();4492zwp_text_input_v3_disable(ss->wp_text_input);4493}4494zwp_text_input_v3_commit(ss->wp_text_input);4495}4496}44974498void WaylandThread::window_set_ime_position(const Point2i &p_pos, DisplayServer::WindowID p_window_id) {4499SeatState *ss = wl_seat_get_seat_state(wl_seat_current);45004501if (ss && ss->wp_text_input && ss->ime_enabled) {4502ss->ime_rect = Rect2i(p_pos, Size2i(1, 10));4503zwp_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);4504zwp_text_input_v3_commit(ss->wp_text_input);4505}4506}45074508int WaylandThread::keyboard_get_layout_count() const {4509SeatState *ss = wl_seat_get_seat_state(wl_seat_current);45104511if (ss && ss->xkb_keymap) {4512return xkb_keymap_num_layouts(ss->xkb_keymap);4513}45144515return 0;4516}45174518int WaylandThread::keyboard_get_current_layout_index() const {4519SeatState *ss = wl_seat_get_seat_state(wl_seat_current);45204521if (ss) {4522return ss->current_layout_index;4523}45244525return 0;4526}45274528void WaylandThread::keyboard_set_current_layout_index(int p_index) {4529SeatState *ss = wl_seat_get_seat_state(wl_seat_current);45304531if (ss) {4532ss->current_layout_index = p_index;4533}4534}45354536String WaylandThread::keyboard_get_layout_name(int p_index) const {4537SeatState *ss = wl_seat_get_seat_state(wl_seat_current);45384539if (ss && ss->xkb_keymap) {4540return String::utf8(xkb_keymap_layout_get_name(ss->xkb_keymap, p_index));4541}45424543return "";4544}45454546Key WaylandThread::keyboard_get_key_from_physical(Key p_key) const {4547SeatState *ss = wl_seat_get_seat_state(wl_seat_current);45484549if (ss && ss->xkb_state) {4550xkb_keycode_t xkb_keycode = KeyMappingXKB::get_xkb_keycode(p_key);4551return KeyMappingXKB::get_keycode(xkb_state_key_get_one_sym(ss->xkb_state, xkb_keycode));4552}45534554return Key::NONE;4555}45564557void WaylandThread::keyboard_echo_keys() {4558SeatState *ss = wl_seat_get_seat_state(wl_seat_current);45594560if (ss) {4561seat_state_echo_keys(ss);4562}4563}45644565void WaylandThread::selection_set_text(const String &p_text) {4566SeatState *ss = wl_seat_get_seat_state(wl_seat_current);45674568if (registry.wl_data_device_manager == nullptr) {4569DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, wl_data_device_manager global not available.");4570return;4571}45724573if (ss == nullptr) {4574DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, current seat not set.");4575return;4576}45774578if (ss->wl_data_device == nullptr) {4579DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, seat doesn't have wl_data_device.");4580return;4581}45824583ss->selection_data = p_text.to_utf8_buffer();45844585if (ss->wl_data_source_selection == nullptr) {4586ss->wl_data_source_selection = wl_data_device_manager_create_data_source(registry.wl_data_device_manager);4587wl_data_source_add_listener(ss->wl_data_source_selection, &wl_data_source_listener, ss);4588wl_data_source_offer(ss->wl_data_source_selection, "text/plain;charset=utf-8");4589wl_data_source_offer(ss->wl_data_source_selection, "text/plain");45904591// TODO: Implement a good way of getting the latest serial from the user.4592wl_data_device_set_selection(ss->wl_data_device, ss->wl_data_source_selection, MAX(ss->pointer_data.button_serial, ss->last_key_pressed_serial));4593}45944595// Wait for the message to get to the server before continuing, otherwise the4596// clipboard update might come with a delay.4597wl_display_roundtrip(wl_display);4598}45994600bool WaylandThread::selection_has_mime(const String &p_mime) const {4601SeatState *ss = wl_seat_get_seat_state(wl_seat_current);46024603if (ss == nullptr) {4604DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");4605return false;4606}46074608OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_selection);4609if (!os) {4610return false;4611}46124613return os->mime_types.has(p_mime);4614}46154616Vector<uint8_t> WaylandThread::selection_get_mime(const String &p_mime) const {4617SeatState *ss = wl_seat_get_seat_state(wl_seat_current);4618if (ss == nullptr) {4619DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");4620return Vector<uint8_t>();4621}46224623if (ss->wl_data_source_selection) {4624// We have a source so the stuff we're pasting is ours. We'll have to pass the4625// data directly or we'd stall waiting for Godot (ourselves) to send us the4626// data :P46274628OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_selection);4629ERR_FAIL_NULL_V(os, Vector<uint8_t>());46304631if (os->mime_types.has(p_mime)) {4632// All righty, we're offering this type. Let's just return the data as is.4633return ss->selection_data;4634}46354636// ... we don't offer that type. Oh well.4637return Vector<uint8_t>();4638}46394640return _wl_data_offer_read(wl_display, p_mime.utf8().get_data(), ss->wl_data_offer_selection);4641}46424643bool WaylandThread::primary_has_mime(const String &p_mime) const {4644SeatState *ss = wl_seat_get_seat_state(wl_seat_current);46454646if (ss == nullptr) {4647DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");4648return false;4649}46504651OfferState *os = wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer);4652if (!os) {4653return false;4654}46554656return os->mime_types.has(p_mime);4657}46584659Vector<uint8_t> WaylandThread::primary_get_mime(const String &p_mime) const {4660SeatState *ss = wl_seat_get_seat_state(wl_seat_current);4661if (ss == nullptr) {4662DEBUG_LOG_WAYLAND_THREAD("Couldn't get primary, current seat not set.");4663return Vector<uint8_t>();4664}46654666if (ss->wp_primary_selection_source) {4667// We have a source so the stuff we're pasting is ours. We'll have to pass the4668// data directly or we'd stall waiting for Godot (ourselves) to send us the4669// data :P46704671OfferState *os = wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer);4672ERR_FAIL_NULL_V(os, Vector<uint8_t>());46734674if (os->mime_types.has(p_mime)) {4675// All righty, we're offering this type. Let's just return the data as is.4676return ss->selection_data;4677}46784679// ... we don't offer that type. Oh well.4680return Vector<uint8_t>();4681}46824683return _wp_primary_selection_offer_read(wl_display, p_mime.utf8().get_data(), ss->wp_primary_selection_offer);4684}46854686void WaylandThread::primary_set_text(const String &p_text) {4687SeatState *ss = wl_seat_get_seat_state(wl_seat_current);46884689if (registry.wp_primary_selection_device_manager == nullptr) {4690DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary, protocol not available.");4691return;4692}46934694if (ss == nullptr) {4695DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary, current seat not set.");4696return;4697}46984699if (ss->wp_primary_selection_device == nullptr) {4700DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary selection, seat doesn't have wp_primary_selection_device.");4701return;4702}47034704ss->primary_data = p_text.to_utf8_buffer();47054706if (ss->wp_primary_selection_source == nullptr) {4707ss->wp_primary_selection_source = zwp_primary_selection_device_manager_v1_create_source(registry.wp_primary_selection_device_manager);4708zwp_primary_selection_source_v1_add_listener(ss->wp_primary_selection_source, &wp_primary_selection_source_listener, ss);4709zwp_primary_selection_source_v1_offer(ss->wp_primary_selection_source, "text/plain;charset=utf-8");4710zwp_primary_selection_source_v1_offer(ss->wp_primary_selection_source, "text/plain");47114712// TODO: Implement a good way of getting the latest serial from the user.4713zwp_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));4714}47154716// Wait for the message to get to the server before continuing, otherwise the4717// clipboard update might come with a delay.4718wl_display_roundtrip(wl_display);4719}47204721void WaylandThread::commit_surfaces() {4722for (KeyValue<DisplayServer::WindowID, WindowState> &pair : windows) {4723wl_surface_commit(pair.value.wl_surface);4724}4725}47264727void WaylandThread::set_frame() {4728frame = true;4729}47304731bool WaylandThread::get_reset_frame() {4732bool old_frame = frame;4733frame = false;47344735return old_frame;4736}47374738// Dispatches events until a frame event is received, a window is reported as4739// suspended or the timeout expires.4740bool WaylandThread::wait_frame_suspend_ms(int p_timeout) {4741// This is a bit of a chicken and egg thing... Looks like the main event loop4742// has to call its rightfully forever-blocking poll right in between4743// `wl_display_prepare_read` and `wl_display_read`. This means, that it will4744// basically be guaranteed to stay stuck in a "prepare read" state, where it4745// will block any other attempt at reading the display fd, such as ours. The4746// solution? Let's make sure the mutex is locked (it should) and unblock the4747// main thread with a roundtrip!4748MutexLock mutex_lock(mutex);4749wl_display_roundtrip(wl_display);47504751if (is_suspended()) {4752// All windows are suspended! The compositor is telling us _explicitly_ that4753// we don't need to draw, without letting us guess through the frame event's4754// timing and stuff like that. Our job here is done.4755return false;4756}47574758if (frame) {4759// We already have a frame! Probably it got there while the caller locked :D4760frame = false;4761return true;4762}47634764struct pollfd poll_fd;4765poll_fd.fd = wl_display_get_fd(wl_display);4766poll_fd.events = POLLIN | POLLHUP;47674768int begin_ms = OS::get_singleton()->get_ticks_msec();4769int remaining_ms = p_timeout;47704771while (remaining_ms > 0) {4772// Empty the event queue while it's full.4773while (wl_display_prepare_read(wl_display) != 0) {4774if (wl_display_dispatch_pending(wl_display) == -1) {4775// Oh no. We'll check and handle any display error below.4776break;4777}47784779if (is_suspended()) {4780return false;4781}47824783if (frame) {4784// We had a frame event in the queue :D4785frame = false;4786return true;4787}4788}47894790int werror = wl_display_get_error(wl_display);47914792if (werror) {4793if (werror == EPROTO) {4794struct wl_interface *wl_interface = nullptr;4795uint32_t id = 0;47964797int error_code = wl_display_get_protocol_error(wl_display, (const struct wl_interface **)&wl_interface, &id);4798CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id));4799} else {4800CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror));4801}4802}48034804wl_display_flush(wl_display);48054806// Wait for the event file descriptor to have new data.4807poll(&poll_fd, 1, remaining_ms);48084809if (poll_fd.revents | POLLIN) {4810// Load the queues with fresh new data.4811wl_display_read_events(wl_display);4812} else {4813// Oh well... Stop signaling that we want to read.4814wl_display_cancel_read(wl_display);48154816// We've got no new events :(4817// We won't even bother with checking the frame flag.4818return false;4819}48204821// Let's try dispatching now...4822wl_display_dispatch_pending(wl_display);48234824if (is_suspended()) {4825return false;4826}48274828if (frame) {4829frame = false;4830return true;4831}48324833remaining_ms -= OS::get_singleton()->get_ticks_msec() - begin_ms;4834}48354836DEBUG_LOG_WAYLAND_THREAD("Frame timeout.");4837return false;4838}48394840uint64_t WaylandThread::window_get_last_frame_time(DisplayServer::WindowID p_window_id) const {4841ERR_FAIL_COND_V(!windows.has(p_window_id), false);4842return windows[p_window_id].last_frame_time;4843}48444845bool WaylandThread::window_is_suspended(DisplayServer::WindowID p_window_id) const {4846ERR_FAIL_COND_V(!windows.has(p_window_id), false);4847return windows[p_window_id].suspended;4848}48494850bool WaylandThread::is_fifo_available() const {4851return registry.wp_fifo_manager_name != 0;4852}48534854bool WaylandThread::is_suspended() const {4855for (const KeyValue<DisplayServer::WindowID, WindowState> &E : windows) {4856if (!E.value.suspended) {4857return false;4858}4859}48604861return true;4862}48634864void WaylandThread::destroy() {4865if (!initialized) {4866return;4867}48684869if (wl_display && events_thread.is_started()) {4870thread_data.thread_done.set();48714872// By sending a roundtrip message we're unblocking the polling thread so that4873// it can realize that it's done and also handle every event that's left.4874wl_display_roundtrip(wl_display);48754876events_thread.wait_to_finish();4877}48784879for (KeyValue<DisplayServer::WindowID, WindowState> &pair : windows) {4880WindowState &ws = pair.value;4881if (ws.wp_fractional_scale) {4882wp_fractional_scale_v1_destroy(ws.wp_fractional_scale);4883}48844885if (ws.wp_viewport) {4886wp_viewport_destroy(ws.wp_viewport);4887}48884889if (ws.frame_callback) {4890wl_callback_destroy(ws.frame_callback);4891}48924893#ifdef LIBDECOR_ENABLED4894if (ws.libdecor_frame) {4895libdecor_frame_close(ws.libdecor_frame);4896}4897#endif // LIBDECOR_ENABLED48984899if (ws.xdg_toplevel) {4900xdg_toplevel_destroy(ws.xdg_toplevel);4901}49024903if (ws.xdg_surface) {4904xdg_surface_destroy(ws.xdg_surface);4905}49064907if (ws.wl_surface) {4908wl_surface_destroy(ws.wl_surface);4909}4910}49114912for (struct wl_seat *wl_seat : registry.wl_seats) {4913SeatState *ss = wl_seat_get_seat_state(wl_seat);4914ERR_FAIL_NULL(ss);49154916wl_seat_destroy(wl_seat);49174918xkb_context_unref(ss->xkb_context);4919xkb_state_unref(ss->xkb_state);4920xkb_keymap_unref(ss->xkb_keymap);49214922if (ss->wl_keyboard) {4923wl_keyboard_destroy(ss->wl_keyboard);4924}49254926if (ss->keymap_buffer) {4927munmap((void *)ss->keymap_buffer, ss->keymap_buffer_size);4928}49294930if (ss->wl_pointer) {4931wl_pointer_destroy(ss->wl_pointer);4932}49334934if (ss->cursor_frame_callback) {4935// We don't need to set a null userdata for safety as the thread is done.4936wl_callback_destroy(ss->cursor_frame_callback);4937}49384939if (ss->cursor_surface) {4940wl_surface_destroy(ss->cursor_surface);4941}49424943if (ss->wl_data_device) {4944wl_data_device_destroy(ss->wl_data_device);4945}49464947if (ss->wp_cursor_shape_device) {4948wp_cursor_shape_device_v1_destroy(ss->wp_cursor_shape_device);4949}49504951if (ss->wp_relative_pointer) {4952zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);4953}49544955if (ss->wp_locked_pointer) {4956zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);4957}49584959if (ss->wp_confined_pointer) {4960zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);4961}49624963if (ss->wp_tablet_seat) {4964zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat);4965}49664967for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {4968TabletToolState *state = wp_tablet_tool_get_state(tool);4969if (state) {4970memdelete(state);4971}49724973zwp_tablet_tool_v2_destroy(tool);4974}49754976memdelete(ss);4977}49784979for (struct wl_output *wl_output : registry.wl_outputs) {4980ERR_FAIL_NULL(wl_output);49814982memdelete(wl_output_get_screen_state(wl_output));4983wl_output_destroy(wl_output);4984}49854986if (wl_cursor_theme) {4987wl_cursor_theme_destroy(wl_cursor_theme);4988}49894990if (registry.wp_idle_inhibit_manager) {4991zwp_idle_inhibit_manager_v1_destroy(registry.wp_idle_inhibit_manager);4992}49934994if (registry.wp_pointer_constraints) {4995zwp_pointer_constraints_v1_destroy(registry.wp_pointer_constraints);4996}49974998if (registry.wp_pointer_gestures) {4999zwp_pointer_gestures_v1_destroy(registry.wp_pointer_gestures);5000}50015002if (registry.wp_relative_pointer_manager) {5003zwp_relative_pointer_manager_v1_destroy(registry.wp_relative_pointer_manager);5004}50055006if (registry.xdg_activation) {5007xdg_activation_v1_destroy(registry.xdg_activation);5008}50095010if (registry.xdg_system_bell) {5011xdg_system_bell_v1_destroy(registry.xdg_system_bell);5012}50135014if (registry.xdg_decoration_manager) {5015zxdg_decoration_manager_v1_destroy(registry.xdg_decoration_manager);5016}50175018if (registry.wp_cursor_shape_manager) {5019wp_cursor_shape_manager_v1_destroy(registry.wp_cursor_shape_manager);5020}50215022if (registry.wp_fractional_scale_manager) {5023wp_fractional_scale_manager_v1_destroy(registry.wp_fractional_scale_manager);5024}50255026if (registry.wp_viewporter) {5027wp_viewporter_destroy(registry.wp_viewporter);5028}50295030if (registry.xdg_wm_base) {5031xdg_wm_base_destroy(registry.xdg_wm_base);5032}50335034// NOTE: Deprecated.5035if (registry.xdg_exporter_v1) {5036zxdg_exporter_v1_destroy(registry.xdg_exporter_v1);5037}50385039if (registry.xdg_exporter_v2) {5040zxdg_exporter_v2_destroy(registry.xdg_exporter_v2);5041}5042if (registry.wl_shm) {5043wl_shm_destroy(registry.wl_shm);5044}50455046if (registry.wl_compositor) {5047wl_compositor_destroy(registry.wl_compositor);5048}50495050if (wl_registry) {5051wl_registry_destroy(wl_registry);5052}50535054if (wl_display) {5055wl_display_disconnect(wl_display);5056}5057}50585059#endif // WAYLAND_ENABLED506050615062