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.