From af009553988cfc2c78b9c3c70d8a39eae6bc51fd Mon Sep 17 00:00:00 2001 From: Riteo Date: Thu, 12 Sep 2024 12:13:45 +0200 Subject: [PATCH] wl_output, wl_seat, wl_surface, text_input xdg_activation --- platform/linuxbsd/wayland/SCsub | 5 + .../linuxbsd/wayland/thread/core/output.cpp | 98 ++++ .../linuxbsd/wayland/thread/core/seat.cpp | 119 +++++ .../linuxbsd/wayland/thread/core/surface.cpp | 77 ++++ .../linuxbsd/wayland/thread/text-input.cpp | 250 ++++++++++ .../wayland/thread/xdg-activation.cpp | 43 ++ platform/linuxbsd/wayland/wayland_thread.cpp | 432 ------------------ 7 files changed, 592 insertions(+), 432 deletions(-) create mode 100644 platform/linuxbsd/wayland/thread/core/output.cpp create mode 100644 platform/linuxbsd/wayland/thread/core/seat.cpp create mode 100644 platform/linuxbsd/wayland/thread/core/surface.cpp create mode 100644 platform/linuxbsd/wayland/thread/text-input.cpp create mode 100644 platform/linuxbsd/wayland/thread/xdg-activation.cpp diff --git a/platform/linuxbsd/wayland/SCsub b/platform/linuxbsd/wayland/SCsub index 82c667f96817..39c81ed34c24 100644 --- a/platform/linuxbsd/wayland/SCsub +++ b/platform/linuxbsd/wayland/SCsub @@ -195,11 +195,16 @@ source_files = [ "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", 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/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/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/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/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index c8eef49d63d1..776ff07b2b37 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -211,29 +211,6 @@ void WaylandThread::_update_scale(int p_scale) { } } -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); @@ -259,184 +236,6 @@ void WaylandThread::_frame_wl_callback_on_done(void *data, struct wl_callback *w } } -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::_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); @@ -450,237 +249,6 @@ void WaylandThread::_cursor_frame_callback_on_done(void *data, struct wl_callbac seat_state_update_cursor(ss); } -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(); -} - -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.")); -} - // NOTE: This must be started after a valid wl_display is loaded. void WaylandThread::_poll_events_thread(void *p_data) { ThreadData *data = (ThreadData *)p_data;