From 3c7a25878248649656effc29b89aaed6dc957dc3 Mon Sep 17 00:00:00 2001 From: Riteo Date: Wed, 11 Sep 2024 19:26:22 +0200 Subject: [PATCH] Wayland: Split WaylandThread implementation into multiple files Wayland is an asynchronous object-oriented API. This means that most code in WaylandThread is made of event handlers for each kind of object. Over time it all became quite unwieldy. This patch moves the implementation for all the handlers into separate files, one for each protocol. The core protocol got further split into files as it's quite chunky. Note that this relates only to the _implementation_ of the `WaylandThread` class (into which the handlers are currently located) and does no further attempt at splitting everything up (e.g. our custom object state structs), since that would require a lot more work. Personally I think that this is definitely clearer, given how simple this change is, and that potentially we might go even further over time. Perhaps we could use a "wayland" namespace for all the state objects and get rid of them from the `WaylandThread` class? Just thinking out loud :P --- platform/linuxbsd/wayland/SCsub | 19 + .../linuxbsd/wayland/thread/core/data.cpp | 188 ++ .../linuxbsd/wayland/thread/core/keyboard.cpp | 145 + .../linuxbsd/wayland/thread/core/output.cpp | 98 + .../linuxbsd/wayland/thread/core/pointer.cpp | 452 +++ .../linuxbsd/wayland/thread/core/registry.cpp | 604 ++++ .../linuxbsd/wayland/thread/core/seat.cpp | 119 + .../linuxbsd/wayland/thread/core/surface.cpp | 77 + platform/linuxbsd/wayland/thread/libdecor.cpp | 106 + .../wayland/thread/primary-selection.cpp | 105 + platform/linuxbsd/wayland/thread/tablet.cpp | 398 +++ .../linuxbsd/wayland/thread/text-input.cpp | 250 ++ platform/linuxbsd/wayland/thread/util.cpp | 166 ++ .../wayland/thread/xdg-activation.cpp | 43 + .../linuxbsd/wayland/thread/xdg-shell.cpp | 139 + platform/linuxbsd/wayland/wayland_thread.cpp | 2509 +---------------- platform/linuxbsd/wayland/wayland_thread.h | 13 + 17 files changed, 2947 insertions(+), 2484 deletions(-) create mode 100644 platform/linuxbsd/wayland/thread/core/data.cpp create mode 100644 platform/linuxbsd/wayland/thread/core/keyboard.cpp create mode 100644 platform/linuxbsd/wayland/thread/core/output.cpp create mode 100644 platform/linuxbsd/wayland/thread/core/pointer.cpp create mode 100644 platform/linuxbsd/wayland/thread/core/registry.cpp create mode 100644 platform/linuxbsd/wayland/thread/core/seat.cpp create mode 100644 platform/linuxbsd/wayland/thread/core/surface.cpp create mode 100644 platform/linuxbsd/wayland/thread/libdecor.cpp create mode 100644 platform/linuxbsd/wayland/thread/primary-selection.cpp create mode 100644 platform/linuxbsd/wayland/thread/tablet.cpp create mode 100644 platform/linuxbsd/wayland/thread/text-input.cpp create mode 100644 platform/linuxbsd/wayland/thread/util.cpp create mode 100644 platform/linuxbsd/wayland/thread/xdg-activation.cpp create mode 100644 platform/linuxbsd/wayland/thread/xdg-shell.cpp diff --git a/platform/linuxbsd/wayland/SCsub b/platform/linuxbsd/wayland/SCsub index 89b586845cea..39c81ed34c24 100644 --- a/platform/linuxbsd/wayland/SCsub +++ b/platform/linuxbsd/wayland/SCsub @@ -186,8 +186,27 @@ source_files = [ "protocol/idle_inhibit.gen.c", "protocol/tablet.gen.c", "protocol/text_input.gen.c", + "display_server_wayland.cpp", + "wayland_thread.cpp", + + "thread/core/data.cpp", + "thread/core/keyboard.cpp", + "thread/core/pointer.cpp", + "thread/core/registry.cpp", + "thread/core/output.cpp", + "thread/core/seat.cpp", + "thread/core/surface.cpp", + + "thread/libdecor.cpp", + "thread/primary-selection.cpp", + "thread/tablet.cpp", + "thread/text-input.cpp", + "thread/util.cpp", + "thread/xdg-activation.cpp", + "thread/xdg-shell.cpp", + "key_mapping_xkb.cpp", "detect_prime_egl.cpp", ] diff --git a/platform/linuxbsd/wayland/thread/core/data.cpp b/platform/linuxbsd/wayland/thread/core/data.cpp new file mode 100644 index 000000000000..0151c8622273 --- /dev/null +++ b/platform/linuxbsd/wayland/thread/core/data.cpp @@ -0,0 +1,188 @@ +/**************************************************************************/ +/* data.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +#include + +// NOTE: Don't forget to `memfree` the offer's state. +void WaylandThread::_wl_data_device_on_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) { + wl_proxy_tag_godot((struct wl_proxy *)id); + wl_data_offer_add_listener(id, &wl_data_offer_listener, memnew(OfferState)); +} + +void 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) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + ss->dnd_enter_serial = serial; + ss->wl_data_offer_dnd = id; + + // Godot only supports DnD file copying for now. + wl_data_offer_accept(id, serial, "text/uri-list"); + wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); +} + +void WaylandThread::_wl_data_device_on_leave(void *data, struct wl_data_device *wl_data_device) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (ss->wl_data_offer_dnd) { + memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd)); + wl_data_offer_destroy(ss->wl_data_offer_dnd); + ss->wl_data_offer_dnd = nullptr; + } +} + +void 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) { +} + +void WaylandThread::_wl_data_device_on_drop(void *data, struct wl_data_device *wl_data_device) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + WaylandThread *wayland_thread = ss->wayland_thread; + ERR_FAIL_NULL(wayland_thread); + + OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_dnd); + ERR_FAIL_NULL(os); + + if (os) { + Ref msg; + msg.instantiate(); + + Vector list_data = _wl_data_offer_read(wayland_thread->wl_display, "text/uri-list", ss->wl_data_offer_dnd); + + msg->files = String::utf8((const char *)list_data.ptr(), list_data.size()).split("\r\n", false); + for (int i = 0; i < msg->files.size(); i++) { + msg->files.write[i] = msg->files[i].replace("file://", "").uri_decode(); + } + + wayland_thread->push_message(msg); + + wl_data_offer_finish(ss->wl_data_offer_dnd); + } + + memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd)); + wl_data_offer_destroy(ss->wl_data_offer_dnd); + ss->wl_data_offer_dnd = nullptr; +} + +void WaylandThread::_wl_data_device_on_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (ss->wl_data_offer_selection) { + memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_selection)); + wl_data_offer_destroy(ss->wl_data_offer_selection); + } + + ss->wl_data_offer_selection = id; +} + +void WaylandThread::_wl_data_offer_on_offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type) { + OfferState *os = (OfferState *)data; + ERR_FAIL_NULL(os); + + if (os) { + os->mime_types.insert(String::utf8(mime_type)); + } +} + +void WaylandThread::_wl_data_offer_on_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions) { +} + +void WaylandThread::_wl_data_offer_on_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) { +} + +void WaylandThread::_wl_data_source_on_target(void *data, struct wl_data_source *wl_data_source, const char *mime_type) { +} + +void WaylandThread::_wl_data_source_on_send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + Vector *data_to_send = nullptr; + + if (wl_data_source == ss->wl_data_source_selection) { + data_to_send = &ss->selection_data; + DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested selection."); + } + + if (data_to_send) { + ssize_t written_bytes = 0; + + bool valid_mime = false; + + if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) { + valid_mime = true; + } else if (strcmp(mime_type, "text/plain") == 0) { + valid_mime = true; + } + + if (valid_mime) { + written_bytes = write(fd, data_to_send->ptr(), data_to_send->size()); + } + + if (written_bytes > 0) { + DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes)); + } else if (written_bytes == 0) { + DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent."); + } else { + ERR_PRINT(vformat("Clipboard: write error %d.", errno)); + } + } + + close(fd); +} + +void WaylandThread::_wl_data_source_on_cancelled(void *data, struct wl_data_source *wl_data_source) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + wl_data_source_destroy(wl_data_source); + + if (wl_data_source == ss->wl_data_source_selection) { + ss->wl_data_source_selection = nullptr; + ss->selection_data.clear(); + + DEBUG_LOG_WAYLAND_THREAD("Clipboard: selection set by another program."); + return; + } +} + +void WaylandThread::_wl_data_source_on_dnd_drop_performed(void *data, struct wl_data_source *wl_data_source) { +} + +void WaylandThread::_wl_data_source_on_dnd_finished(void *data, struct wl_data_source *wl_data_source) { +} + +void WaylandThread::_wl_data_source_on_action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action) { +} diff --git a/platform/linuxbsd/wayland/thread/core/keyboard.cpp b/platform/linuxbsd/wayland/thread/core/keyboard.cpp new file mode 100644 index 000000000000..a7e2bd8cef62 --- /dev/null +++ b/platform/linuxbsd/wayland/thread/core/keyboard.cpp @@ -0,0 +1,145 @@ +/**************************************************************************/ +/* keyboard.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +#include + +void WaylandThread::_wl_keyboard_on_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { + ERR_FAIL_COND_MSG(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, "Unsupported keymap format announced from the Wayland compositor."); + + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (ss->keymap_buffer) { + // We have already a mapped buffer, so we unmap it. There's no need to reset + // its pointer or size, as we're gonna set them below. + munmap((void *)ss->keymap_buffer, ss->keymap_buffer_size); + ss->keymap_buffer = nullptr; + } + + ss->keymap_buffer = (const char *)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); + ss->keymap_buffer_size = size; + + xkb_keymap_unref(ss->xkb_keymap); + ss->xkb_keymap = xkb_keymap_new_from_string(ss->xkb_context, ss->keymap_buffer, + XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + + xkb_state_unref(ss->xkb_state); + ss->xkb_state = xkb_state_new(ss->xkb_keymap); +} + +void WaylandThread::_wl_keyboard_on_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + WaylandThread *wayland_thread = ss->wayland_thread; + ERR_FAIL_NULL(wayland_thread); + + wayland_thread->_set_current_seat(ss->wl_seat); + + Ref msg; + msg.instantiate(); + msg->event = DisplayServer::WINDOW_EVENT_FOCUS_IN; + wayland_thread->push_message(msg); +} + +void WaylandThread::_wl_keyboard_on_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + WaylandThread *wayland_thread = ss->wayland_thread; + ERR_FAIL_NULL(wayland_thread); + + ss->repeating_keycode = XKB_KEYCODE_INVALID; + + Ref msg; + msg.instantiate(); + msg->event = DisplayServer::WINDOW_EVENT_FOCUS_OUT; + wayland_thread->push_message(msg); +} + +void WaylandThread::_wl_keyboard_on_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + WaylandThread *wayland_thread = ss->wayland_thread; + ERR_FAIL_NULL(wayland_thread); + + // We have to add 8 to the scancode to get an XKB-compatible keycode. + xkb_keycode_t xkb_keycode = key + 8; + + bool pressed = state & WL_KEYBOARD_KEY_STATE_PRESSED; + + if (pressed) { + if (xkb_keymap_key_repeats(ss->xkb_keymap, xkb_keycode)) { + ss->last_repeat_start_msec = OS::get_singleton()->get_ticks_msec(); + ss->repeating_keycode = xkb_keycode; + } + + ss->last_key_pressed_serial = serial; + } else if (ss->repeating_keycode == xkb_keycode) { + ss->repeating_keycode = XKB_KEYCODE_INVALID; + } + + Ref k; + k.instantiate(); + + if (!_seat_state_configure_key_event(*ss, k, xkb_keycode, pressed)) { + return; + } + + Ref msg; + msg.instantiate(); + msg->event = k; + wayland_thread->push_message(msg); +} + +void 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) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + xkb_state_update_mask(ss->xkb_state, mods_depressed, mods_latched, mods_locked, ss->current_layout_index, ss->current_layout_index, group); + + ss->shift_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED); + ss->ctrl_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED); + ss->alt_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_DEPRESSED); + ss->meta_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_DEPRESSED); + + ss->current_layout_index = group; +} + +void WaylandThread::_wl_keyboard_on_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + ss->repeat_key_delay_msec = 1000 / rate; + ss->repeat_start_delay_msec = delay; +} diff --git a/platform/linuxbsd/wayland/thread/core/output.cpp b/platform/linuxbsd/wayland/thread/core/output.cpp new file mode 100644 index 000000000000..b15a56ea0ee6 --- /dev/null +++ b/platform/linuxbsd/wayland/thread/core/output.cpp @@ -0,0 +1,98 @@ +/**************************************************************************/ +/* output.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +void 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) { + ScreenState *ss = (ScreenState *)data; + ERR_FAIL_NULL(ss); + + ss->pending_data.position.x = x; + + ss->pending_data.position.x = x; + ss->pending_data.position.y = y; + + ss->pending_data.physical_size.width = physical_width; + ss->pending_data.physical_size.height = physical_height; + + ss->pending_data.make.parse_utf8(make); + ss->pending_data.model.parse_utf8(model); + + // `wl_output::done` is a version 2 addition. We'll directly update the data + // for compatibility. + if (wl_output_get_version(wl_output) == 1) { + ss->data = ss->pending_data; + } +} + +void WaylandThread::_wl_output_on_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { + ScreenState *ss = (ScreenState *)data; + ERR_FAIL_NULL(ss); + + ss->pending_data.size.width = width; + ss->pending_data.size.height = height; + + ss->pending_data.refresh_rate = refresh ? refresh / 1000.0f : -1; + + // `wl_output::done` is a version 2 addition. We'll directly update the data + // for compatibility. + if (wl_output_get_version(wl_output) == 1) { + ss->data = ss->pending_data; + } +} + +// NOTE: The following `wl_output` events are only for version 2 onwards, so we +// can assume that they're "atomic" (i.e. rely on the `wl_output::done` event). + +void WaylandThread::_wl_output_on_done(void *data, struct wl_output *wl_output) { + ScreenState *ss = (ScreenState *)data; + ERR_FAIL_NULL(ss); + + ss->data = ss->pending_data; + + ss->wayland_thread->_update_scale(ss->data.scale); + + DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x done.", (size_t)wl_output)); +} + +void WaylandThread::_wl_output_on_scale(void *data, struct wl_output *wl_output, int32_t factor) { + ScreenState *ss = (ScreenState *)data; + ERR_FAIL_NULL(ss); + + ss->pending_data.scale = factor; + + DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x scale %d", (size_t)wl_output, factor)); +} + +void WaylandThread::_wl_output_on_name(void *data, struct wl_output *wl_output, const char *name) { +} + +void WaylandThread::_wl_output_on_description(void *data, struct wl_output *wl_output, const char *description) { +} diff --git a/platform/linuxbsd/wayland/thread/core/pointer.cpp b/platform/linuxbsd/wayland/thread/core/pointer.cpp new file mode 100644 index 000000000000..22113e82a23d --- /dev/null +++ b/platform/linuxbsd/wayland/thread/core/pointer.cpp @@ -0,0 +1,452 @@ +/**************************************************************************/ +/* pointer.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +// FIXME: Does this cause issues with *BSDs? +#include + +void 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) { + if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) { + return; + } + + DEBUG_LOG_WAYLAND_THREAD("Pointing window."); + + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + ERR_FAIL_NULL(ss->cursor_surface); + ss->pointer_enter_serial = serial; + ss->pointed_surface = surface; + ss->last_pointed_surface = surface; + + seat_state_update_cursor(ss); + + Ref msg; + msg.instantiate(); + msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER; + + ss->wayland_thread->push_message(msg); +} + +void WaylandThread::_wl_pointer_on_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) { + if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) { + return; + } + + DEBUG_LOG_WAYLAND_THREAD("Left window."); + + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + WaylandThread *wayland_thread = ss->wayland_thread; + ERR_FAIL_NULL(wayland_thread); + + ss->pointed_surface = nullptr; + + ss->pointer_data_buffer.pressed_button_mask.clear(); + + Ref msg; + msg.instantiate(); + msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT; + + wayland_thread->push_message(msg); +} + +void 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) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (!ss->pointed_surface) { + // We're probably on a decoration or some other third-party thing. + return; + } + + WindowState *ws = wl_surface_get_window_state(ss->pointed_surface); + ERR_FAIL_NULL(ws); + + PointerData &pd = ss->pointer_data_buffer; + + // TODO: Scale only when sending the Wayland message. + pd.position.x = wl_fixed_to_double(surface_x); + pd.position.y = wl_fixed_to_double(surface_y); + + pd.position *= window_state_get_scale_factor(ws); + + pd.motion_time = time; +} + +void WaylandThread::_wl_pointer_on_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (!ss->pointed_surface) { + // We're probably on a decoration or some other third-party thing. + return; + } + + PointerData &pd = ss->pointer_data_buffer; + + MouseButton button_pressed = MouseButton::NONE; + + switch (button) { + case BTN_LEFT: + button_pressed = MouseButton::LEFT; + break; + + case BTN_MIDDLE: + button_pressed = MouseButton::MIDDLE; + break; + + case BTN_RIGHT: + button_pressed = MouseButton::RIGHT; + break; + + case BTN_EXTRA: + button_pressed = MouseButton::MB_XBUTTON1; + break; + + case BTN_SIDE: + button_pressed = MouseButton::MB_XBUTTON2; + break; + + default: { + } + } + + MouseButtonMask mask = mouse_button_to_mask(button_pressed); + + if (state & WL_POINTER_BUTTON_STATE_PRESSED) { + pd.pressed_button_mask.set_flag(mask); + pd.last_button_pressed = button_pressed; + pd.double_click_begun = true; + } else { + pd.pressed_button_mask.clear_flag(mask); + } + + pd.button_time = time; + pd.button_serial = serial; +} + +void WaylandThread::_wl_pointer_on_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (!ss->pointed_surface) { + // We're probably on a decoration or some other third-party thing. + return; + } + + PointerData &pd = ss->pointer_data_buffer; + + switch (axis) { + case WL_POINTER_AXIS_VERTICAL_SCROLL: { + pd.scroll_vector.y = wl_fixed_to_double(value); + } break; + + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: { + pd.scroll_vector.x = wl_fixed_to_double(value); + } break; + } + + pd.button_time = time; +} + +void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_pointer) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (!ss->pointed_surface) { + // We're probably on a decoration or some other third-party thing. + return; + } + + WaylandThread *wayland_thread = ss->wayland_thread; + ERR_FAIL_NULL(wayland_thread); + + wayland_thread->_set_current_seat(ss->wl_seat); + + PointerData &old_pd = ss->pointer_data; + PointerData &pd = ss->pointer_data_buffer; + + if (old_pd.motion_time != pd.motion_time || old_pd.relative_motion_time != pd.relative_motion_time) { + Ref mm; + mm.instantiate(); + + // Set all pressed modifiers. + mm->set_shift_pressed(ss->shift_pressed); + mm->set_ctrl_pressed(ss->ctrl_pressed); + mm->set_alt_pressed(ss->alt_pressed); + mm->set_meta_pressed(ss->meta_pressed); + + mm->set_window_id(DisplayServer::MAIN_WINDOW_ID); + mm->set_button_mask(pd.pressed_button_mask); + mm->set_position(pd.position); + mm->set_global_position(pd.position); + + Vector2 pos_delta = pd.position - old_pd.position; + + if (old_pd.relative_motion_time != pd.relative_motion_time) { + uint32_t time_delta = pd.relative_motion_time - old_pd.relative_motion_time; + + mm->set_relative(pd.relative_motion); + mm->set_velocity((Vector2)pos_delta / time_delta); + } else { + // The spec includes the possibility of having motion events without an + // associated relative motion event. If that's the case, fallback to a + // simple delta of the position. The captured mouse won't report the + // relative speed anymore though. + uint32_t time_delta = pd.motion_time - old_pd.motion_time; + + mm->set_relative(pd.position - old_pd.position); + mm->set_velocity((Vector2)pos_delta / time_delta); + } + mm->set_relative_screen_position(mm->get_relative()); + mm->set_screen_velocity(mm->get_velocity()); + + Ref msg; + msg.instantiate(); + + msg->event = mm; + + wayland_thread->push_message(msg); + } + + if (pd.discrete_scroll_vector_120 - old_pd.discrete_scroll_vector_120 != Vector2i()) { + // This is a discrete scroll (eg. from a scroll wheel), so we'll just emit + // scroll wheel buttons. + if (pd.scroll_vector.y != 0) { + MouseButton button = pd.scroll_vector.y > 0 ? MouseButton::WHEEL_DOWN : MouseButton::WHEEL_UP; + pd.pressed_button_mask.set_flag(mouse_button_to_mask(button)); + } + + if (pd.scroll_vector.x != 0) { + MouseButton button = pd.scroll_vector.x > 0 ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT; + pd.pressed_button_mask.set_flag(mouse_button_to_mask(button)); + } + } else { + if (pd.scroll_vector - old_pd.scroll_vector != Vector2()) { + // This is a continuous scroll, so we'll emit a pan gesture. + Ref pg; + pg.instantiate(); + + // Set all pressed modifiers. + pg->set_shift_pressed(ss->shift_pressed); + pg->set_ctrl_pressed(ss->ctrl_pressed); + pg->set_alt_pressed(ss->alt_pressed); + pg->set_meta_pressed(ss->meta_pressed); + + pg->set_position(pd.position); + + pg->set_window_id(DisplayServer::MAIN_WINDOW_ID); + + pg->set_delta(pd.scroll_vector); + + Ref msg; + msg.instantiate(); + + msg->event = pg; + + wayland_thread->push_message(msg); + } + } + + if (old_pd.pressed_button_mask != pd.pressed_button_mask) { + BitField pressed_mask_delta = old_pd.pressed_button_mask ^ pd.pressed_button_mask; + + const MouseButton buttons_to_test[] = { + MouseButton::LEFT, + MouseButton::MIDDLE, + MouseButton::RIGHT, + MouseButton::WHEEL_UP, + MouseButton::WHEEL_DOWN, + MouseButton::WHEEL_LEFT, + MouseButton::WHEEL_RIGHT, + MouseButton::MB_XBUTTON1, + MouseButton::MB_XBUTTON2, + }; + + for (MouseButton test_button : buttons_to_test) { + MouseButtonMask test_button_mask = mouse_button_to_mask(test_button); + if (pressed_mask_delta.has_flag(test_button_mask)) { + Ref mb; + mb.instantiate(); + + // Set all pressed modifiers. + mb->set_shift_pressed(ss->shift_pressed); + mb->set_ctrl_pressed(ss->ctrl_pressed); + mb->set_alt_pressed(ss->alt_pressed); + mb->set_meta_pressed(ss->meta_pressed); + + mb->set_window_id(DisplayServer::MAIN_WINDOW_ID); + mb->set_position(pd.position); + mb->set_global_position(pd.position); + + if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN) { + // If this is a discrete scroll, specify how many "clicks" it did for this + // pointer frame. + mb->set_factor(Math::abs(pd.discrete_scroll_vector_120.y / (float)120)); + } + + if (test_button == MouseButton::WHEEL_RIGHT || test_button == MouseButton::WHEEL_LEFT) { + // If this is a discrete scroll, specify how many "clicks" it did for this + // pointer frame. + mb->set_factor(fabs(pd.discrete_scroll_vector_120.x / (float)120)); + } + + mb->set_button_mask(pd.pressed_button_mask); + + mb->set_button_index(test_button); + mb->set_pressed(pd.pressed_button_mask.has_flag(test_button_mask)); + + // We have to set the last position pressed here as we can't take for + // granted what the individual events might have seen due to them not having + // a guaranteed order. + if (mb->is_pressed()) { + pd.last_pressed_position = pd.position; + } + + if (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).distance_to(Vector2(pd.last_pressed_position)) < 5) { + pd.double_click_begun = false; + mb->set_double_click(true); + } + + Ref msg; + msg.instantiate(); + + msg->event = mb; + + wayland_thread->push_message(msg); + + // Send an event resetting immediately the wheel key. + // Wayland specification defines axis_stop events as optional and says to + // treat all axis events as unterminated. As such, we have to manually do + // it ourselves. + if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN || test_button == MouseButton::WHEEL_LEFT || test_button == MouseButton::WHEEL_RIGHT) { + // FIXME: This is ugly, I can't find a clean way to clone an InputEvent. + // This works for now, despite being horrible. + Ref wh_up; + wh_up.instantiate(); + + wh_up->set_window_id(DisplayServer::MAIN_WINDOW_ID); + wh_up->set_position(pd.position); + wh_up->set_global_position(pd.position); + + // We have to unset the button to avoid it getting stuck. + pd.pressed_button_mask.clear_flag(test_button_mask); + wh_up->set_button_mask(pd.pressed_button_mask); + + wh_up->set_button_index(test_button); + wh_up->set_pressed(false); + + Ref msg_up; + msg_up.instantiate(); + msg_up->event = wh_up; + wayland_thread->push_message(msg_up); + } + } + } + } + + // Reset the scroll vectors as we already handled them. + pd.scroll_vector = Vector2(); + pd.discrete_scroll_vector_120 = Vector2i(); + + // Update the data all getters read. Wayland's specification requires us to do + // this, since all pointer actions are sent in individual events. + old_pd = pd; +} + +void WaylandThread::_wl_pointer_on_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (!ss->pointed_surface) { + // We're probably on a decoration or some other third-party thing. + return; + } + + ss->pointer_data_buffer.scroll_type = axis_source; +} + +void WaylandThread::_wl_pointer_on_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) { +} + +// NOTE: This event is deprecated since version 8 and superseded by +// `wl_pointer::axis_value120`. This thus converts the data to its +// fraction-of-120 format. +void WaylandThread::_wl_pointer_on_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (!ss->pointed_surface) { + // We're probably on a decoration or some other third-party thing. + return; + } + + PointerData &pd = ss->pointer_data_buffer; + + // NOTE: We can allow ourselves to not accumulate this data (and thus just + // assign it) as the spec guarantees only one event per axis type. + + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + pd.discrete_scroll_vector_120.y = discrete * 120; + } + + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + pd.discrete_scroll_vector_120.x = discrete * 120; + } +} + +// Supersedes `wl_pointer::axis_discrete` Since version 8. +void WaylandThread::_wl_pointer_on_axis_value120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (!ss->pointed_surface) { + // We're probably on a decoration or some other third-party thing. + return; + } + + PointerData &pd = ss->pointer_data_buffer; + + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + pd.discrete_scroll_vector_120.y += value120; + } + + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + pd.discrete_scroll_vector_120.x += value120; + } +} + +// TODO: Add support to this event. +void WaylandThread::_wl_pointer_on_axis_relative_direction(void *data, struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction) { +} diff --git a/platform/linuxbsd/wayland/thread/core/registry.cpp b/platform/linuxbsd/wayland/thread/core/registry.cpp new file mode 100644 index 000000000000..19efacdb5e8f --- /dev/null +++ b/platform/linuxbsd/wayland/thread/core/registry.cpp @@ -0,0 +1,604 @@ +/**************************************************************************/ +/* registry.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) { + RegistryState *registry = (RegistryState *)data; + ERR_FAIL_NULL(registry); + + if (strcmp(interface, wl_shm_interface.name) == 0) { + registry->wl_shm = (struct wl_shm *)wl_registry_bind(wl_registry, name, &wl_shm_interface, 1); + registry->wl_shm_name = name; + return; + } + + if (strcmp(interface, zxdg_exporter_v1_interface.name) == 0) { + registry->xdg_exporter = (struct zxdg_exporter_v1 *)wl_registry_bind(wl_registry, name, &zxdg_exporter_v1_interface, 1); + registry->xdg_exporter_name = name; + return; + } + + if (strcmp(interface, wl_compositor_interface.name) == 0) { + registry->wl_compositor = (struct wl_compositor *)wl_registry_bind(wl_registry, name, &wl_compositor_interface, CLAMP((int)version, 1, 6)); + registry->wl_compositor_name = name; + return; + } + + if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { + registry->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)); + registry->wl_data_device_manager_name = name; + + // This global creates some seat data. Let's do that for the ones already available. + for (struct wl_seat *wl_seat : registry->wl_seats) { + SeatState *ss = wl_seat_get_seat_state(wl_seat); + ERR_FAIL_NULL(ss); + + if (ss->wl_data_device == nullptr) { + ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat); + wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss); + } + } + return; + } + + if (strcmp(interface, wl_output_interface.name) == 0) { + struct wl_output *wl_output = (struct wl_output *)wl_registry_bind(wl_registry, name, &wl_output_interface, CLAMP((int)version, 1, 4)); + wl_proxy_tag_godot((struct wl_proxy *)wl_output); + + registry->wl_outputs.push_back(wl_output); + + ScreenState *ss = memnew(ScreenState); + ss->wl_output_name = name; + ss->wayland_thread = registry->wayland_thread; + + wl_proxy_tag_godot((struct wl_proxy *)wl_output); + wl_output_add_listener(wl_output, &wl_output_listener, ss); + return; + } + + if (strcmp(interface, wl_seat_interface.name) == 0) { + struct wl_seat *wl_seat = (struct wl_seat *)wl_registry_bind(wl_registry, name, &wl_seat_interface, CLAMP((int)version, 1, 9)); + wl_proxy_tag_godot((struct wl_proxy *)wl_seat); + + SeatState *ss = memnew(SeatState); + ss->wl_seat = wl_seat; + ss->wl_seat_name = name; + + ss->registry = registry; + ss->wayland_thread = registry->wayland_thread; + + // Some extra stuff depends on other globals. We'll initialize them if the + // globals are already there, otherwise we'll have to do that once and if they + // get announced. + // + // NOTE: Don't forget to also bind/destroy with the respective global. + if (!ss->wl_data_device && registry->wl_data_device_manager) { + // Clipboard & DnD. + ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat); + wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss); + } + + if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) { + // Primary selection. + ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat); + zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss); + } + + if (!ss->wp_tablet_seat && registry->wp_tablet_manager) { + // Tablet. + ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat); + zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss); + } + + if (!ss->wp_text_input && registry->wp_text_input_manager) { + // IME. + ss->wp_text_input = zwp_text_input_manager_v3_get_text_input(registry->wp_text_input_manager, wl_seat); + zwp_text_input_v3_add_listener(ss->wp_text_input, &wp_text_input_listener, ss); + } + + registry->wl_seats.push_back(wl_seat); + + wl_seat_add_listener(wl_seat, &wl_seat_listener, ss); + + if (registry->wayland_thread->wl_seat_current == nullptr) { + registry->wayland_thread->_set_current_seat(wl_seat); + } + + return; + } + + if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, CLAMP((int)version, 1, 6)); + registry->xdg_wm_base_name = name; + + xdg_wm_base_add_listener(registry->xdg_wm_base, &xdg_wm_base_listener, nullptr); + return; + } + + if (strcmp(interface, wp_viewporter_interface.name) == 0) { + registry->wp_viewporter = (struct wp_viewporter *)wl_registry_bind(wl_registry, name, &wp_viewporter_interface, 1); + registry->wp_viewporter_name = name; + } + + if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { + registry->wp_fractional_scale_manager = (struct wp_fractional_scale_manager_v1 *)wl_registry_bind(wl_registry, name, &wp_fractional_scale_manager_v1_interface, 1); + registry->wp_fractional_scale_manager_name = name; + + // NOTE: We're not mapping the fractional scale object here because this is + // supposed to be a "startup global". If for some reason this isn't true (who + // knows), add a conditional branch for creating the add-on object. + } + + if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { + registry->xdg_decoration_manager = (struct zxdg_decoration_manager_v1 *)wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1); + registry->xdg_decoration_manager_name = name; + return; + } + + if (strcmp(interface, xdg_activation_v1_interface.name) == 0) { + registry->xdg_activation = (struct xdg_activation_v1 *)wl_registry_bind(wl_registry, name, &xdg_activation_v1_interface, 1); + registry->xdg_activation_name = name; + return; + } + + if (strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0) { + registry->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); + + // This global creates some seat data. Let's do that for the ones already available. + for (struct wl_seat *wl_seat : registry->wl_seats) { + SeatState *ss = wl_seat_get_seat_state(wl_seat); + ERR_FAIL_NULL(ss); + + if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) { + ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat); + zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss); + } + } + } + + if (strcmp(interface, zwp_relative_pointer_manager_v1_interface.name) == 0) { + registry->wp_relative_pointer_manager = (struct zwp_relative_pointer_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_relative_pointer_manager_v1_interface, 1); + registry->wp_relative_pointer_manager_name = name; + return; + } + + if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) { + registry->wp_pointer_constraints = (struct zwp_pointer_constraints_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1); + registry->wp_pointer_constraints_name = name; + return; + } + + if (strcmp(interface, zwp_pointer_gestures_v1_interface.name) == 0) { + registry->wp_pointer_gestures = (struct zwp_pointer_gestures_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_gestures_v1_interface, 1); + registry->wp_pointer_gestures_name = name; + return; + } + + if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { + registry->wp_idle_inhibit_manager = (struct zwp_idle_inhibit_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_idle_inhibit_manager_v1_interface, 1); + registry->wp_idle_inhibit_manager_name = name; + return; + } + + if (strcmp(interface, zwp_tablet_manager_v2_interface.name) == 0) { + registry->wp_tablet_manager = (struct zwp_tablet_manager_v2 *)wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1); + registry->wp_tablet_manager_name = name; + + // This global creates some seat data. Let's do that for the ones already available. + for (struct wl_seat *wl_seat : registry->wl_seats) { + SeatState *ss = wl_seat_get_seat_state(wl_seat); + ERR_FAIL_NULL(ss); + + ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat); + zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss); + } + + return; + } + + if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) { + registry->wp_text_input_manager = (struct zwp_text_input_manager_v3 *)wl_registry_bind(wl_registry, name, &zwp_text_input_manager_v3_interface, 1); + registry->wp_text_input_manager_name = name; + + // This global creates some seat data. Let's do that for the ones already available. + for (struct wl_seat *wl_seat : registry->wl_seats) { + SeatState *ss = wl_seat_get_seat_state(wl_seat); + ERR_FAIL_NULL(ss); + + ss->wp_text_input = zwp_text_input_manager_v3_get_text_input(registry->wp_text_input_manager, wl_seat); + zwp_text_input_v3_add_listener(ss->wp_text_input, &wp_text_input_listener, ss); + } + + return; + } +} + +void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) { + RegistryState *registry = (RegistryState *)data; + ERR_FAIL_NULL(registry); + + if (name == registry->wl_shm_name) { + if (registry->wl_shm) { + wl_shm_destroy(registry->wl_shm); + registry->wl_shm = nullptr; + } + + registry->wl_shm_name = 0; + + return; + } + + if (name == registry->xdg_exporter_name) { + if (registry->xdg_exporter) { + zxdg_exporter_v1_destroy(registry->xdg_exporter); + registry->xdg_exporter = nullptr; + } + + registry->xdg_exporter_name = 0; + + return; + } + + if (name == registry->wl_compositor_name) { + if (registry->wl_compositor) { + wl_compositor_destroy(registry->wl_compositor); + registry->wl_compositor = nullptr; + } + + registry->wl_compositor_name = 0; + + return; + } + + if (name == registry->wl_data_device_manager_name) { + if (registry->wl_data_device_manager) { + wl_data_device_manager_destroy(registry->wl_data_device_manager); + registry->wl_data_device_manager = nullptr; + } + + registry->wl_data_device_manager_name = 0; + + // This global is used to create some seat data. Let's clean it. + for (struct wl_seat *wl_seat : registry->wl_seats) { + SeatState *ss = wl_seat_get_seat_state(wl_seat); + ERR_FAIL_NULL(ss); + + if (ss->wl_data_device) { + wl_data_device_destroy(ss->wl_data_device); + ss->wl_data_device = nullptr; + } + + ss->wl_data_device = nullptr; + } + + return; + } + + if (name == registry->xdg_wm_base_name) { + if (registry->xdg_wm_base) { + xdg_wm_base_destroy(registry->xdg_wm_base); + registry->xdg_wm_base = nullptr; + } + + registry->xdg_wm_base_name = 0; + + return; + } + + if (name == registry->wp_viewporter_name) { + WindowState *ws = ®istry->wayland_thread->main_window; + + if (registry->wp_viewporter) { + wp_viewporter_destroy(registry->wp_viewporter); + registry->wp_viewporter = nullptr; + } + + if (ws->wp_viewport) { + wp_viewport_destroy(ws->wp_viewport); + ws->wp_viewport = nullptr; + } + + registry->wp_viewporter_name = 0; + + return; + } + + if (name == registry->wp_fractional_scale_manager_name) { + WindowState *ws = ®istry->wayland_thread->main_window; + + if (registry->wp_fractional_scale_manager) { + wp_fractional_scale_manager_v1_destroy(registry->wp_fractional_scale_manager); + registry->wp_fractional_scale_manager = nullptr; + } + + if (ws->wp_fractional_scale) { + wp_fractional_scale_v1_destroy(ws->wp_fractional_scale); + ws->wp_fractional_scale = nullptr; + } + + registry->wp_fractional_scale_manager_name = 0; + } + + if (name == registry->xdg_decoration_manager_name) { + if (registry->xdg_decoration_manager) { + zxdg_decoration_manager_v1_destroy(registry->xdg_decoration_manager); + registry->xdg_decoration_manager = nullptr; + } + + registry->xdg_decoration_manager_name = 0; + + return; + } + + if (name == registry->xdg_activation_name) { + if (registry->xdg_activation) { + xdg_activation_v1_destroy(registry->xdg_activation); + registry->xdg_activation = nullptr; + } + + registry->xdg_activation_name = 0; + + return; + } + + if (name == registry->wp_primary_selection_device_manager_name) { + if (registry->wp_primary_selection_device_manager) { + zwp_primary_selection_device_manager_v1_destroy(registry->wp_primary_selection_device_manager); + registry->wp_primary_selection_device_manager = nullptr; + } + + registry->wp_primary_selection_device_manager_name = 0; + + // This global is used to create some seat data. Let's clean it. + for (struct wl_seat *wl_seat : registry->wl_seats) { + SeatState *ss = wl_seat_get_seat_state(wl_seat); + ERR_FAIL_NULL(ss); + + if (ss->wp_primary_selection_device) { + zwp_primary_selection_device_v1_destroy(ss->wp_primary_selection_device); + ss->wp_primary_selection_device = nullptr; + } + + if (ss->wp_primary_selection_source) { + zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source); + ss->wp_primary_selection_source = nullptr; + } + + if (ss->wp_primary_selection_offer) { + memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer)); + zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer); + ss->wp_primary_selection_offer = nullptr; + } + } + + return; + } + + if (name == registry->wp_relative_pointer_manager_name) { + if (registry->wp_relative_pointer_manager) { + zwp_relative_pointer_manager_v1_destroy(registry->wp_relative_pointer_manager); + registry->wp_relative_pointer_manager = nullptr; + } + + registry->wp_relative_pointer_manager_name = 0; + + // This global is used to create some seat data. Let's clean it. + for (struct wl_seat *wl_seat : registry->wl_seats) { + SeatState *ss = wl_seat_get_seat_state(wl_seat); + ERR_FAIL_NULL(ss); + + if (ss->wp_relative_pointer) { + zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer); + ss->wp_relative_pointer = nullptr; + } + } + + return; + } + + if (name == registry->wp_pointer_constraints_name) { + if (registry->wp_pointer_constraints) { + zwp_pointer_constraints_v1_destroy(registry->wp_pointer_constraints); + registry->wp_pointer_constraints = nullptr; + } + + registry->wp_pointer_constraints_name = 0; + + // This global is used to create some seat data. Let's clean it. + for (struct wl_seat *wl_seat : registry->wl_seats) { + SeatState *ss = wl_seat_get_seat_state(wl_seat); + ERR_FAIL_NULL(ss); + + if (ss->wp_relative_pointer) { + zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer); + ss->wp_relative_pointer = nullptr; + } + + if (ss->wp_locked_pointer) { + zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer); + ss->wp_locked_pointer = nullptr; + } + + if (ss->wp_confined_pointer) { + zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer); + ss->wp_confined_pointer = nullptr; + } + } + + return; + } + + if (name == registry->wp_pointer_gestures_name) { + if (registry->wp_pointer_gestures) { + zwp_pointer_gestures_v1_destroy(registry->wp_pointer_gestures); + } + + registry->wp_pointer_gestures = nullptr; + registry->wp_pointer_gestures_name = 0; + + // This global is used to create some seat data. Let's clean it. + for (struct wl_seat *wl_seat : registry->wl_seats) { + SeatState *ss = wl_seat_get_seat_state(wl_seat); + ERR_FAIL_NULL(ss); + + if (ss->wp_pointer_gesture_pinch) { + zwp_pointer_gesture_pinch_v1_destroy(ss->wp_pointer_gesture_pinch); + ss->wp_pointer_gesture_pinch = nullptr; + } + } + + return; + } + + if (name == registry->wp_idle_inhibit_manager_name) { + if (registry->wp_idle_inhibit_manager) { + zwp_idle_inhibit_manager_v1_destroy(registry->wp_idle_inhibit_manager); + registry->wp_idle_inhibit_manager = nullptr; + } + + registry->wp_idle_inhibit_manager_name = 0; + + return; + } + + if (name == registry->wp_tablet_manager_name) { + if (registry->wp_tablet_manager) { + zwp_tablet_manager_v2_destroy(registry->wp_tablet_manager); + registry->wp_tablet_manager = nullptr; + } + + registry->wp_tablet_manager_name = 0; + + // This global is used to create some seat data. Let's clean it. + for (struct wl_seat *wl_seat : registry->wl_seats) { + SeatState *ss = wl_seat_get_seat_state(wl_seat); + ERR_FAIL_NULL(ss); + + for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) { + TabletToolState *state = wp_tablet_tool_get_state(tool); + if (state) { + memdelete(state); + } + + zwp_tablet_tool_v2_destroy(tool); + } + + ss->tablet_tools.clear(); + } + + return; + } + + if (name == registry->wp_text_input_manager_name) { + if (registry->wp_text_input_manager) { + zwp_text_input_manager_v3_destroy(registry->wp_text_input_manager); + registry->wp_text_input_manager = nullptr; + } + + registry->wp_text_input_manager_name = 0; + + for (struct wl_seat *wl_seat : registry->wl_seats) { + SeatState *ss = wl_seat_get_seat_state(wl_seat); + ERR_FAIL_NULL(ss); + + zwp_text_input_v3_destroy(ss->wp_text_input); + ss->wp_text_input = nullptr; + } + + return; + } + + { + // Iterate through all of the seats to find if any got removed. + List::Element *E = registry->wl_seats.front(); + while (E) { + struct wl_seat *wl_seat = E->get(); + List::Element *N = E->next(); + + SeatState *ss = wl_seat_get_seat_state(wl_seat); + ERR_FAIL_NULL(ss); + + if (ss->wl_seat_name == name) { + if (wl_seat) { + wl_seat_destroy(wl_seat); + } + + if (ss->wl_data_device) { + wl_data_device_destroy(ss->wl_data_device); + } + + if (ss->wp_tablet_seat) { + zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat); + + for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) { + TabletToolState *state = wp_tablet_tool_get_state(tool); + if (state) { + memdelete(state); + } + + zwp_tablet_tool_v2_destroy(tool); + } + } + + memdelete(ss); + + registry->wl_seats.erase(E); + return; + } + + E = N; + } + } + + { + // Iterate through all of the outputs to find if any got removed. + // FIXME: This is a very bruteforce approach. + List::Element *it = registry->wl_outputs.front(); + while (it) { + // Iterate through all of the screens to find if any got removed. + struct wl_output *wl_output = it->get(); + ERR_FAIL_NULL(wl_output); + + ScreenState *ss = wl_output_get_screen_state(wl_output); + + if (ss->wl_output_name == name) { + registry->wl_outputs.erase(it); + + memdelete(ss); + wl_output_destroy(wl_output); + + return; + } + + it = it->next(); + } + } +} diff --git a/platform/linuxbsd/wayland/thread/core/seat.cpp b/platform/linuxbsd/wayland/thread/core/seat.cpp new file mode 100644 index 000000000000..93dc3b9dc91d --- /dev/null +++ b/platform/linuxbsd/wayland/thread/core/seat.cpp @@ -0,0 +1,119 @@ +/**************************************************************************/ +/* seat.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) { + SeatState *ss = (SeatState *)data; + + ERR_FAIL_NULL(ss); + + // TODO: Handle touch. + + // Pointer handling. + if (capabilities & WL_SEAT_CAPABILITY_POINTER) { + if (!ss->wl_pointer) { + ss->cursor_surface = wl_compositor_create_surface(ss->registry->wl_compositor); + wl_surface_commit(ss->cursor_surface); + + ss->wl_pointer = wl_seat_get_pointer(wl_seat); + wl_pointer_add_listener(ss->wl_pointer, &wl_pointer_listener, ss); + + if (ss->registry->wp_relative_pointer_manager) { + ss->wp_relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(ss->registry->wp_relative_pointer_manager, ss->wl_pointer); + zwp_relative_pointer_v1_add_listener(ss->wp_relative_pointer, &wp_relative_pointer_listener, ss); + } + + if (ss->registry->wp_pointer_gestures) { + ss->wp_pointer_gesture_pinch = zwp_pointer_gestures_v1_get_pinch_gesture(ss->registry->wp_pointer_gestures, ss->wl_pointer); + zwp_pointer_gesture_pinch_v1_add_listener(ss->wp_pointer_gesture_pinch, &wp_pointer_gesture_pinch_listener, ss); + } + + // TODO: Constrain new pointers if the global mouse mode is constrained. + } + } else { + if (ss->cursor_frame_callback) { + // Just in case. I got bitten by weird race-like conditions already. + wl_callback_set_user_data(ss->cursor_frame_callback, nullptr); + + wl_callback_destroy(ss->cursor_frame_callback); + ss->cursor_frame_callback = nullptr; + } + + if (ss->cursor_surface) { + wl_surface_destroy(ss->cursor_surface); + ss->cursor_surface = nullptr; + } + + if (ss->wl_pointer) { + wl_pointer_destroy(ss->wl_pointer); + ss->wl_pointer = nullptr; + } + + if (ss->wp_relative_pointer) { + zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer); + ss->wp_relative_pointer = nullptr; + } + + if (ss->wp_confined_pointer) { + zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer); + ss->wp_confined_pointer = nullptr; + } + + if (ss->wp_locked_pointer) { + zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer); + ss->wp_locked_pointer = nullptr; + } + } + + // Keyboard handling. + if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { + if (!ss->wl_keyboard) { + ss->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + ERR_FAIL_NULL(ss->xkb_context); + + ss->wl_keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(ss->wl_keyboard, &wl_keyboard_listener, ss); + } + } else { + if (ss->xkb_context) { + xkb_context_unref(ss->xkb_context); + ss->xkb_context = nullptr; + } + + if (ss->wl_keyboard) { + wl_keyboard_destroy(ss->wl_keyboard); + ss->wl_keyboard = nullptr; + } + } +} + +void WaylandThread::_wl_seat_on_name(void *data, struct wl_seat *wl_seat, const char *name) { +} diff --git a/platform/linuxbsd/wayland/thread/core/surface.cpp b/platform/linuxbsd/wayland/thread/core/surface.cpp new file mode 100644 index 000000000000..94e9aa46a25d --- /dev/null +++ b/platform/linuxbsd/wayland/thread/core/surface.cpp @@ -0,0 +1,77 @@ +/**************************************************************************/ +/* surface.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +void WaylandThread::_wl_surface_on_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { + if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) { + // This won't have the right data bound to it. Not worth it and would probably + // just break everything. + return; + } + + WindowState *ws = (WindowState *)data; + ERR_FAIL_NULL(ws); + + DEBUG_LOG_WAYLAND_THREAD(vformat("Window entered output %x.\n", (size_t)wl_output)); + + ws->wl_outputs.insert(wl_output); + + // Workaround for buffer scaling as there's no guaranteed way of knowing the + // preferred scale. + // TODO: Skip this branch for newer `wl_surface`s once we add support for + // `wl_surface::preferred_buffer_scale` + if (ws->preferred_fractional_scale == 0) { + window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height); + } +} + +void WaylandThread::_wl_surface_on_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { + if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) { + // This won't have the right data bound to it. Not worth it and would probably + // just break everything. + return; + } + + WindowState *ws = (WindowState *)data; + ERR_FAIL_NULL(ws); + + ws->wl_outputs.erase(wl_output); + + DEBUG_LOG_WAYLAND_THREAD(vformat("Window left output %x.\n", (size_t)wl_output)); +} + +// TODO: Add support to this event. +void WaylandThread::_wl_surface_on_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor) { +} + +// TODO: Add support to this event. +void WaylandThread::_wl_surface_on_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform) { +} diff --git a/platform/linuxbsd/wayland/thread/libdecor.cpp b/platform/linuxbsd/wayland/thread/libdecor.cpp new file mode 100644 index 000000000000..16fb3cbb166f --- /dev/null +++ b/platform/linuxbsd/wayland/thread/libdecor.cpp @@ -0,0 +1,106 @@ +/**************************************************************************/ +/* libdecor.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +#ifdef LIBDECOR_ENABLED +void WaylandThread::libdecor_on_error(struct libdecor *context, enum libdecor_error error, const char *message) { + ERR_PRINT(vformat("libdecor error %d: %s", error, message)); +} + +// NOTE: This is pretty much a reimplementation of _xdg_surface_on_configure +// and _xdg_toplevel_on_configure. Libdecor really likes wrapping everything, +// forcing us to do stuff like this. +void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *user_data) { + WindowState *ws = (WindowState *)user_data; + ERR_FAIL_NULL(ws); + + int width = 0; + int height = 0; + + ws->pending_libdecor_configuration = configuration; + + if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { + // The configuration doesn't have a size. We'll use the one already set in the window. + width = ws->rect.size.width; + height = ws->rect.size.height; + } + + ERR_FAIL_COND_MSG(width == 0 || height == 0, "Window has invalid size."); + + libdecor_window_state window_state = LIBDECOR_WINDOW_STATE_NONE; + + // Expect the window to be in a plain state. It will get properly set if the + // compositor reports otherwise below. + ws->mode = DisplayServer::WINDOW_MODE_WINDOWED; + ws->suspended = false; + + if (libdecor_configuration_get_window_state(configuration, &window_state)) { + if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) { + ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED; + } + + if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) { + ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN; + } + + if (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) { + ws->suspended = true; + } + } + + window_state_update_size(ws, width, height); + + DEBUG_LOG_WAYLAND_THREAD(vformat("libdecor frame on configure rect %s", ws->rect)); +} + +void WaylandThread::libdecor_frame_on_close(struct libdecor_frame *frame, void *user_data) { + WindowState *ws = (WindowState *)user_data; + ERR_FAIL_NULL(ws); + + Ref winevent_msg; + winevent_msg.instantiate(); + winevent_msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST; + + ws->wayland_thread->push_message(winevent_msg); + + DEBUG_LOG_WAYLAND_THREAD("libdecor frame on close"); +} + +void WaylandThread::libdecor_frame_on_commit(struct libdecor_frame *frame, void *user_data) { + // We're skipping this as we don't really care about libdecor's commit for + // atomicity reasons. See `_frame_wl_callback_on_done` for more info. + + DEBUG_LOG_WAYLAND_THREAD("libdecor frame on commit"); +} + +void WaylandThread::libdecor_frame_on_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data) { +} +#endif // LIBDECOR_ENABLED diff --git a/platform/linuxbsd/wayland/thread/primary-selection.cpp b/platform/linuxbsd/wayland/thread/primary-selection.cpp new file mode 100644 index 000000000000..df936f33ed65 --- /dev/null +++ b/platform/linuxbsd/wayland/thread/primary-selection.cpp @@ -0,0 +1,105 @@ +/**************************************************************************/ +/* primary-selection.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +#include + +// NOTE: Don't forget to `memfree` the offer's state. +void 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) { + wl_proxy_tag_godot((struct wl_proxy *)offer); + zwp_primary_selection_offer_v1_add_listener(offer, &wp_primary_selection_offer_listener, memnew(OfferState)); +} + +void 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) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (ss->wp_primary_selection_offer) { + memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer)); + zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer); + } + + ss->wp_primary_selection_offer = id; +} + +void WaylandThread::_wp_primary_selection_offer_on_offer(void *data, struct zwp_primary_selection_offer_v1 *wp_primary_selection_offer_v1, const char *mime_type) { + OfferState *os = (OfferState *)data; + ERR_FAIL_NULL(os); + + if (os) { + os->mime_types.insert(String::utf8(mime_type)); + } +} + +void 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) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + Vector *data_to_send = nullptr; + + if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) { + data_to_send = &ss->primary_data; + DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested primary selection."); + } + + if (data_to_send) { + ssize_t written_bytes = 0; + + if (strcmp(mime_type, "text/plain") == 0) { + written_bytes = write(fd, data_to_send->ptr(), data_to_send->size()); + } + + if (written_bytes > 0) { + DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes)); + } else if (written_bytes == 0) { + DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent."); + } else { + ERR_PRINT(vformat("Clipboard: write error %d.", errno)); + } + } + + close(fd); +} + +void WaylandThread::_wp_primary_selection_source_on_cancelled(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) { + zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source); + ss->wp_primary_selection_source = nullptr; + + ss->primary_data.clear(); + + DEBUG_LOG_WAYLAND_THREAD("Clipboard: primary selection set by another program."); + return; + } +} diff --git a/platform/linuxbsd/wayland/thread/tablet.cpp b/platform/linuxbsd/wayland/thread/tablet.cpp new file mode 100644 index 000000000000..4ca0df7c7fa2 --- /dev/null +++ b/platform/linuxbsd/wayland/thread/tablet.cpp @@ -0,0 +1,398 @@ +/**************************************************************************/ +/* tablet.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +// FIXME: Does this cause issues with *BSDs? +#include + +void WaylandThread::_wp_tablet_seat_on_tablet_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_v2 *id) { +} + +void WaylandThread::_wp_tablet_seat_on_tool_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_tool_v2 *id) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + TabletToolState *state = memnew(TabletToolState); + state->wl_seat = ss->wl_seat; + + wl_proxy_tag_godot((struct wl_proxy *)id); + zwp_tablet_tool_v2_add_listener(id, &wp_tablet_tool_listener, state); + + ss->tablet_tools.push_back(id); +} + +void WaylandThread::_wp_tablet_seat_on_pad_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_pad_v2 *id) { +} + +void WaylandThread::_wp_tablet_tool_on_type(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t tool_type) { + TabletToolState *state = wp_tablet_tool_get_state(wp_tablet_tool_v2); + + if (state && tool_type == ZWP_TABLET_TOOL_V2_TYPE_ERASER) { + state->is_eraser = true; + } +} + +void 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) { +} + +void 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) { +} + +void WaylandThread::_wp_tablet_tool_on_capability(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t capability) { +} + +void WaylandThread::_wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) { +} + +void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) { + TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); + if (!ts) { + return; + } + + SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); + if (!ss) { + return; + } + + List::Element *E = ss->tablet_tools.find(wp_tablet_tool_v2); + + if (E && E->get()) { + struct zwp_tablet_tool_v2 *tool = E->get(); + TabletToolState *state = wp_tablet_tool_get_state(tool); + if (state) { + memdelete(state); + } + + zwp_tablet_tool_v2_destroy(tool); + ss->tablet_tools.erase(E); + } +} + +void 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) { + if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) { + // We're probably on a decoration or something. + return; + } + + TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); + if (!ts) { + return; + } + + SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); + if (!ss) { + return; + } + + WaylandThread *wayland_thread = ss->wayland_thread; + ERR_FAIL_NULL(wayland_thread); + + ts->data_pending.proximity_serial = serial; + ts->data_pending.proximal_surface = surface; + ts->last_surface = surface; + + Ref msg; + msg.instantiate(); + msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER; + wayland_thread->push_message(msg); + + DEBUG_LOG_WAYLAND_THREAD("Tablet tool entered window."); +} + +void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) { + TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); + if (!ts || !ts->data_pending.proximal_surface) { + // Not our stuff, we don't care. + return; + } + + SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); + if (!ss) { + return; + } + + WaylandThread *wayland_thread = ss->wayland_thread; + ERR_FAIL_NULL(wayland_thread); + + ts->data_pending.proximal_surface = nullptr; + + Ref msg; + msg.instantiate(); + msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT; + + wayland_thread->push_message(msg); + + DEBUG_LOG_WAYLAND_THREAD("Tablet tool left window."); +} + +void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial) { + TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); + if (!ts) { + return; + } + + TabletToolData &td = ts->data_pending; + + td.pressed_button_mask.set_flag(mouse_button_to_mask(MouseButton::LEFT)); + td.last_button_pressed = MouseButton::LEFT; + td.double_click_begun = true; + + // The protocol doesn't cover this, but we can use this funky hack to make + // double clicking work. + td.button_time = OS::get_singleton()->get_ticks_msec(); +} + +void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) { + TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); + if (!ts) { + return; + } + + TabletToolData &td = ts->data_pending; + + td.pressed_button_mask.clear_flag(mouse_button_to_mask(MouseButton::LEFT)); + + // The protocol doesn't cover this, but we can use this funky hack to make + // double clicking work. + td.button_time = OS::get_singleton()->get_ticks_msec(); +} + +void 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) { + TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); + if (!ts) { + return; + } + + if (!ts->data_pending.proximal_surface) { + // We're probably on a decoration or some other third-party thing. + return; + } + + WindowState *ws = wl_surface_get_window_state(ts->data_pending.proximal_surface); + ERR_FAIL_NULL(ws); + + TabletToolData &td = ts->data_pending; + + double scale_factor = window_state_get_scale_factor(ws); + + td.position.x = wl_fixed_to_double(x); + td.position.y = wl_fixed_to_double(y); + td.position *= scale_factor; + + td.motion_time = OS::get_singleton()->get_ticks_msec(); +} + +void WaylandThread::_wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t pressure) { + TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); + if (!ts) { + return; + } + + ts->data_pending.pressure = pressure; +} + +void WaylandThread::_wp_tablet_tool_on_distance(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t distance) { + // Unsupported +} + +void 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) { + TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); + if (!ts) { + return; + } + + TabletToolData &td = ts->data_pending; + + td.tilt.x = wl_fixed_to_double(tilt_x); + td.tilt.y = wl_fixed_to_double(tilt_y); +} + +void WaylandThread::_wp_tablet_tool_on_rotation(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t degrees) { + // Unsupported. +} + +void WaylandThread::_wp_tablet_tool_on_slider(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, int32_t position) { + // Unsupported. +} + +void WaylandThread::_wp_tablet_tool_on_wheel(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t degrees, int32_t clicks) { + // TODO +} + +void 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) { + TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); + if (!ts) { + return; + } + + TabletToolData &td = ts->data_pending; + + MouseButton mouse_button = MouseButton::NONE; + + if (button == BTN_STYLUS) { + mouse_button = MouseButton::LEFT; + } + + if (button == BTN_STYLUS2) { + mouse_button = MouseButton::RIGHT; + } + + if (mouse_button != MouseButton::NONE) { + MouseButtonMask mask = mouse_button_to_mask(mouse_button); + + if (state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_PRESSED) { + td.pressed_button_mask.set_flag(mask); + td.last_button_pressed = mouse_button; + td.double_click_begun = true; + } else { + td.pressed_button_mask.clear_flag(mask); + } + + // The protocol doesn't cover this, but we can use this funky hack to make + // double clicking work. + td.button_time = OS::get_singleton()->get_ticks_msec(); + } +} + +void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t time) { + TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); + if (!ts) { + return; + } + + SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); + if (!ss) { + return; + } + + WaylandThread *wayland_thread = ss->wayland_thread; + ERR_FAIL_NULL(wayland_thread); + + TabletToolData &old_td = ts->data; + TabletToolData &td = ts->data_pending; + + if (old_td.position != td.position || old_td.tilt != td.tilt || old_td.pressure != td.pressure) { + Ref mm; + mm.instantiate(); + + mm->set_window_id(DisplayServer::MAIN_WINDOW_ID); + + // Set all pressed modifiers. + mm->set_shift_pressed(ss->shift_pressed); + mm->set_ctrl_pressed(ss->ctrl_pressed); + mm->set_alt_pressed(ss->alt_pressed); + mm->set_meta_pressed(ss->meta_pressed); + + mm->set_button_mask(td.pressed_button_mask); + + mm->set_position(td.position); + mm->set_global_position(td.position); + + // NOTE: The Godot API expects normalized values and we store them raw, + // straight from the compositor, so we have to normalize them here. + + // According to the tablet proto spec, tilt is expressed in degrees relative + // to the Z axis of the tablet, so it shouldn't go over 90 degrees either way, + // I think. We'll clamp it just in case. + td.tilt = td.tilt.clampf(-90, 90); + + mm->set_tilt(td.tilt / 90); + + // The tablet proto spec explicitly says that pressure is defined as a value + // between 0 to 65535. + mm->set_pressure(td.pressure / (float)65535); + + mm->set_pen_inverted(ts->is_eraser); + + mm->set_relative(td.position - old_td.position); + mm->set_relative_screen_position(mm->get_relative()); + + Vector2 pos_delta = td.position - old_td.position; + uint32_t time_delta = td.motion_time - old_td.motion_time; + mm->set_velocity((Vector2)pos_delta / time_delta); + + Ref inputev_msg; + inputev_msg.instantiate(); + + inputev_msg->event = mm; + + wayland_thread->push_message(inputev_msg); + } + + if (old_td.pressed_button_mask != td.pressed_button_mask) { + BitField pressed_mask_delta = BitField((int64_t)old_td.pressed_button_mask ^ (int64_t)td.pressed_button_mask); + + for (MouseButton test_button : { MouseButton::LEFT, MouseButton::RIGHT }) { + MouseButtonMask test_button_mask = mouse_button_to_mask(test_button); + + if (pressed_mask_delta.has_flag(test_button_mask)) { + Ref mb; + mb.instantiate(); + + // Set all pressed modifiers. + mb->set_shift_pressed(ss->shift_pressed); + mb->set_ctrl_pressed(ss->ctrl_pressed); + mb->set_alt_pressed(ss->alt_pressed); + mb->set_meta_pressed(ss->meta_pressed); + + mb->set_window_id(DisplayServer::MAIN_WINDOW_ID); + mb->set_position(td.position); + mb->set_global_position(td.position); + + mb->set_button_mask(td.pressed_button_mask); + mb->set_button_index(test_button); + mb->set_pressed(td.pressed_button_mask.has_flag(test_button_mask)); + + // We have to set the last position pressed here as we can't take for + // granted what the individual events might have seen due to them not having + // a garaunteed order. + if (mb->is_pressed()) { + td.last_pressed_position = td.position; + } + + if (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).distance_to(Vector2(old_td.last_pressed_position)) < 5) { + td.double_click_begun = false; + mb->set_double_click(true); + } + + Ref msg; + msg.instantiate(); + + msg->event = mb; + + wayland_thread->push_message(msg); + } + } + } + + old_td = td; +} diff --git a/platform/linuxbsd/wayland/thread/text-input.cpp b/platform/linuxbsd/wayland/thread/text-input.cpp new file mode 100644 index 000000000000..68c025a91345 --- /dev/null +++ b/platform/linuxbsd/wayland/thread/text-input.cpp @@ -0,0 +1,250 @@ +/**************************************************************************/ +/* text-input.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +void WaylandThread::_wp_fractional_scale_on_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale) { + WindowState *ws = (WindowState *)data; + ERR_FAIL_NULL(ws); + + ws->preferred_fractional_scale = (double)scale / 120; + + window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height); +} + +void 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) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (!ss->pointed_surface) { + // We're probably on a decoration or some other third-party thing. + return; + } + + PointerData &pd = ss->pointer_data_buffer; + + WindowState *ws = wl_surface_get_window_state(ss->pointed_surface); + ERR_FAIL_NULL(ws); + + pd.relative_motion.x = wl_fixed_to_double(dx); + pd.relative_motion.y = wl_fixed_to_double(dy); + + pd.relative_motion *= window_state_get_scale_factor(ws); + + pd.relative_motion_time = uptime_lo; +} + +void 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) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + if (fingers == 2) { + ss->old_pinch_scale = wl_fixed_from_int(1); + ss->active_gesture = Gesture::MAGNIFY; + } +} + +void 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) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + WaylandThread *wayland_thread = ss->wayland_thread; + ERR_FAIL_NULL(wayland_thread); + + PointerData &pd = ss->pointer_data_buffer; + + if (ss->active_gesture == Gesture::MAGNIFY) { + Ref mg; + mg.instantiate(); + + mg->set_window_id(DisplayServer::MAIN_WINDOW_ID); + + // Set all pressed modifiers. + mg->set_shift_pressed(ss->shift_pressed); + mg->set_ctrl_pressed(ss->ctrl_pressed); + mg->set_alt_pressed(ss->alt_pressed); + mg->set_meta_pressed(ss->meta_pressed); + + mg->set_position(pd.position); + + wl_fixed_t scale_delta = scale - ss->old_pinch_scale; + mg->set_factor(1 + wl_fixed_to_double(scale_delta)); + + Ref magnify_msg; + magnify_msg.instantiate(); + magnify_msg->event = mg; + + // Since Wayland allows only one gesture at a time and godot instead expects + // both of them, we'll have to create two separate input events: one for + // magnification and one for panning. + + Ref pg; + pg.instantiate(); + + pg->set_window_id(DisplayServer::MAIN_WINDOW_ID); + + // Set all pressed modifiers. + pg->set_shift_pressed(ss->shift_pressed); + pg->set_ctrl_pressed(ss->ctrl_pressed); + pg->set_alt_pressed(ss->alt_pressed); + pg->set_meta_pressed(ss->meta_pressed); + + pg->set_position(pd.position); + pg->set_delta(Vector2(wl_fixed_to_double(dx), wl_fixed_to_double(dy))); + + Ref pan_msg; + pan_msg.instantiate(); + pan_msg->event = pg; + + wayland_thread->push_message(magnify_msg); + wayland_thread->push_message(pan_msg); + + ss->old_pinch_scale = scale; + } +} + +void 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) { + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); + + ss->active_gesture = Gesture::NONE; +} + +void WaylandThread::_wp_text_input_on_enter(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface) { + SeatState *ss = (SeatState *)data; + if (!ss) { + return; + } + + ss->ime_enabled = true; +} + +void WaylandThread::_wp_text_input_on_leave(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface) { + SeatState *ss = (SeatState *)data; + if (!ss) { + return; + } + + ss->ime_enabled = false; + ss->ime_active = false; + ss->ime_text = String(); + ss->ime_text_commit = String(); + ss->ime_cursor = Vector2i(); + + Ref msg; + msg.instantiate(); + msg->text = String(); + msg->selection = Vector2i(); + ss->wayland_thread->push_message(msg); +} + +void 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) { + SeatState *ss = (SeatState *)data; + if (!ss) { + return; + } + + ss->ime_text = String::utf8(text); + + // Convert cursor positions from UTF-8 to UTF-32 offset. + int32_t cursor_begin_utf32 = 0; + int32_t cursor_end_utf32 = 0; + for (int i = 0; i < ss->ime_text.length(); i++) { + uint32_t c = ss->ime_text[i]; + if (c <= 0x7f) { // 7 bits. + cursor_begin -= 1; + cursor_end -= 1; + } else if (c <= 0x7ff) { // 11 bits + cursor_begin -= 2; + cursor_end -= 2; + } else if (c <= 0xffff) { // 16 bits + cursor_begin -= 3; + cursor_end -= 3; + } else if (c <= 0x001fffff) { // 21 bits + cursor_begin -= 4; + cursor_end -= 4; + } else if (c <= 0x03ffffff) { // 26 bits + cursor_begin -= 5; + cursor_end -= 5; + } else if (c <= 0x7fffffff) { // 31 bits + cursor_begin -= 6; + cursor_end -= 6; + } else { + cursor_begin -= 1; + cursor_end -= 1; + } + if (cursor_begin == 0) { + cursor_begin_utf32 = i + 1; + } + if (cursor_end == 0) { + cursor_end_utf32 = i + 1; + } + if (cursor_begin <= 0 && cursor_end <= 0) { + break; + } + } + ss->ime_cursor = Vector2i(cursor_begin_utf32, cursor_end_utf32 - cursor_begin_utf32); +} + +void WaylandThread::_wp_text_input_on_commit_string(void *data, struct zwp_text_input_v3 *wp_text_input_v3, const char *text) { + SeatState *ss = (SeatState *)data; + if (!ss) { + return; + } + + ss->ime_text_commit = String::utf8(text); +} + +void 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) { + // Not implemented. +} + +void WaylandThread::_wp_text_input_on_done(void *data, struct zwp_text_input_v3 *wp_text_input_v3, uint32_t serial) { + SeatState *ss = (SeatState *)data; + if (!ss) { + return; + } + + if (!ss->ime_text_commit.is_empty()) { + Ref msg; + msg.instantiate(); + msg->text = ss->ime_text_commit; + ss->wayland_thread->push_message(msg); + } else if (!ss->ime_text.is_empty()) { + Ref msg; + msg.instantiate(); + msg->text = ss->ime_text; + msg->selection = ss->ime_cursor; + ss->wayland_thread->push_message(msg); + } + ss->ime_text = String(); + ss->ime_text_commit = String(); + ss->ime_cursor = Vector2i(); +} diff --git a/platform/linuxbsd/wayland/thread/util.cpp b/platform/linuxbsd/wayland/thread/util.cpp new file mode 100644 index 000000000000..e9522ded1855 --- /dev/null +++ b/platform/linuxbsd/wayland/thread/util.cpp @@ -0,0 +1,166 @@ +/**************************************************************************/ +/* util.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +#include +#include +#include + +// Read the content pointed by fd into a Vector. +Vector WaylandThread::_read_fd(int fd) { + // This is pretty much an arbitrary size. + uint32_t chunk_size = 2048; + + LocalVector data; + data.resize(chunk_size); + + uint32_t bytes_read = 0; + + while (true) { + ssize_t last_bytes_read = read(fd, data.ptr() + bytes_read, chunk_size); + if (last_bytes_read < 0) { + ERR_PRINT(vformat("Read error %d.", errno)); + + data.clear(); + break; + } + + if (last_bytes_read == 0) { + // We're done, we've reached the EOF. + DEBUG_LOG_WAYLAND_THREAD(vformat("Done reading %d bytes.", bytes_read)); + + close(fd); + + data.resize(bytes_read); + break; + } + + DEBUG_LOG_WAYLAND_THREAD(vformat("Read chunk of %d bytes.", last_bytes_read)); + + bytes_read += last_bytes_read; + + // Increase the buffer size by one chunk in preparation of the next read. + data.resize(bytes_read + chunk_size); + } + + return data; +} + +// Based on the wayland book's shared memory boilerplate (PD/CC0). +// See: https://wayland-book.com/surfaces/shared-memory.html +int WaylandThread::_allocate_shm_file(size_t size) { + int retries = 100; + + do { + // Generate a random name. + char name[] = "/wl_shm-godot-XXXXXX"; + for (long unsigned int i = sizeof(name) - 7; i < sizeof(name) - 1; i++) { + name[i] = Math::random('A', 'Z'); + } + + // Try to open a shared memory object with that name. + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + // Success, unlink its name as we just need the file descriptor. + shm_unlink(name); + + // Resize the file to the requested length. + int ret; + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + close(fd); + return -1; + } + + return fd; + } + + retries--; + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +// Return the content of a wl_data_offer. +Vector WaylandThread::_wl_data_offer_read(struct wl_display *p_display, const char *p_mime, struct wl_data_offer *p_offer) { + if (!p_offer) { + return Vector(); + } + + int fds[2]; + if (pipe(fds) == 0) { + wl_data_offer_receive(p_offer, p_mime, fds[1]); + + // Let the compositor know about the pipe. + // NOTE: It's important to just flush and not roundtrip here as we would risk + // running some cleanup event, like for example `wl_data_device::leave`. We're + // going to wait for the message anyways as the read will probably block if + // the compositor doesn't read from the other end of the pipe. + wl_display_flush(p_display); + + // Close the write end of the pipe, which we don't need and would otherwise + // just stall our next `read`s. + close(fds[1]); + + return _read_fd(fds[0]); + } + + return Vector(); +} + +// Read the content of a wp_primary_selection_offer. +Vector WaylandThread::_wp_primary_selection_offer_read(struct wl_display *p_display, const char *p_mime, struct zwp_primary_selection_offer_v1 *p_offer) { + if (!p_offer) { + return Vector(); + } + + int fds[2]; + if (pipe(fds) == 0) { + zwp_primary_selection_offer_v1_receive(p_offer, p_mime, fds[1]); + + // NOTE: It's important to just flush and not roundtrip here as we would risk + // running some cleanup event, like for example `wl_data_device::leave`. We're + // going to wait for the message anyways as the read will probably block if + // the compositor doesn't read from the other end of the pipe. + wl_display_flush(p_display); + + // Close the write end of the pipe, which we don't need and would otherwise + // just stall our next `read`s. + close(fds[1]); + + return _read_fd(fds[0]); + } + + return Vector(); +} diff --git a/platform/linuxbsd/wayland/thread/xdg-activation.cpp b/platform/linuxbsd/wayland/thread/xdg-activation.cpp new file mode 100644 index 000000000000..314f0d74a933 --- /dev/null +++ b/platform/linuxbsd/wayland/thread/xdg-activation.cpp @@ -0,0 +1,43 @@ +/**************************************************************************/ +/* xdg-activation.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +void WaylandThread::_xdg_activation_token_on_done(void *data, struct xdg_activation_token_v1 *xdg_activation_token, const char *token) { + WindowState *ws = (WindowState *)data; + ERR_FAIL_NULL(ws); + ERR_FAIL_NULL(ws->wayland_thread); + ERR_FAIL_NULL(ws->wl_surface); + + xdg_activation_v1_activate(ws->wayland_thread->registry.xdg_activation, token, ws->wl_surface); + xdg_activation_token_v1_destroy(xdg_activation_token); + + DEBUG_LOG_WAYLAND_THREAD(vformat("Received activation token and requested window activation.")); +} diff --git a/platform/linuxbsd/wayland/thread/xdg-shell.cpp b/platform/linuxbsd/wayland/thread/xdg-shell.cpp new file mode 100644 index 000000000000..7a31d8cc24e5 --- /dev/null +++ b/platform/linuxbsd/wayland/thread/xdg-shell.cpp @@ -0,0 +1,139 @@ +/**************************************************************************/ +/* xdg-shell.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "wayland/wayland_thread.h" + +void WaylandThread::_xdg_wm_base_on_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { + xdg_wm_base_pong(xdg_wm_base, serial); +} + +void WaylandThread::_xdg_surface_on_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) { + xdg_surface_ack_configure(xdg_surface, serial); + + WindowState *ws = (WindowState *)data; + ERR_FAIL_NULL(ws); + + DEBUG_LOG_WAYLAND_THREAD(vformat("xdg surface on configure width %d height %d", ws->rect.size.width, ws->rect.size.height)); +} + +void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) { + WindowState *ws = (WindowState *)data; + ERR_FAIL_NULL(ws); + + // Expect the window to be in a plain state. It will get properly set if the + // compositor reports otherwise below. + ws->mode = DisplayServer::WINDOW_MODE_WINDOWED; + ws->suspended = false; + + uint32_t *state = nullptr; + wl_array_for_each(state, states) { + switch (*state) { + case XDG_TOPLEVEL_STATE_MAXIMIZED: { + ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED; + } break; + + case XDG_TOPLEVEL_STATE_FULLSCREEN: { + ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN; + } break; + + case XDG_TOPLEVEL_STATE_SUSPENDED: { + ws->suspended = true; + } break; + + default: { + // We don't care about the other states (for now). + } break; + } + } + + if (width != 0 && height != 0) { + window_state_update_size(ws, width, height); + } + + DEBUG_LOG_WAYLAND_THREAD(vformat("XDG toplevel on configure width %d height %d.", width, height)); +} + +void WaylandThread::_xdg_toplevel_on_close(void *data, struct xdg_toplevel *xdg_toplevel) { + WindowState *ws = (WindowState *)data; + ERR_FAIL_NULL(ws); + + Ref msg; + msg.instantiate(); + msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST; + ws->wayland_thread->push_message(msg); +} + +void WaylandThread::_xdg_toplevel_on_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) { +} + +void WaylandThread::_xdg_toplevel_on_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities) { + WindowState *ws = (WindowState *)data; + ERR_FAIL_NULL(ws); + + ws->can_maximize = false; + ws->can_fullscreen = false; + ws->can_minimize = false; + + uint32_t *capability = nullptr; + wl_array_for_each(capability, capabilities) { + switch (*capability) { + case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: { + ws->can_maximize = true; + } break; + case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: { + ws->can_fullscreen = true; + } break; + + case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: { + ws->can_minimize = true; + } break; + + default: { + } break; + } + } +} + +void WaylandThread::_xdg_exported_on_exported(void *data, zxdg_exported_v1 *exported, const char *handle) { + WindowState *ws = (WindowState *)data; + ERR_FAIL_NULL(ws); + + ws->exported_handle = vformat("wayland:%s", String::utf8(handle)); +} + +void WaylandThread::_xdg_toplevel_decoration_on_configure(void *data, struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration, uint32_t mode) { + if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE) { +#ifdef LIBDECOR_ENABLED + WARN_PRINT_ONCE("Native client side decorations are not yet supported without libdecor!"); +#else + WARN_PRINT_ONCE("Native client side decorations are not yet supported!"); +#endif // LIBDECOR_ENABLED + } +} diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 08b20c5b4265..776ff07b2b37 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -32,160 +32,10 @@ #ifdef WAYLAND_ENABLED -// FIXME: Does this cause issues with *BSDs? -#include - // For the actual polling thread. #include -// For shared memory buffer creation. -#include #include -#include - -// Fix the wl_array_for_each macro to work with C++. This is based on the -// original from `wayland-util.h` in the Wayland client library. -#undef wl_array_for_each -#define wl_array_for_each(pos, array) \ - for (pos = (decltype(pos))(array)->data; (const char *)pos < ((const char *)(array)->data + (array)->size); (pos)++) - -#define WAYLAND_THREAD_DEBUG_LOGS_ENABLED -#ifdef WAYLAND_THREAD_DEBUG_LOGS_ENABLED -#define DEBUG_LOG_WAYLAND_THREAD(...) print_verbose(__VA_ARGS__) -#else -#define DEBUG_LOG_WAYLAND_THREAD(...) -#endif - -// Read the content pointed by fd into a Vector. -Vector WaylandThread::_read_fd(int fd) { - // This is pretty much an arbitrary size. - uint32_t chunk_size = 2048; - - LocalVector data; - data.resize(chunk_size); - - uint32_t bytes_read = 0; - - while (true) { - ssize_t last_bytes_read = read(fd, data.ptr() + bytes_read, chunk_size); - if (last_bytes_read < 0) { - ERR_PRINT(vformat("Read error %d.", errno)); - - data.clear(); - break; - } - - if (last_bytes_read == 0) { - // We're done, we've reached the EOF. - DEBUG_LOG_WAYLAND_THREAD(vformat("Done reading %d bytes.", bytes_read)); - - close(fd); - - data.resize(bytes_read); - break; - } - - DEBUG_LOG_WAYLAND_THREAD(vformat("Read chunk of %d bytes.", last_bytes_read)); - - bytes_read += last_bytes_read; - - // Increase the buffer size by one chunk in preparation of the next read. - data.resize(bytes_read + chunk_size); - } - - return data; -} - -// Based on the wayland book's shared memory boilerplate (PD/CC0). -// See: https://wayland-book.com/surfaces/shared-memory.html -int WaylandThread::_allocate_shm_file(size_t size) { - int retries = 100; - - do { - // Generate a random name. - char name[] = "/wl_shm-godot-XXXXXX"; - for (long unsigned int i = sizeof(name) - 7; i < sizeof(name) - 1; i++) { - name[i] = Math::random('A', 'Z'); - } - - // Try to open a shared memory object with that name. - int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); - if (fd >= 0) { - // Success, unlink its name as we just need the file descriptor. - shm_unlink(name); - - // Resize the file to the requested length. - int ret; - do { - ret = ftruncate(fd, size); - } while (ret < 0 && errno == EINTR); - - if (ret < 0) { - close(fd); - return -1; - } - - return fd; - } - - retries--; - } while (retries > 0 && errno == EEXIST); - - return -1; -} - -// Return the content of a wl_data_offer. -Vector WaylandThread::_wl_data_offer_read(struct wl_display *p_display, const char *p_mime, struct wl_data_offer *p_offer) { - if (!p_offer) { - return Vector(); - } - - int fds[2]; - if (pipe(fds) == 0) { - wl_data_offer_receive(p_offer, p_mime, fds[1]); - - // Let the compositor know about the pipe. - // NOTE: It's important to just flush and not roundtrip here as we would risk - // running some cleanup event, like for example `wl_data_device::leave`. We're - // going to wait for the message anyways as the read will probably block if - // the compositor doesn't read from the other end of the pipe. - wl_display_flush(p_display); - - // Close the write end of the pipe, which we don't need and would otherwise - // just stall our next `read`s. - close(fds[1]); - - return _read_fd(fds[0]); - } - - return Vector(); -} - -// Read the content of a wp_primary_selection_offer. -Vector WaylandThread::_wp_primary_selection_offer_read(struct wl_display *p_display, const char *p_mime, struct zwp_primary_selection_offer_v1 *p_offer) { - if (!p_offer) { - return Vector(); - } - - int fds[2]; - if (pipe(fds) == 0) { - zwp_primary_selection_offer_v1_receive(p_offer, p_mime, fds[1]); - - // NOTE: It's important to just flush and not roundtrip here as we would risk - // running some cleanup event, like for example `wl_data_device::leave`. We're - // going to wait for the message anyways as the read will probably block if - // the compositor doesn't read from the other end of the pipe. - wl_display_flush(p_display); - - // Close the write end of the pipe, which we don't need and would otherwise - // just stall our next `read`s. - close(fds[1]); - - return _read_fd(fds[0]); - } - - return Vector(); -} // Sets up an `InputEventKey` and returns whether it has any meaningful value. bool WaylandThread::_seat_state_configure_key_event(SeatState &p_ss, Ref p_event, xkb_keycode_t p_keycode, bool p_pressed) { @@ -361,2351 +211,42 @@ void WaylandThread::_update_scale(int p_scale) { } } -void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) { - RegistryState *registry = (RegistryState *)data; - ERR_FAIL_NULL(registry); - - if (strcmp(interface, wl_shm_interface.name) == 0) { - registry->wl_shm = (struct wl_shm *)wl_registry_bind(wl_registry, name, &wl_shm_interface, 1); - registry->wl_shm_name = name; - return; - } - - if (strcmp(interface, zxdg_exporter_v1_interface.name) == 0) { - registry->xdg_exporter = (struct zxdg_exporter_v1 *)wl_registry_bind(wl_registry, name, &zxdg_exporter_v1_interface, 1); - registry->xdg_exporter_name = name; - return; - } - - if (strcmp(interface, wl_compositor_interface.name) == 0) { - registry->wl_compositor = (struct wl_compositor *)wl_registry_bind(wl_registry, name, &wl_compositor_interface, CLAMP((int)version, 1, 6)); - registry->wl_compositor_name = name; - return; - } - - if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { - registry->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)); - registry->wl_data_device_manager_name = name; - - // This global creates some seat data. Let's do that for the ones already available. - for (struct wl_seat *wl_seat : registry->wl_seats) { - SeatState *ss = wl_seat_get_seat_state(wl_seat); - ERR_FAIL_NULL(ss); - - if (ss->wl_data_device == nullptr) { - ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat); - wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss); - } - } - return; - } - - if (strcmp(interface, wl_output_interface.name) == 0) { - struct wl_output *wl_output = (struct wl_output *)wl_registry_bind(wl_registry, name, &wl_output_interface, CLAMP((int)version, 1, 4)); - wl_proxy_tag_godot((struct wl_proxy *)wl_output); - - registry->wl_outputs.push_back(wl_output); - - ScreenState *ss = memnew(ScreenState); - ss->wl_output_name = name; - ss->wayland_thread = registry->wayland_thread; - - wl_proxy_tag_godot((struct wl_proxy *)wl_output); - wl_output_add_listener(wl_output, &wl_output_listener, ss); - return; - } - - if (strcmp(interface, wl_seat_interface.name) == 0) { - struct wl_seat *wl_seat = (struct wl_seat *)wl_registry_bind(wl_registry, name, &wl_seat_interface, CLAMP((int)version, 1, 9)); - wl_proxy_tag_godot((struct wl_proxy *)wl_seat); - - SeatState *ss = memnew(SeatState); - ss->wl_seat = wl_seat; - ss->wl_seat_name = name; - - ss->registry = registry; - ss->wayland_thread = registry->wayland_thread; - - // Some extra stuff depends on other globals. We'll initialize them if the - // globals are already there, otherwise we'll have to do that once and if they - // get announced. - // - // NOTE: Don't forget to also bind/destroy with the respective global. - if (!ss->wl_data_device && registry->wl_data_device_manager) { - // Clipboard & DnD. - ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat); - wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss); - } - - if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) { - // Primary selection. - ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat); - zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss); - } - - if (!ss->wp_tablet_seat && registry->wp_tablet_manager) { - // Tablet. - ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat); - zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss); - } - - if (!ss->wp_text_input && registry->wp_text_input_manager) { - // IME. - ss->wp_text_input = zwp_text_input_manager_v3_get_text_input(registry->wp_text_input_manager, wl_seat); - zwp_text_input_v3_add_listener(ss->wp_text_input, &wp_text_input_listener, ss); - } - - registry->wl_seats.push_back(wl_seat); - - wl_seat_add_listener(wl_seat, &wl_seat_listener, ss); - - if (registry->wayland_thread->wl_seat_current == nullptr) { - registry->wayland_thread->_set_current_seat(wl_seat); - } - - return; - } - - if (strcmp(interface, xdg_wm_base_interface.name) == 0) { - registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, CLAMP((int)version, 1, 6)); - registry->xdg_wm_base_name = name; - - xdg_wm_base_add_listener(registry->xdg_wm_base, &xdg_wm_base_listener, nullptr); - return; - } - - if (strcmp(interface, wp_viewporter_interface.name) == 0) { - registry->wp_viewporter = (struct wp_viewporter *)wl_registry_bind(wl_registry, name, &wp_viewporter_interface, 1); - registry->wp_viewporter_name = name; - } - - if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { - registry->wp_fractional_scale_manager = (struct wp_fractional_scale_manager_v1 *)wl_registry_bind(wl_registry, name, &wp_fractional_scale_manager_v1_interface, 1); - registry->wp_fractional_scale_manager_name = name; - - // NOTE: We're not mapping the fractional scale object here because this is - // supposed to be a "startup global". If for some reason this isn't true (who - // knows), add a conditional branch for creating the add-on object. - } - - if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { - registry->xdg_decoration_manager = (struct zxdg_decoration_manager_v1 *)wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1); - registry->xdg_decoration_manager_name = name; - return; - } - - if (strcmp(interface, xdg_activation_v1_interface.name) == 0) { - registry->xdg_activation = (struct xdg_activation_v1 *)wl_registry_bind(wl_registry, name, &xdg_activation_v1_interface, 1); - registry->xdg_activation_name = name; - return; - } - - if (strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0) { - registry->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); - - // This global creates some seat data. Let's do that for the ones already available. - for (struct wl_seat *wl_seat : registry->wl_seats) { - SeatState *ss = wl_seat_get_seat_state(wl_seat); - ERR_FAIL_NULL(ss); - - if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) { - ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat); - zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss); - } - } - } - - if (strcmp(interface, zwp_relative_pointer_manager_v1_interface.name) == 0) { - registry->wp_relative_pointer_manager = (struct zwp_relative_pointer_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_relative_pointer_manager_v1_interface, 1); - registry->wp_relative_pointer_manager_name = name; - return; - } - - if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) { - registry->wp_pointer_constraints = (struct zwp_pointer_constraints_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1); - registry->wp_pointer_constraints_name = name; - return; - } - - if (strcmp(interface, zwp_pointer_gestures_v1_interface.name) == 0) { - registry->wp_pointer_gestures = (struct zwp_pointer_gestures_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_gestures_v1_interface, 1); - registry->wp_pointer_gestures_name = name; - return; - } - - if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { - registry->wp_idle_inhibit_manager = (struct zwp_idle_inhibit_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_idle_inhibit_manager_v1_interface, 1); - registry->wp_idle_inhibit_manager_name = name; - return; - } - - if (strcmp(interface, zwp_tablet_manager_v2_interface.name) == 0) { - registry->wp_tablet_manager = (struct zwp_tablet_manager_v2 *)wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1); - registry->wp_tablet_manager_name = name; - - // This global creates some seat data. Let's do that for the ones already available. - for (struct wl_seat *wl_seat : registry->wl_seats) { - SeatState *ss = wl_seat_get_seat_state(wl_seat); - ERR_FAIL_NULL(ss); - - ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat); - zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss); - } - - return; - } +void WaylandThread::_frame_wl_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t callback_data) { + wl_callback_destroy(wl_callback); - if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) { - registry->wp_text_input_manager = (struct zwp_text_input_manager_v3 *)wl_registry_bind(wl_registry, name, &zwp_text_input_manager_v3_interface, 1); - registry->wp_text_input_manager_name = name; + WindowState *ws = (WindowState *)data; + ERR_FAIL_NULL(ws); + ERR_FAIL_NULL(ws->wayland_thread); + ERR_FAIL_NULL(ws->wl_surface); - // This global creates some seat data. Let's do that for the ones already available. - for (struct wl_seat *wl_seat : registry->wl_seats) { - SeatState *ss = wl_seat_get_seat_state(wl_seat); - ERR_FAIL_NULL(ss); + ws->wayland_thread->set_frame(); - ss->wp_text_input = zwp_text_input_manager_v3_get_text_input(registry->wp_text_input_manager, wl_seat); - zwp_text_input_v3_add_listener(ss->wp_text_input, &wp_text_input_listener, ss); - } + ws->frame_callback = wl_surface_frame(ws->wl_surface), + wl_callback_add_listener(ws->frame_callback, &frame_wl_callback_listener, ws); - return; + if (ws->wl_surface && ws->buffer_scale_changed) { + // NOTE: We're only now setting the buffer scale as the idea is to get this + // data committed together with the new frame, all by the rendering driver. + // This is important because we might otherwise set an invalid combination of + // buffer size and scale (e.g. odd size and 2x scale). We're pretty much + // guaranteed to get a proper buffer in the next render loop as the rescaling + // method also informs the engine of a "window rect change", triggering + // rendering if needed. + wl_surface_set_buffer_scale(ws->wl_surface, window_state_get_preferred_buffer_scale(ws)); } } -void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) { - RegistryState *registry = (RegistryState *)data; - ERR_FAIL_NULL(registry); - - if (name == registry->wl_shm_name) { - if (registry->wl_shm) { - wl_shm_destroy(registry->wl_shm); - registry->wl_shm = nullptr; - } - - registry->wl_shm_name = 0; - - return; - } - - if (name == registry->xdg_exporter_name) { - if (registry->xdg_exporter) { - zxdg_exporter_v1_destroy(registry->xdg_exporter); - registry->xdg_exporter = nullptr; - } - - registry->xdg_exporter_name = 0; - - return; - } - - if (name == registry->wl_compositor_name) { - if (registry->wl_compositor) { - wl_compositor_destroy(registry->wl_compositor); - registry->wl_compositor = nullptr; - } - - registry->wl_compositor_name = 0; - - return; - } - - if (name == registry->wl_data_device_manager_name) { - if (registry->wl_data_device_manager) { - wl_data_device_manager_destroy(registry->wl_data_device_manager); - registry->wl_data_device_manager = nullptr; - } - - registry->wl_data_device_manager_name = 0; - - // This global is used to create some seat data. Let's clean it. - for (struct wl_seat *wl_seat : registry->wl_seats) { - SeatState *ss = wl_seat_get_seat_state(wl_seat); - ERR_FAIL_NULL(ss); - - if (ss->wl_data_device) { - wl_data_device_destroy(ss->wl_data_device); - ss->wl_data_device = nullptr; - } - - ss->wl_data_device = nullptr; - } - - return; - } - - if (name == registry->xdg_wm_base_name) { - if (registry->xdg_wm_base) { - xdg_wm_base_destroy(registry->xdg_wm_base); - registry->xdg_wm_base = nullptr; - } - - registry->xdg_wm_base_name = 0; - - return; - } - - if (name == registry->wp_viewporter_name) { - WindowState *ws = ®istry->wayland_thread->main_window; - - if (registry->wp_viewporter) { - wp_viewporter_destroy(registry->wp_viewporter); - registry->wp_viewporter = nullptr; - } - - if (ws->wp_viewport) { - wp_viewport_destroy(ws->wp_viewport); - ws->wp_viewport = nullptr; - } - - registry->wp_viewporter_name = 0; - - return; - } - - if (name == registry->wp_fractional_scale_manager_name) { - WindowState *ws = ®istry->wayland_thread->main_window; - - if (registry->wp_fractional_scale_manager) { - wp_fractional_scale_manager_v1_destroy(registry->wp_fractional_scale_manager); - registry->wp_fractional_scale_manager = nullptr; - } - - if (ws->wp_fractional_scale) { - wp_fractional_scale_v1_destroy(ws->wp_fractional_scale); - ws->wp_fractional_scale = nullptr; - } - - registry->wp_fractional_scale_manager_name = 0; - } - - if (name == registry->xdg_decoration_manager_name) { - if (registry->xdg_decoration_manager) { - zxdg_decoration_manager_v1_destroy(registry->xdg_decoration_manager); - registry->xdg_decoration_manager = nullptr; - } - - registry->xdg_decoration_manager_name = 0; - - return; - } - - if (name == registry->xdg_activation_name) { - if (registry->xdg_activation) { - xdg_activation_v1_destroy(registry->xdg_activation); - registry->xdg_activation = nullptr; - } - - registry->xdg_activation_name = 0; - - return; - } - - if (name == registry->wp_primary_selection_device_manager_name) { - if (registry->wp_primary_selection_device_manager) { - zwp_primary_selection_device_manager_v1_destroy(registry->wp_primary_selection_device_manager); - registry->wp_primary_selection_device_manager = nullptr; - } - - registry->wp_primary_selection_device_manager_name = 0; - - // This global is used to create some seat data. Let's clean it. - for (struct wl_seat *wl_seat : registry->wl_seats) { - SeatState *ss = wl_seat_get_seat_state(wl_seat); - ERR_FAIL_NULL(ss); - - if (ss->wp_primary_selection_device) { - zwp_primary_selection_device_v1_destroy(ss->wp_primary_selection_device); - ss->wp_primary_selection_device = nullptr; - } - - if (ss->wp_primary_selection_source) { - zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source); - ss->wp_primary_selection_source = nullptr; - } - - if (ss->wp_primary_selection_offer) { - memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer)); - zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer); - ss->wp_primary_selection_offer = nullptr; - } - } - - return; - } - - if (name == registry->wp_relative_pointer_manager_name) { - if (registry->wp_relative_pointer_manager) { - zwp_relative_pointer_manager_v1_destroy(registry->wp_relative_pointer_manager); - registry->wp_relative_pointer_manager = nullptr; - } - - registry->wp_relative_pointer_manager_name = 0; - - // This global is used to create some seat data. Let's clean it. - for (struct wl_seat *wl_seat : registry->wl_seats) { - SeatState *ss = wl_seat_get_seat_state(wl_seat); - ERR_FAIL_NULL(ss); - - if (ss->wp_relative_pointer) { - zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer); - ss->wp_relative_pointer = nullptr; - } - } - - return; - } - - if (name == registry->wp_pointer_constraints_name) { - if (registry->wp_pointer_constraints) { - zwp_pointer_constraints_v1_destroy(registry->wp_pointer_constraints); - registry->wp_pointer_constraints = nullptr; - } - - registry->wp_pointer_constraints_name = 0; - - // This global is used to create some seat data. Let's clean it. - for (struct wl_seat *wl_seat : registry->wl_seats) { - SeatState *ss = wl_seat_get_seat_state(wl_seat); - ERR_FAIL_NULL(ss); +void WaylandThread::_cursor_frame_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t time_ms) { + wl_callback_destroy(wl_callback); - if (ss->wp_relative_pointer) { - zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer); - ss->wp_relative_pointer = nullptr; - } + SeatState *ss = (SeatState *)data; + ERR_FAIL_NULL(ss); - if (ss->wp_locked_pointer) { - zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer); - ss->wp_locked_pointer = nullptr; - } + ss->cursor_frame_callback = nullptr; - if (ss->wp_confined_pointer) { - zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer); - ss->wp_confined_pointer = nullptr; - } - } + ss->cursor_time_ms = time_ms; - return; - } - - if (name == registry->wp_pointer_gestures_name) { - if (registry->wp_pointer_gestures) { - zwp_pointer_gestures_v1_destroy(registry->wp_pointer_gestures); - } - - registry->wp_pointer_gestures = nullptr; - registry->wp_pointer_gestures_name = 0; - - // This global is used to create some seat data. Let's clean it. - for (struct wl_seat *wl_seat : registry->wl_seats) { - SeatState *ss = wl_seat_get_seat_state(wl_seat); - ERR_FAIL_NULL(ss); - - if (ss->wp_pointer_gesture_pinch) { - zwp_pointer_gesture_pinch_v1_destroy(ss->wp_pointer_gesture_pinch); - ss->wp_pointer_gesture_pinch = nullptr; - } - } - - return; - } - - if (name == registry->wp_idle_inhibit_manager_name) { - if (registry->wp_idle_inhibit_manager) { - zwp_idle_inhibit_manager_v1_destroy(registry->wp_idle_inhibit_manager); - registry->wp_idle_inhibit_manager = nullptr; - } - - registry->wp_idle_inhibit_manager_name = 0; - - return; - } - - if (name == registry->wp_tablet_manager_name) { - if (registry->wp_tablet_manager) { - zwp_tablet_manager_v2_destroy(registry->wp_tablet_manager); - registry->wp_tablet_manager = nullptr; - } - - registry->wp_tablet_manager_name = 0; - - // This global is used to create some seat data. Let's clean it. - for (struct wl_seat *wl_seat : registry->wl_seats) { - SeatState *ss = wl_seat_get_seat_state(wl_seat); - ERR_FAIL_NULL(ss); - - for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) { - TabletToolState *state = wp_tablet_tool_get_state(tool); - if (state) { - memdelete(state); - } - - zwp_tablet_tool_v2_destroy(tool); - } - - ss->tablet_tools.clear(); - } - - return; - } - - if (name == registry->wp_text_input_manager_name) { - if (registry->wp_text_input_manager) { - zwp_text_input_manager_v3_destroy(registry->wp_text_input_manager); - registry->wp_text_input_manager = nullptr; - } - - registry->wp_text_input_manager_name = 0; - - for (struct wl_seat *wl_seat : registry->wl_seats) { - SeatState *ss = wl_seat_get_seat_state(wl_seat); - ERR_FAIL_NULL(ss); - - zwp_text_input_v3_destroy(ss->wp_text_input); - ss->wp_text_input = nullptr; - } - - return; - } - - { - // Iterate through all of the seats to find if any got removed. - List::Element *E = registry->wl_seats.front(); - while (E) { - struct wl_seat *wl_seat = E->get(); - List::Element *N = E->next(); - - SeatState *ss = wl_seat_get_seat_state(wl_seat); - ERR_FAIL_NULL(ss); - - if (ss->wl_seat_name == name) { - if (wl_seat) { - wl_seat_destroy(wl_seat); - } - - if (ss->wl_data_device) { - wl_data_device_destroy(ss->wl_data_device); - } - - if (ss->wp_tablet_seat) { - zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat); - - for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) { - TabletToolState *state = wp_tablet_tool_get_state(tool); - if (state) { - memdelete(state); - } - - zwp_tablet_tool_v2_destroy(tool); - } - } - - memdelete(ss); - - registry->wl_seats.erase(E); - return; - } - - E = N; - } - } - - { - // Iterate through all of the outputs to find if any got removed. - // FIXME: This is a very bruteforce approach. - List::Element *it = registry->wl_outputs.front(); - while (it) { - // Iterate through all of the screens to find if any got removed. - struct wl_output *wl_output = it->get(); - ERR_FAIL_NULL(wl_output); - - ScreenState *ss = wl_output_get_screen_state(wl_output); - - if (ss->wl_output_name == name) { - registry->wl_outputs.erase(it); - - memdelete(ss); - wl_output_destroy(wl_output); - - return; - } - - it = it->next(); - } - } -} - -void WaylandThread::_wl_surface_on_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { - if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) { - // This won't have the right data bound to it. Not worth it and would probably - // just break everything. - return; - } - - WindowState *ws = (WindowState *)data; - ERR_FAIL_NULL(ws); - - DEBUG_LOG_WAYLAND_THREAD(vformat("Window entered output %x.\n", (size_t)wl_output)); - - ws->wl_outputs.insert(wl_output); - - // Workaround for buffer scaling as there's no guaranteed way of knowing the - // preferred scale. - // TODO: Skip this branch for newer `wl_surface`s once we add support for - // `wl_surface::preferred_buffer_scale` - if (ws->preferred_fractional_scale == 0) { - window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height); - } -} - -void WaylandThread::_frame_wl_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t callback_data) { - wl_callback_destroy(wl_callback); - - WindowState *ws = (WindowState *)data; - ERR_FAIL_NULL(ws); - ERR_FAIL_NULL(ws->wayland_thread); - ERR_FAIL_NULL(ws->wl_surface); - - ws->wayland_thread->set_frame(); - - ws->frame_callback = wl_surface_frame(ws->wl_surface), - wl_callback_add_listener(ws->frame_callback, &frame_wl_callback_listener, ws); - - if (ws->wl_surface && ws->buffer_scale_changed) { - // NOTE: We're only now setting the buffer scale as the idea is to get this - // data committed together with the new frame, all by the rendering driver. - // This is important because we might otherwise set an invalid combination of - // buffer size and scale (e.g. odd size and 2x scale). We're pretty much - // guaranteed to get a proper buffer in the next render loop as the rescaling - // method also informs the engine of a "window rect change", triggering - // rendering if needed. - wl_surface_set_buffer_scale(ws->wl_surface, window_state_get_preferred_buffer_scale(ws)); - } -} - -void WaylandThread::_wl_surface_on_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { - if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) { - // This won't have the right data bound to it. Not worth it and would probably - // just break everything. - return; - } - - WindowState *ws = (WindowState *)data; - ERR_FAIL_NULL(ws); - - ws->wl_outputs.erase(wl_output); - - DEBUG_LOG_WAYLAND_THREAD(vformat("Window left output %x.\n", (size_t)wl_output)); -} - -// TODO: Add support to this event. -void WaylandThread::_wl_surface_on_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor) { -} - -// TODO: Add support to this event. -void WaylandThread::_wl_surface_on_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform) { -} - -void 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) { - ScreenState *ss = (ScreenState *)data; - ERR_FAIL_NULL(ss); - - ss->pending_data.position.x = x; - - ss->pending_data.position.x = x; - ss->pending_data.position.y = y; - - ss->pending_data.physical_size.width = physical_width; - ss->pending_data.physical_size.height = physical_height; - - ss->pending_data.make.parse_utf8(make); - ss->pending_data.model.parse_utf8(model); - - // `wl_output::done` is a version 2 addition. We'll directly update the data - // for compatibility. - if (wl_output_get_version(wl_output) == 1) { - ss->data = ss->pending_data; - } -} - -void WaylandThread::_wl_output_on_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { - ScreenState *ss = (ScreenState *)data; - ERR_FAIL_NULL(ss); - - ss->pending_data.size.width = width; - ss->pending_data.size.height = height; - - ss->pending_data.refresh_rate = refresh ? refresh / 1000.0f : -1; - - // `wl_output::done` is a version 2 addition. We'll directly update the data - // for compatibility. - if (wl_output_get_version(wl_output) == 1) { - ss->data = ss->pending_data; - } -} - -// NOTE: The following `wl_output` events are only for version 2 onwards, so we -// can assume that they're "atomic" (i.e. rely on the `wl_output::done` event). - -void WaylandThread::_wl_output_on_done(void *data, struct wl_output *wl_output) { - ScreenState *ss = (ScreenState *)data; - ERR_FAIL_NULL(ss); - - ss->data = ss->pending_data; - - ss->wayland_thread->_update_scale(ss->data.scale); - - DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x done.", (size_t)wl_output)); -} - -void WaylandThread::_wl_output_on_scale(void *data, struct wl_output *wl_output, int32_t factor) { - ScreenState *ss = (ScreenState *)data; - ERR_FAIL_NULL(ss); - - ss->pending_data.scale = factor; - - DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x scale %d", (size_t)wl_output, factor)); -} - -void WaylandThread::_wl_output_on_name(void *data, struct wl_output *wl_output, const char *name) { -} - -void WaylandThread::_wl_output_on_description(void *data, struct wl_output *wl_output, const char *description) { -} - -void WaylandThread::_xdg_wm_base_on_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { - xdg_wm_base_pong(xdg_wm_base, serial); -} - -void WaylandThread::_xdg_surface_on_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) { - xdg_surface_ack_configure(xdg_surface, serial); - - WindowState *ws = (WindowState *)data; - ERR_FAIL_NULL(ws); - - DEBUG_LOG_WAYLAND_THREAD(vformat("xdg surface on configure width %d height %d", ws->rect.size.width, ws->rect.size.height)); -} - -void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) { - WindowState *ws = (WindowState *)data; - ERR_FAIL_NULL(ws); - - // Expect the window to be in a plain state. It will get properly set if the - // compositor reports otherwise below. - ws->mode = DisplayServer::WINDOW_MODE_WINDOWED; - ws->suspended = false; - - uint32_t *state = nullptr; - wl_array_for_each(state, states) { - switch (*state) { - case XDG_TOPLEVEL_STATE_MAXIMIZED: { - ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED; - } break; - - case XDG_TOPLEVEL_STATE_FULLSCREEN: { - ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN; - } break; - - case XDG_TOPLEVEL_STATE_SUSPENDED: { - ws->suspended = true; - } break; - - default: { - // We don't care about the other states (for now). - } break; - } - } - - if (width != 0 && height != 0) { - window_state_update_size(ws, width, height); - } - - DEBUG_LOG_WAYLAND_THREAD(vformat("XDG toplevel on configure width %d height %d.", width, height)); -} - -void WaylandThread::_xdg_toplevel_on_close(void *data, struct xdg_toplevel *xdg_toplevel) { - WindowState *ws = (WindowState *)data; - ERR_FAIL_NULL(ws); - - Ref msg; - msg.instantiate(); - msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST; - ws->wayland_thread->push_message(msg); -} - -void WaylandThread::_xdg_toplevel_on_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) { -} - -void WaylandThread::_xdg_toplevel_on_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities) { - WindowState *ws = (WindowState *)data; - ERR_FAIL_NULL(ws); - - ws->can_maximize = false; - ws->can_fullscreen = false; - ws->can_minimize = false; - - uint32_t *capability = nullptr; - wl_array_for_each(capability, capabilities) { - switch (*capability) { - case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: { - ws->can_maximize = true; - } break; - case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: { - ws->can_fullscreen = true; - } break; - - case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: { - ws->can_minimize = true; - } break; - - default: { - } break; - } - } -} - -void WaylandThread::_xdg_exported_on_exported(void *data, zxdg_exported_v1 *exported, const char *handle) { - WindowState *ws = (WindowState *)data; - ERR_FAIL_NULL(ws); - - ws->exported_handle = vformat("wayland:%s", String::utf8(handle)); -} - -void WaylandThread::_xdg_toplevel_decoration_on_configure(void *data, struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration, uint32_t mode) { - if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE) { -#ifdef LIBDECOR_ENABLED - WARN_PRINT_ONCE("Native client side decorations are not yet supported without libdecor!"); -#else - WARN_PRINT_ONCE("Native client side decorations are not yet supported!"); -#endif // LIBDECOR_ENABLED - } -} - -#ifdef LIBDECOR_ENABLED -void WaylandThread::libdecor_on_error(struct libdecor *context, enum libdecor_error error, const char *message) { - ERR_PRINT(vformat("libdecor error %d: %s", error, message)); -} - -// NOTE: This is pretty much a reimplementation of _xdg_surface_on_configure -// and _xdg_toplevel_on_configure. Libdecor really likes wrapping everything, -// forcing us to do stuff like this. -void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *user_data) { - WindowState *ws = (WindowState *)user_data; - ERR_FAIL_NULL(ws); - - int width = 0; - int height = 0; - - ws->pending_libdecor_configuration = configuration; - - if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { - // The configuration doesn't have a size. We'll use the one already set in the window. - width = ws->rect.size.width; - height = ws->rect.size.height; - } - - ERR_FAIL_COND_MSG(width == 0 || height == 0, "Window has invalid size."); - - libdecor_window_state window_state = LIBDECOR_WINDOW_STATE_NONE; - - // Expect the window to be in a plain state. It will get properly set if the - // compositor reports otherwise below. - ws->mode = DisplayServer::WINDOW_MODE_WINDOWED; - ws->suspended = false; - - if (libdecor_configuration_get_window_state(configuration, &window_state)) { - if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) { - ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED; - } - - if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) { - ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN; - } - - if (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) { - ws->suspended = true; - } - } - - window_state_update_size(ws, width, height); - - DEBUG_LOG_WAYLAND_THREAD(vformat("libdecor frame on configure rect %s", ws->rect)); -} - -void WaylandThread::libdecor_frame_on_close(struct libdecor_frame *frame, void *user_data) { - WindowState *ws = (WindowState *)user_data; - ERR_FAIL_NULL(ws); - - Ref winevent_msg; - winevent_msg.instantiate(); - winevent_msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST; - - ws->wayland_thread->push_message(winevent_msg); - - DEBUG_LOG_WAYLAND_THREAD("libdecor frame on close"); -} - -void WaylandThread::libdecor_frame_on_commit(struct libdecor_frame *frame, void *user_data) { - // We're skipping this as we don't really care about libdecor's commit for - // atomicity reasons. See `_frame_wl_callback_on_done` for more info. - - DEBUG_LOG_WAYLAND_THREAD("libdecor frame on commit"); -} - -void WaylandThread::libdecor_frame_on_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data) { -} -#endif // LIBDECOR_ENABLED - -void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) { - SeatState *ss = (SeatState *)data; - - ERR_FAIL_NULL(ss); - - // TODO: Handle touch. - - // Pointer handling. - if (capabilities & WL_SEAT_CAPABILITY_POINTER) { - if (!ss->wl_pointer) { - ss->cursor_surface = wl_compositor_create_surface(ss->registry->wl_compositor); - wl_surface_commit(ss->cursor_surface); - - ss->wl_pointer = wl_seat_get_pointer(wl_seat); - wl_pointer_add_listener(ss->wl_pointer, &wl_pointer_listener, ss); - - if (ss->registry->wp_relative_pointer_manager) { - ss->wp_relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(ss->registry->wp_relative_pointer_manager, ss->wl_pointer); - zwp_relative_pointer_v1_add_listener(ss->wp_relative_pointer, &wp_relative_pointer_listener, ss); - } - - if (ss->registry->wp_pointer_gestures) { - ss->wp_pointer_gesture_pinch = zwp_pointer_gestures_v1_get_pinch_gesture(ss->registry->wp_pointer_gestures, ss->wl_pointer); - zwp_pointer_gesture_pinch_v1_add_listener(ss->wp_pointer_gesture_pinch, &wp_pointer_gesture_pinch_listener, ss); - } - - // TODO: Constrain new pointers if the global mouse mode is constrained. - } - } else { - if (ss->cursor_frame_callback) { - // Just in case. I got bitten by weird race-like conditions already. - wl_callback_set_user_data(ss->cursor_frame_callback, nullptr); - - wl_callback_destroy(ss->cursor_frame_callback); - ss->cursor_frame_callback = nullptr; - } - - if (ss->cursor_surface) { - wl_surface_destroy(ss->cursor_surface); - ss->cursor_surface = nullptr; - } - - if (ss->wl_pointer) { - wl_pointer_destroy(ss->wl_pointer); - ss->wl_pointer = nullptr; - } - - if (ss->wp_relative_pointer) { - zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer); - ss->wp_relative_pointer = nullptr; - } - - if (ss->wp_confined_pointer) { - zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer); - ss->wp_confined_pointer = nullptr; - } - - if (ss->wp_locked_pointer) { - zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer); - ss->wp_locked_pointer = nullptr; - } - } - - // Keyboard handling. - if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { - if (!ss->wl_keyboard) { - ss->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - ERR_FAIL_NULL(ss->xkb_context); - - ss->wl_keyboard = wl_seat_get_keyboard(wl_seat); - wl_keyboard_add_listener(ss->wl_keyboard, &wl_keyboard_listener, ss); - } - } else { - if (ss->xkb_context) { - xkb_context_unref(ss->xkb_context); - ss->xkb_context = nullptr; - } - - if (ss->wl_keyboard) { - wl_keyboard_destroy(ss->wl_keyboard); - ss->wl_keyboard = nullptr; - } - } -} - -void WaylandThread::_wl_seat_on_name(void *data, struct wl_seat *wl_seat, const char *name) { -} - -void WaylandThread::_cursor_frame_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t time_ms) { - wl_callback_destroy(wl_callback); - - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - ss->cursor_frame_callback = nullptr; - - ss->cursor_time_ms = time_ms; - - seat_state_update_cursor(ss); -} - -void 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) { - if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) { - return; - } - - DEBUG_LOG_WAYLAND_THREAD("Pointing window."); - - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - ERR_FAIL_NULL(ss->cursor_surface); - ss->pointer_enter_serial = serial; - ss->pointed_surface = surface; - ss->last_pointed_surface = surface; - - seat_state_update_cursor(ss); - - Ref msg; - msg.instantiate(); - msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER; - - ss->wayland_thread->push_message(msg); -} - -void WaylandThread::_wl_pointer_on_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) { - if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) { - return; - } - - DEBUG_LOG_WAYLAND_THREAD("Left window."); - - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - WaylandThread *wayland_thread = ss->wayland_thread; - ERR_FAIL_NULL(wayland_thread); - - ss->pointed_surface = nullptr; - - ss->pointer_data_buffer.pressed_button_mask.clear(); - - Ref msg; - msg.instantiate(); - msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT; - - wayland_thread->push_message(msg); -} - -void 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) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (!ss->pointed_surface) { - // We're probably on a decoration or some other third-party thing. - return; - } - - WindowState *ws = wl_surface_get_window_state(ss->pointed_surface); - ERR_FAIL_NULL(ws); - - PointerData &pd = ss->pointer_data_buffer; - - // TODO: Scale only when sending the Wayland message. - pd.position.x = wl_fixed_to_double(surface_x); - pd.position.y = wl_fixed_to_double(surface_y); - - pd.position *= window_state_get_scale_factor(ws); - - pd.motion_time = time; -} - -void WaylandThread::_wl_pointer_on_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (!ss->pointed_surface) { - // We're probably on a decoration or some other third-party thing. - return; - } - - PointerData &pd = ss->pointer_data_buffer; - - MouseButton button_pressed = MouseButton::NONE; - - switch (button) { - case BTN_LEFT: - button_pressed = MouseButton::LEFT; - break; - - case BTN_MIDDLE: - button_pressed = MouseButton::MIDDLE; - break; - - case BTN_RIGHT: - button_pressed = MouseButton::RIGHT; - break; - - case BTN_EXTRA: - button_pressed = MouseButton::MB_XBUTTON1; - break; - - case BTN_SIDE: - button_pressed = MouseButton::MB_XBUTTON2; - break; - - default: { - } - } - - MouseButtonMask mask = mouse_button_to_mask(button_pressed); - - if (state & WL_POINTER_BUTTON_STATE_PRESSED) { - pd.pressed_button_mask.set_flag(mask); - pd.last_button_pressed = button_pressed; - pd.double_click_begun = true; - } else { - pd.pressed_button_mask.clear_flag(mask); - } - - pd.button_time = time; - pd.button_serial = serial; -} - -void WaylandThread::_wl_pointer_on_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (!ss->pointed_surface) { - // We're probably on a decoration or some other third-party thing. - return; - } - - PointerData &pd = ss->pointer_data_buffer; - - switch (axis) { - case WL_POINTER_AXIS_VERTICAL_SCROLL: { - pd.scroll_vector.y = wl_fixed_to_double(value); - } break; - - case WL_POINTER_AXIS_HORIZONTAL_SCROLL: { - pd.scroll_vector.x = wl_fixed_to_double(value); - } break; - } - - pd.button_time = time; -} - -void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_pointer) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (!ss->pointed_surface) { - // We're probably on a decoration or some other third-party thing. - return; - } - - WaylandThread *wayland_thread = ss->wayland_thread; - ERR_FAIL_NULL(wayland_thread); - - wayland_thread->_set_current_seat(ss->wl_seat); - - PointerData &old_pd = ss->pointer_data; - PointerData &pd = ss->pointer_data_buffer; - - if (old_pd.motion_time != pd.motion_time || old_pd.relative_motion_time != pd.relative_motion_time) { - Ref mm; - mm.instantiate(); - - // Set all pressed modifiers. - mm->set_shift_pressed(ss->shift_pressed); - mm->set_ctrl_pressed(ss->ctrl_pressed); - mm->set_alt_pressed(ss->alt_pressed); - mm->set_meta_pressed(ss->meta_pressed); - - mm->set_window_id(DisplayServer::MAIN_WINDOW_ID); - mm->set_button_mask(pd.pressed_button_mask); - mm->set_position(pd.position); - mm->set_global_position(pd.position); - - Vector2 pos_delta = pd.position - old_pd.position; - - if (old_pd.relative_motion_time != pd.relative_motion_time) { - uint32_t time_delta = pd.relative_motion_time - old_pd.relative_motion_time; - - mm->set_relative(pd.relative_motion); - mm->set_velocity((Vector2)pos_delta / time_delta); - } else { - // The spec includes the possibility of having motion events without an - // associated relative motion event. If that's the case, fallback to a - // simple delta of the position. The captured mouse won't report the - // relative speed anymore though. - uint32_t time_delta = pd.motion_time - old_pd.motion_time; - - mm->set_relative(pd.position - old_pd.position); - mm->set_velocity((Vector2)pos_delta / time_delta); - } - mm->set_relative_screen_position(mm->get_relative()); - mm->set_screen_velocity(mm->get_velocity()); - - Ref msg; - msg.instantiate(); - - msg->event = mm; - - wayland_thread->push_message(msg); - } - - if (pd.discrete_scroll_vector_120 - old_pd.discrete_scroll_vector_120 != Vector2i()) { - // This is a discrete scroll (eg. from a scroll wheel), so we'll just emit - // scroll wheel buttons. - if (pd.scroll_vector.y != 0) { - MouseButton button = pd.scroll_vector.y > 0 ? MouseButton::WHEEL_DOWN : MouseButton::WHEEL_UP; - pd.pressed_button_mask.set_flag(mouse_button_to_mask(button)); - } - - if (pd.scroll_vector.x != 0) { - MouseButton button = pd.scroll_vector.x > 0 ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT; - pd.pressed_button_mask.set_flag(mouse_button_to_mask(button)); - } - } else { - if (pd.scroll_vector - old_pd.scroll_vector != Vector2()) { - // This is a continuous scroll, so we'll emit a pan gesture. - Ref pg; - pg.instantiate(); - - // Set all pressed modifiers. - pg->set_shift_pressed(ss->shift_pressed); - pg->set_ctrl_pressed(ss->ctrl_pressed); - pg->set_alt_pressed(ss->alt_pressed); - pg->set_meta_pressed(ss->meta_pressed); - - pg->set_position(pd.position); - - pg->set_window_id(DisplayServer::MAIN_WINDOW_ID); - - pg->set_delta(pd.scroll_vector); - - Ref msg; - msg.instantiate(); - - msg->event = pg; - - wayland_thread->push_message(msg); - } - } - - if (old_pd.pressed_button_mask != pd.pressed_button_mask) { - BitField pressed_mask_delta = old_pd.pressed_button_mask ^ pd.pressed_button_mask; - - const MouseButton buttons_to_test[] = { - MouseButton::LEFT, - MouseButton::MIDDLE, - MouseButton::RIGHT, - MouseButton::WHEEL_UP, - MouseButton::WHEEL_DOWN, - MouseButton::WHEEL_LEFT, - MouseButton::WHEEL_RIGHT, - MouseButton::MB_XBUTTON1, - MouseButton::MB_XBUTTON2, - }; - - for (MouseButton test_button : buttons_to_test) { - MouseButtonMask test_button_mask = mouse_button_to_mask(test_button); - if (pressed_mask_delta.has_flag(test_button_mask)) { - Ref mb; - mb.instantiate(); - - // Set all pressed modifiers. - mb->set_shift_pressed(ss->shift_pressed); - mb->set_ctrl_pressed(ss->ctrl_pressed); - mb->set_alt_pressed(ss->alt_pressed); - mb->set_meta_pressed(ss->meta_pressed); - - mb->set_window_id(DisplayServer::MAIN_WINDOW_ID); - mb->set_position(pd.position); - mb->set_global_position(pd.position); - - if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN) { - // If this is a discrete scroll, specify how many "clicks" it did for this - // pointer frame. - mb->set_factor(Math::abs(pd.discrete_scroll_vector_120.y / (float)120)); - } - - if (test_button == MouseButton::WHEEL_RIGHT || test_button == MouseButton::WHEEL_LEFT) { - // If this is a discrete scroll, specify how many "clicks" it did for this - // pointer frame. - mb->set_factor(fabs(pd.discrete_scroll_vector_120.x / (float)120)); - } - - mb->set_button_mask(pd.pressed_button_mask); - - mb->set_button_index(test_button); - mb->set_pressed(pd.pressed_button_mask.has_flag(test_button_mask)); - - // We have to set the last position pressed here as we can't take for - // granted what the individual events might have seen due to them not having - // a guaranteed order. - if (mb->is_pressed()) { - pd.last_pressed_position = pd.position; - } - - if (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).distance_to(Vector2(pd.last_pressed_position)) < 5) { - pd.double_click_begun = false; - mb->set_double_click(true); - } - - Ref msg; - msg.instantiate(); - - msg->event = mb; - - wayland_thread->push_message(msg); - - // Send an event resetting immediately the wheel key. - // Wayland specification defines axis_stop events as optional and says to - // treat all axis events as unterminated. As such, we have to manually do - // it ourselves. - if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN || test_button == MouseButton::WHEEL_LEFT || test_button == MouseButton::WHEEL_RIGHT) { - // FIXME: This is ugly, I can't find a clean way to clone an InputEvent. - // This works for now, despite being horrible. - Ref wh_up; - wh_up.instantiate(); - - wh_up->set_window_id(DisplayServer::MAIN_WINDOW_ID); - wh_up->set_position(pd.position); - wh_up->set_global_position(pd.position); - - // We have to unset the button to avoid it getting stuck. - pd.pressed_button_mask.clear_flag(test_button_mask); - wh_up->set_button_mask(pd.pressed_button_mask); - - wh_up->set_button_index(test_button); - wh_up->set_pressed(false); - - Ref msg_up; - msg_up.instantiate(); - msg_up->event = wh_up; - wayland_thread->push_message(msg_up); - } - } - } - } - - // Reset the scroll vectors as we already handled them. - pd.scroll_vector = Vector2(); - pd.discrete_scroll_vector_120 = Vector2i(); - - // Update the data all getters read. Wayland's specification requires us to do - // this, since all pointer actions are sent in individual events. - old_pd = pd; -} - -void WaylandThread::_wl_pointer_on_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (!ss->pointed_surface) { - // We're probably on a decoration or some other third-party thing. - return; - } - - ss->pointer_data_buffer.scroll_type = axis_source; -} - -void WaylandThread::_wl_pointer_on_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) { -} - -// NOTE: This event is deprecated since version 8 and superseded by -// `wl_pointer::axis_value120`. This thus converts the data to its -// fraction-of-120 format. -void WaylandThread::_wl_pointer_on_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (!ss->pointed_surface) { - // We're probably on a decoration or some other third-party thing. - return; - } - - PointerData &pd = ss->pointer_data_buffer; - - // NOTE: We can allow ourselves to not accumulate this data (and thus just - // assign it) as the spec guarantees only one event per axis type. - - if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { - pd.discrete_scroll_vector_120.y = discrete * 120; - } - - if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { - pd.discrete_scroll_vector_120.x = discrete * 120; - } -} - -// Supersedes `wl_pointer::axis_discrete` Since version 8. -void WaylandThread::_wl_pointer_on_axis_value120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (!ss->pointed_surface) { - // We're probably on a decoration or some other third-party thing. - return; - } - - PointerData &pd = ss->pointer_data_buffer; - - if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { - pd.discrete_scroll_vector_120.y += value120; - } - - if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { - pd.discrete_scroll_vector_120.x += value120; - } -} - -// TODO: Add support to this event. -void WaylandThread::_wl_pointer_on_axis_relative_direction(void *data, struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction) { -} - -void WaylandThread::_wl_keyboard_on_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { - ERR_FAIL_COND_MSG(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, "Unsupported keymap format announced from the Wayland compositor."); - - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (ss->keymap_buffer) { - // We have already a mapped buffer, so we unmap it. There's no need to reset - // its pointer or size, as we're gonna set them below. - munmap((void *)ss->keymap_buffer, ss->keymap_buffer_size); - ss->keymap_buffer = nullptr; - } - - ss->keymap_buffer = (const char *)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); - ss->keymap_buffer_size = size; - - xkb_keymap_unref(ss->xkb_keymap); - ss->xkb_keymap = xkb_keymap_new_from_string(ss->xkb_context, ss->keymap_buffer, - XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); - - xkb_state_unref(ss->xkb_state); - ss->xkb_state = xkb_state_new(ss->xkb_keymap); -} - -void WaylandThread::_wl_keyboard_on_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - WaylandThread *wayland_thread = ss->wayland_thread; - ERR_FAIL_NULL(wayland_thread); - - wayland_thread->_set_current_seat(ss->wl_seat); - - Ref msg; - msg.instantiate(); - msg->event = DisplayServer::WINDOW_EVENT_FOCUS_IN; - wayland_thread->push_message(msg); -} - -void WaylandThread::_wl_keyboard_on_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - WaylandThread *wayland_thread = ss->wayland_thread; - ERR_FAIL_NULL(wayland_thread); - - ss->repeating_keycode = XKB_KEYCODE_INVALID; - - Ref msg; - msg.instantiate(); - msg->event = DisplayServer::WINDOW_EVENT_FOCUS_OUT; - wayland_thread->push_message(msg); -} - -void WaylandThread::_wl_keyboard_on_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - WaylandThread *wayland_thread = ss->wayland_thread; - ERR_FAIL_NULL(wayland_thread); - - // We have to add 8 to the scancode to get an XKB-compatible keycode. - xkb_keycode_t xkb_keycode = key + 8; - - bool pressed = state & WL_KEYBOARD_KEY_STATE_PRESSED; - - if (pressed) { - if (xkb_keymap_key_repeats(ss->xkb_keymap, xkb_keycode)) { - ss->last_repeat_start_msec = OS::get_singleton()->get_ticks_msec(); - ss->repeating_keycode = xkb_keycode; - } - - ss->last_key_pressed_serial = serial; - } else if (ss->repeating_keycode == xkb_keycode) { - ss->repeating_keycode = XKB_KEYCODE_INVALID; - } - - Ref k; - k.instantiate(); - - if (!_seat_state_configure_key_event(*ss, k, xkb_keycode, pressed)) { - return; - } - - Ref msg; - msg.instantiate(); - msg->event = k; - wayland_thread->push_message(msg); -} - -void 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) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - xkb_state_update_mask(ss->xkb_state, mods_depressed, mods_latched, mods_locked, ss->current_layout_index, ss->current_layout_index, group); - - ss->shift_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED); - ss->ctrl_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED); - ss->alt_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_DEPRESSED); - ss->meta_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_DEPRESSED); - - ss->current_layout_index = group; -} - -void WaylandThread::_wl_keyboard_on_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - ss->repeat_key_delay_msec = 1000 / rate; - ss->repeat_start_delay_msec = delay; -} - -// NOTE: Don't forget to `memfree` the offer's state. -void WaylandThread::_wl_data_device_on_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) { - wl_proxy_tag_godot((struct wl_proxy *)id); - wl_data_offer_add_listener(id, &wl_data_offer_listener, memnew(OfferState)); -} - -void 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) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - ss->dnd_enter_serial = serial; - ss->wl_data_offer_dnd = id; - - // Godot only supports DnD file copying for now. - wl_data_offer_accept(id, serial, "text/uri-list"); - wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); -} - -void WaylandThread::_wl_data_device_on_leave(void *data, struct wl_data_device *wl_data_device) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (ss->wl_data_offer_dnd) { - memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd)); - wl_data_offer_destroy(ss->wl_data_offer_dnd); - ss->wl_data_offer_dnd = nullptr; - } -} - -void 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) { -} - -void WaylandThread::_wl_data_device_on_drop(void *data, struct wl_data_device *wl_data_device) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - WaylandThread *wayland_thread = ss->wayland_thread; - ERR_FAIL_NULL(wayland_thread); - - OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_dnd); - ERR_FAIL_NULL(os); - - if (os) { - Ref msg; - msg.instantiate(); - - Vector list_data = _wl_data_offer_read(wayland_thread->wl_display, "text/uri-list", ss->wl_data_offer_dnd); - - msg->files = String::utf8((const char *)list_data.ptr(), list_data.size()).split("\r\n", false); - for (int i = 0; i < msg->files.size(); i++) { - msg->files.write[i] = msg->files[i].replace("file://", "").uri_decode(); - } - - wayland_thread->push_message(msg); - - wl_data_offer_finish(ss->wl_data_offer_dnd); - } - - memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd)); - wl_data_offer_destroy(ss->wl_data_offer_dnd); - ss->wl_data_offer_dnd = nullptr; -} - -void WaylandThread::_wl_data_device_on_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (ss->wl_data_offer_selection) { - memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_selection)); - wl_data_offer_destroy(ss->wl_data_offer_selection); - } - - ss->wl_data_offer_selection = id; -} - -void WaylandThread::_wl_data_offer_on_offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type) { - OfferState *os = (OfferState *)data; - ERR_FAIL_NULL(os); - - if (os) { - os->mime_types.insert(String::utf8(mime_type)); - } -} - -void WaylandThread::_wl_data_offer_on_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions) { -} - -void WaylandThread::_wl_data_offer_on_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) { -} - -void WaylandThread::_wl_data_source_on_target(void *data, struct wl_data_source *wl_data_source, const char *mime_type) { -} - -void WaylandThread::_wl_data_source_on_send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - Vector *data_to_send = nullptr; - - if (wl_data_source == ss->wl_data_source_selection) { - data_to_send = &ss->selection_data; - DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested selection."); - } - - if (data_to_send) { - ssize_t written_bytes = 0; - - bool valid_mime = false; - - if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) { - valid_mime = true; - } else if (strcmp(mime_type, "text/plain") == 0) { - valid_mime = true; - } - - if (valid_mime) { - written_bytes = write(fd, data_to_send->ptr(), data_to_send->size()); - } - - if (written_bytes > 0) { - DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes)); - } else if (written_bytes == 0) { - DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent."); - } else { - ERR_PRINT(vformat("Clipboard: write error %d.", errno)); - } - } - - close(fd); -} - -void WaylandThread::_wl_data_source_on_cancelled(void *data, struct wl_data_source *wl_data_source) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - wl_data_source_destroy(wl_data_source); - - if (wl_data_source == ss->wl_data_source_selection) { - ss->wl_data_source_selection = nullptr; - ss->selection_data.clear(); - - DEBUG_LOG_WAYLAND_THREAD("Clipboard: selection set by another program."); - return; - } -} - -void WaylandThread::_wl_data_source_on_dnd_drop_performed(void *data, struct wl_data_source *wl_data_source) { -} - -void WaylandThread::_wl_data_source_on_dnd_finished(void *data, struct wl_data_source *wl_data_source) { -} - -void WaylandThread::_wl_data_source_on_action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action) { -} - -void WaylandThread::_wp_fractional_scale_on_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale) { - WindowState *ws = (WindowState *)data; - ERR_FAIL_NULL(ws); - - ws->preferred_fractional_scale = (double)scale / 120; - - window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height); -} - -void 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) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (!ss->pointed_surface) { - // We're probably on a decoration or some other third-party thing. - return; - } - - PointerData &pd = ss->pointer_data_buffer; - - WindowState *ws = wl_surface_get_window_state(ss->pointed_surface); - ERR_FAIL_NULL(ws); - - pd.relative_motion.x = wl_fixed_to_double(dx); - pd.relative_motion.y = wl_fixed_to_double(dy); - - pd.relative_motion *= window_state_get_scale_factor(ws); - - pd.relative_motion_time = uptime_lo; -} - -void 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) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (fingers == 2) { - ss->old_pinch_scale = wl_fixed_from_int(1); - ss->active_gesture = Gesture::MAGNIFY; - } -} - -void 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) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - WaylandThread *wayland_thread = ss->wayland_thread; - ERR_FAIL_NULL(wayland_thread); - - PointerData &pd = ss->pointer_data_buffer; - - if (ss->active_gesture == Gesture::MAGNIFY) { - Ref mg; - mg.instantiate(); - - mg->set_window_id(DisplayServer::MAIN_WINDOW_ID); - - // Set all pressed modifiers. - mg->set_shift_pressed(ss->shift_pressed); - mg->set_ctrl_pressed(ss->ctrl_pressed); - mg->set_alt_pressed(ss->alt_pressed); - mg->set_meta_pressed(ss->meta_pressed); - - mg->set_position(pd.position); - - wl_fixed_t scale_delta = scale - ss->old_pinch_scale; - mg->set_factor(1 + wl_fixed_to_double(scale_delta)); - - Ref magnify_msg; - magnify_msg.instantiate(); - magnify_msg->event = mg; - - // Since Wayland allows only one gesture at a time and godot instead expects - // both of them, we'll have to create two separate input events: one for - // magnification and one for panning. - - Ref pg; - pg.instantiate(); - - pg->set_window_id(DisplayServer::MAIN_WINDOW_ID); - - // Set all pressed modifiers. - pg->set_shift_pressed(ss->shift_pressed); - pg->set_ctrl_pressed(ss->ctrl_pressed); - pg->set_alt_pressed(ss->alt_pressed); - pg->set_meta_pressed(ss->meta_pressed); - - pg->set_position(pd.position); - pg->set_delta(Vector2(wl_fixed_to_double(dx), wl_fixed_to_double(dy))); - - Ref pan_msg; - pan_msg.instantiate(); - pan_msg->event = pg; - - wayland_thread->push_message(magnify_msg); - wayland_thread->push_message(pan_msg); - - ss->old_pinch_scale = scale; - } -} - -void 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) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - ss->active_gesture = Gesture::NONE; -} - -// NOTE: Don't forget to `memfree` the offer's state. -void 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) { - wl_proxy_tag_godot((struct wl_proxy *)offer); - zwp_primary_selection_offer_v1_add_listener(offer, &wp_primary_selection_offer_listener, memnew(OfferState)); -} - -void 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) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (ss->wp_primary_selection_offer) { - memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer)); - zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer); - } - - ss->wp_primary_selection_offer = id; -} - -void WaylandThread::_wp_primary_selection_offer_on_offer(void *data, struct zwp_primary_selection_offer_v1 *wp_primary_selection_offer_v1, const char *mime_type) { - OfferState *os = (OfferState *)data; - ERR_FAIL_NULL(os); - - if (os) { - os->mime_types.insert(String::utf8(mime_type)); - } -} - -void 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) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - Vector *data_to_send = nullptr; - - if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) { - data_to_send = &ss->primary_data; - DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested primary selection."); - } - - if (data_to_send) { - ssize_t written_bytes = 0; - - if (strcmp(mime_type, "text/plain") == 0) { - written_bytes = write(fd, data_to_send->ptr(), data_to_send->size()); - } - - if (written_bytes > 0) { - DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes)); - } else if (written_bytes == 0) { - DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent."); - } else { - ERR_PRINT(vformat("Clipboard: write error %d.", errno)); - } - } - - close(fd); -} - -void WaylandThread::_wp_primary_selection_source_on_cancelled(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) { - zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source); - ss->wp_primary_selection_source = nullptr; - - ss->primary_data.clear(); - - DEBUG_LOG_WAYLAND_THREAD("Clipboard: primary selection set by another program."); - return; - } -} - -void WaylandThread::_wp_tablet_seat_on_tablet_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_v2 *id) { -} - -void WaylandThread::_wp_tablet_seat_on_tool_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_tool_v2 *id) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - TabletToolState *state = memnew(TabletToolState); - state->wl_seat = ss->wl_seat; - - wl_proxy_tag_godot((struct wl_proxy *)id); - zwp_tablet_tool_v2_add_listener(id, &wp_tablet_tool_listener, state); - - ss->tablet_tools.push_back(id); -} - -void WaylandThread::_wp_tablet_seat_on_pad_added(void *data, struct zwp_tablet_seat_v2 *wp_tablet_seat_v2, struct zwp_tablet_pad_v2 *id) { -} - -void WaylandThread::_wp_tablet_tool_on_type(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t tool_type) { - TabletToolState *state = wp_tablet_tool_get_state(wp_tablet_tool_v2); - - if (state && tool_type == ZWP_TABLET_TOOL_V2_TYPE_ERASER) { - state->is_eraser = true; - } -} - -void 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) { -} - -void 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) { -} - -void WaylandThread::_wp_tablet_tool_on_capability(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t capability) { -} - -void WaylandThread::_wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) { -} - -void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) { - TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { - return; - } - - SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); - if (!ss) { - return; - } - - List::Element *E = ss->tablet_tools.find(wp_tablet_tool_v2); - - if (E && E->get()) { - struct zwp_tablet_tool_v2 *tool = E->get(); - TabletToolState *state = wp_tablet_tool_get_state(tool); - if (state) { - memdelete(state); - } - - zwp_tablet_tool_v2_destroy(tool); - ss->tablet_tools.erase(E); - } -} - -void 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) { - if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) { - // We're probably on a decoration or something. - return; - } - - TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { - return; - } - - SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); - if (!ss) { - return; - } - - WaylandThread *wayland_thread = ss->wayland_thread; - ERR_FAIL_NULL(wayland_thread); - - ts->data_pending.proximity_serial = serial; - ts->data_pending.proximal_surface = surface; - ts->last_surface = surface; - - Ref msg; - msg.instantiate(); - msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER; - wayland_thread->push_message(msg); - - DEBUG_LOG_WAYLAND_THREAD("Tablet tool entered window."); -} - -void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) { - TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts || !ts->data_pending.proximal_surface) { - // Not our stuff, we don't care. - return; - } - - SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); - if (!ss) { - return; - } - - WaylandThread *wayland_thread = ss->wayland_thread; - ERR_FAIL_NULL(wayland_thread); - - ts->data_pending.proximal_surface = nullptr; - - Ref msg; - msg.instantiate(); - msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT; - - wayland_thread->push_message(msg); - - DEBUG_LOG_WAYLAND_THREAD("Tablet tool left window."); -} - -void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial) { - TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { - return; - } - - TabletToolData &td = ts->data_pending; - - td.pressed_button_mask.set_flag(mouse_button_to_mask(MouseButton::LEFT)); - td.last_button_pressed = MouseButton::LEFT; - td.double_click_begun = true; - - // The protocol doesn't cover this, but we can use this funky hack to make - // double clicking work. - td.button_time = OS::get_singleton()->get_ticks_msec(); -} - -void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) { - TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { - return; - } - - TabletToolData &td = ts->data_pending; - - td.pressed_button_mask.clear_flag(mouse_button_to_mask(MouseButton::LEFT)); - - // The protocol doesn't cover this, but we can use this funky hack to make - // double clicking work. - td.button_time = OS::get_singleton()->get_ticks_msec(); -} - -void 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) { - TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { - return; - } - - if (!ts->data_pending.proximal_surface) { - // We're probably on a decoration or some other third-party thing. - return; - } - - WindowState *ws = wl_surface_get_window_state(ts->data_pending.proximal_surface); - ERR_FAIL_NULL(ws); - - TabletToolData &td = ts->data_pending; - - double scale_factor = window_state_get_scale_factor(ws); - - td.position.x = wl_fixed_to_double(x); - td.position.y = wl_fixed_to_double(y); - td.position *= scale_factor; - - td.motion_time = OS::get_singleton()->get_ticks_msec(); -} - -void WaylandThread::_wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t pressure) { - TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { - return; - } - - ts->data_pending.pressure = pressure; -} - -void WaylandThread::_wp_tablet_tool_on_distance(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t distance) { - // Unsupported -} - -void 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) { - TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { - return; - } - - TabletToolData &td = ts->data_pending; - - td.tilt.x = wl_fixed_to_double(tilt_x); - td.tilt.y = wl_fixed_to_double(tilt_y); -} - -void WaylandThread::_wp_tablet_tool_on_rotation(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t degrees) { - // Unsupported. -} - -void WaylandThread::_wp_tablet_tool_on_slider(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, int32_t position) { - // Unsupported. -} - -void WaylandThread::_wp_tablet_tool_on_wheel(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t degrees, int32_t clicks) { - // TODO -} - -void 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) { - TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { - return; - } - - TabletToolData &td = ts->data_pending; - - MouseButton mouse_button = MouseButton::NONE; - - if (button == BTN_STYLUS) { - mouse_button = MouseButton::LEFT; - } - - if (button == BTN_STYLUS2) { - mouse_button = MouseButton::RIGHT; - } - - if (mouse_button != MouseButton::NONE) { - MouseButtonMask mask = mouse_button_to_mask(mouse_button); - - if (state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_PRESSED) { - td.pressed_button_mask.set_flag(mask); - td.last_button_pressed = mouse_button; - td.double_click_begun = true; - } else { - td.pressed_button_mask.clear_flag(mask); - } - - // The protocol doesn't cover this, but we can use this funky hack to make - // double clicking work. - td.button_time = OS::get_singleton()->get_ticks_msec(); - } -} - -void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t time) { - TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { - return; - } - - SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); - if (!ss) { - return; - } - - WaylandThread *wayland_thread = ss->wayland_thread; - ERR_FAIL_NULL(wayland_thread); - - TabletToolData &old_td = ts->data; - TabletToolData &td = ts->data_pending; - - if (old_td.position != td.position || old_td.tilt != td.tilt || old_td.pressure != td.pressure) { - Ref mm; - mm.instantiate(); - - mm->set_window_id(DisplayServer::MAIN_WINDOW_ID); - - // Set all pressed modifiers. - mm->set_shift_pressed(ss->shift_pressed); - mm->set_ctrl_pressed(ss->ctrl_pressed); - mm->set_alt_pressed(ss->alt_pressed); - mm->set_meta_pressed(ss->meta_pressed); - - mm->set_button_mask(td.pressed_button_mask); - - mm->set_position(td.position); - mm->set_global_position(td.position); - - // NOTE: The Godot API expects normalized values and we store them raw, - // straight from the compositor, so we have to normalize them here. - - // According to the tablet proto spec, tilt is expressed in degrees relative - // to the Z axis of the tablet, so it shouldn't go over 90 degrees either way, - // I think. We'll clamp it just in case. - td.tilt = td.tilt.clampf(-90, 90); - - mm->set_tilt(td.tilt / 90); - - // The tablet proto spec explicitly says that pressure is defined as a value - // between 0 to 65535. - mm->set_pressure(td.pressure / (float)65535); - - mm->set_pen_inverted(ts->is_eraser); - - mm->set_relative(td.position - old_td.position); - mm->set_relative_screen_position(mm->get_relative()); - - Vector2 pos_delta = td.position - old_td.position; - uint32_t time_delta = td.motion_time - old_td.motion_time; - mm->set_velocity((Vector2)pos_delta / time_delta); - - Ref inputev_msg; - inputev_msg.instantiate(); - - inputev_msg->event = mm; - - wayland_thread->push_message(inputev_msg); - } - - if (old_td.pressed_button_mask != td.pressed_button_mask) { - BitField pressed_mask_delta = BitField((int64_t)old_td.pressed_button_mask ^ (int64_t)td.pressed_button_mask); - - for (MouseButton test_button : { MouseButton::LEFT, MouseButton::RIGHT }) { - MouseButtonMask test_button_mask = mouse_button_to_mask(test_button); - - if (pressed_mask_delta.has_flag(test_button_mask)) { - Ref mb; - mb.instantiate(); - - // Set all pressed modifiers. - mb->set_shift_pressed(ss->shift_pressed); - mb->set_ctrl_pressed(ss->ctrl_pressed); - mb->set_alt_pressed(ss->alt_pressed); - mb->set_meta_pressed(ss->meta_pressed); - - mb->set_window_id(DisplayServer::MAIN_WINDOW_ID); - mb->set_position(td.position); - mb->set_global_position(td.position); - - mb->set_button_mask(td.pressed_button_mask); - mb->set_button_index(test_button); - mb->set_pressed(td.pressed_button_mask.has_flag(test_button_mask)); - - // We have to set the last position pressed here as we can't take for - // granted what the individual events might have seen due to them not having - // a garaunteed order. - if (mb->is_pressed()) { - td.last_pressed_position = td.position; - } - - if (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).distance_to(Vector2(old_td.last_pressed_position)) < 5) { - td.double_click_begun = false; - mb->set_double_click(true); - } - - Ref msg; - msg.instantiate(); - - msg->event = mb; - - wayland_thread->push_message(msg); - } - } - } - - old_td = td; -} - -void WaylandThread::_wp_text_input_on_enter(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface) { - SeatState *ss = (SeatState *)data; - if (!ss) { - return; - } - - ss->ime_enabled = true; -} - -void WaylandThread::_wp_text_input_on_leave(void *data, struct zwp_text_input_v3 *wp_text_input_v3, struct wl_surface *surface) { - SeatState *ss = (SeatState *)data; - if (!ss) { - return; - } - - ss->ime_enabled = false; - ss->ime_active = false; - ss->ime_text = String(); - ss->ime_text_commit = String(); - ss->ime_cursor = Vector2i(); - - Ref msg; - msg.instantiate(); - msg->text = String(); - msg->selection = Vector2i(); - ss->wayland_thread->push_message(msg); -} - -void 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) { - SeatState *ss = (SeatState *)data; - if (!ss) { - return; - } - - ss->ime_text = String::utf8(text); - - // Convert cursor positions from UTF-8 to UTF-32 offset. - int32_t cursor_begin_utf32 = 0; - int32_t cursor_end_utf32 = 0; - for (int i = 0; i < ss->ime_text.length(); i++) { - uint32_t c = ss->ime_text[i]; - if (c <= 0x7f) { // 7 bits. - cursor_begin -= 1; - cursor_end -= 1; - } else if (c <= 0x7ff) { // 11 bits - cursor_begin -= 2; - cursor_end -= 2; - } else if (c <= 0xffff) { // 16 bits - cursor_begin -= 3; - cursor_end -= 3; - } else if (c <= 0x001fffff) { // 21 bits - cursor_begin -= 4; - cursor_end -= 4; - } else if (c <= 0x03ffffff) { // 26 bits - cursor_begin -= 5; - cursor_end -= 5; - } else if (c <= 0x7fffffff) { // 31 bits - cursor_begin -= 6; - cursor_end -= 6; - } else { - cursor_begin -= 1; - cursor_end -= 1; - } - if (cursor_begin == 0) { - cursor_begin_utf32 = i + 1; - } - if (cursor_end == 0) { - cursor_end_utf32 = i + 1; - } - if (cursor_begin <= 0 && cursor_end <= 0) { - break; - } - } - ss->ime_cursor = Vector2i(cursor_begin_utf32, cursor_end_utf32 - cursor_begin_utf32); -} - -void WaylandThread::_wp_text_input_on_commit_string(void *data, struct zwp_text_input_v3 *wp_text_input_v3, const char *text) { - SeatState *ss = (SeatState *)data; - if (!ss) { - return; - } - - ss->ime_text_commit = String::utf8(text); -} - -void 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) { - // Not implemented. -} - -void WaylandThread::_wp_text_input_on_done(void *data, struct zwp_text_input_v3 *wp_text_input_v3, uint32_t serial) { - SeatState *ss = (SeatState *)data; - if (!ss) { - return; - } - - if (!ss->ime_text_commit.is_empty()) { - Ref msg; - msg.instantiate(); - msg->text = ss->ime_text_commit; - ss->wayland_thread->push_message(msg); - } else if (!ss->ime_text.is_empty()) { - Ref msg; - msg.instantiate(); - msg->text = ss->ime_text; - msg->selection = ss->ime_cursor; - ss->wayland_thread->push_message(msg); - } - ss->ime_text = String(); - ss->ime_text_commit = String(); - ss->ime_cursor = Vector2i(); -} - -void WaylandThread::_xdg_activation_token_on_done(void *data, struct xdg_activation_token_v1 *xdg_activation_token, const char *token) { - WindowState *ws = (WindowState *)data; - ERR_FAIL_NULL(ws); - ERR_FAIL_NULL(ws->wayland_thread); - ERR_FAIL_NULL(ws->wl_surface); - - xdg_activation_v1_activate(ws->wayland_thread->registry.xdg_activation, token, ws->wl_surface); - xdg_activation_token_v1_destroy(xdg_activation_token); - - DEBUG_LOG_WAYLAND_THREAD(vformat("Received activation token and requested window activation.")); + seat_state_update_cursor(ss); } // NOTE: This must be started after a valid wl_display is loaded. diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h index 84e9bdc2dc08..91b1511c0733 100644 --- a/platform/linuxbsd/wayland/wayland_thread.h +++ b/platform/linuxbsd/wayland/wayland_thread.h @@ -81,6 +81,19 @@ #include "core/os/thread.h" #include "servers/display_server.h" +#define WAYLAND_THREAD_DEBUG_LOGS_ENABLED +#ifdef WAYLAND_THREAD_DEBUG_LOGS_ENABLED +#define DEBUG_LOG_WAYLAND_THREAD(...) print_verbose(__VA_ARGS__) +#else +#define DEBUG_LOG_WAYLAND_THREAD(...) +#endif + +// Fix the wl_array_for_each macro to work with C++. This is based on the +// original from `wayland-util.h` in the Wayland client library. +#undef wl_array_for_each +#define wl_array_for_each(pos, array) \ + for (pos = (decltype(pos))(array)->data; (const char *)pos < ((const char *)(array)->data + (array)->size); (pos)++) + class WaylandThread { public: // Messages used for exchanging information between Godot's and Wayland's thread.