From bbf65ae72fa2cb8158bf1a52b170e9ed6003597e Mon Sep 17 00:00:00 2001 From: Dery Almas Date: Sat, 15 Nov 2025 23:38:41 +0100 Subject: [PATCH] Wayland: Implement game embedding This patch introduces a new protocol proxy, which multiplxes Wayland clients into a single connection, allowing us to redirect calls (e.g. create toplevel -> create subsurface). Mixed with some state tracking and emulation, we can embed a full-featured client into the editor. --- COPYRIGHT.txt | 28 + editor/run/game_view_plugin.cpp | 37 +- platform/linuxbsd/wayland/SCsub | 16 + .../wayland/display_server_wayland.cpp | 91 + .../linuxbsd/wayland/display_server_wayland.h | 5 + .../wayland/godot-embedding-compositor.xml | 74 + .../linuxbsd/wayland/wayland_embedder.cpp | 2970 +++++++++++++++++ platform/linuxbsd/wayland/wayland_embedder.h | 630 ++++ platform/linuxbsd/wayland/wayland_thread.cpp | 240 +- platform/linuxbsd/wayland/wayland_thread.h | 56 + thirdparty/README.md | 4 + .../wayland-protocols/mesa/wayland-drm.xml | 189 ++ .../stable/linux-dmabuf/README | 5 + .../stable/linux-dmabuf/feedback.rst | 218 ++ .../stable/linux-dmabuf/linux-dmabuf-v1.xml | 585 ++++ .../staging/commit-timing/README | 4 + .../commit-timing/commit-timing-v1.xml | 124 + .../wayland-protocols/staging/fifo/README | 4 + .../staging/fifo/fifo-v1.xml | 143 + .../staging/linux-drm-syncobj/README | 4 + .../linux-drm-syncobj-v1.xml | 261 ++ .../staging/tearing-control/README | 4 + .../tearing-control/tearing-control-v1.xml | 123 + .../linux-explicit-synchronization/README | 5 + ...x-explicit-synchronization-unstable-v1.xml | 256 ++ 25 files changed, 6053 insertions(+), 23 deletions(-) create mode 100644 platform/linuxbsd/wayland/godot-embedding-compositor.xml create mode 100644 platform/linuxbsd/wayland/wayland_embedder.cpp create mode 100644 platform/linuxbsd/wayland/wayland_embedder.h create mode 100644 thirdparty/wayland-protocols/mesa/wayland-drm.xml create mode 100644 thirdparty/wayland-protocols/stable/linux-dmabuf/README create mode 100644 thirdparty/wayland-protocols/stable/linux-dmabuf/feedback.rst create mode 100644 thirdparty/wayland-protocols/stable/linux-dmabuf/linux-dmabuf-v1.xml create mode 100644 thirdparty/wayland-protocols/staging/commit-timing/README create mode 100644 thirdparty/wayland-protocols/staging/commit-timing/commit-timing-v1.xml create mode 100644 thirdparty/wayland-protocols/staging/fifo/README create mode 100644 thirdparty/wayland-protocols/staging/fifo/fifo-v1.xml create mode 100644 thirdparty/wayland-protocols/staging/linux-drm-syncobj/README create mode 100644 thirdparty/wayland-protocols/staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml create mode 100644 thirdparty/wayland-protocols/staging/tearing-control/README create mode 100644 thirdparty/wayland-protocols/staging/tearing-control/tearing-control-v1.xml create mode 100644 thirdparty/wayland-protocols/unstable/linux-explicit-synchronization/README create mode 100644 thirdparty/wayland-protocols/unstable/linux-explicit-synchronization/linux-explicit-synchronization-unstable-v1.xml diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 1ea7b09bdf1..1f2f02778b0 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -643,6 +643,12 @@ Copyright: 2008-2013, Kristian Høgsberg 2015, Red Hat Inc. License: Expat +Files: thirdparty/wayland-protocols/mesa/wayland-drm.xml +Comment: Mesa Wayland protocols +Copyright: 2008-2011, Kristian Høgsberg + 2010-2011, Intel Corporation +License: X11 + Files: thirdparty/wslay/* Comment: Wslay Copyright: 2011, 2012, 2015, Tatsuhiro Tsujikawa @@ -2305,6 +2311,28 @@ License: WOL THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. See https://dspguru.com/wide-open-license/ for more information. +License: X11 + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that\n the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + . + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + License: Zlib This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/editor/run/game_view_plugin.cpp b/editor/run/game_view_plugin.cpp index acdb4d157ba..5361dbdd1d3 100644 --- a/editor/run/game_view_plugin.cpp +++ b/editor/run/game_view_plugin.cpp @@ -668,7 +668,7 @@ GameView::EmbedAvailability GameView::_get_embed_available() { return EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE; } String display_driver = GLOBAL_GET("display/display_server/driver"); - if (display_driver == "headless" || display_driver == "wayland") { + if (display_driver == "headless") { return EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER; } @@ -714,11 +714,7 @@ void GameView::_update_ui() { } break; case EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED: - if (DisplayServer::get_singleton()->get_name() == "Wayland") { - state_label->set_text(TTRC("Game embedding not available on Wayland.\nWayland can be disabled in the Editor Settings (Run > Platforms > Linux/*BSD > Prefer Wayland).")); - } else { - state_label->set_text(TTRC("Game embedding not available on your OS.")); - } + state_label->set_text(TTRC("Game embedding not available on your OS.")); break; case EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER: state_label->set_text(vformat(TTR("Game embedding not available for the Display Server: '%s'.\nDisplay Server can be modified in the Project Settings (Display > Display Server > Driver)."), GLOBAL_GET("display/display_server/driver"))); @@ -991,6 +987,21 @@ void GameView::_update_arguments_for_instance(int p_idx, List &r_argumen // macOS requires the embedded display driver. remove_args.insert("--display-driver"); #endif + +#ifdef WAYLAND_ENABLED + // Wayland requires its display driver. + if (DisplayServer::get_singleton()->get_name() == "Wayland") { + remove_args.insert("--display-driver"); + } +#endif + +#ifdef X11_ENABLED + // X11 requires its display driver. + if (DisplayServer::get_singleton()->get_name() == "X11") { + remove_args.insert("--display-driver"); + } +#endif + while (E) { List::Element *N = E->next(); @@ -1020,6 +1031,20 @@ void GameView::_update_arguments_for_instance(int p_idx, List &r_argumen N = r_arguments.insert_after(N, "--embedded"); #endif +#ifdef WAYLAND_ENABLED + if (DisplayServer::get_singleton()->get_name() == "Wayland") { + N = r_arguments.insert_after(N, "--display-driver"); + N = r_arguments.insert_after(N, "wayland"); + } +#endif + +#ifdef X11_ENABLED + if (DisplayServer::get_singleton()->get_name() == "X11") { + N = r_arguments.insert_after(N, "--display-driver"); + N = r_arguments.insert_after(N, "x11"); + } +#endif + // Be sure to have the correct window size in the embedded_process control. _update_embed_window_size(); Rect2i rect = embedded_process->get_screen_embedded_window_rect(); diff --git a/platform/linuxbsd/wayland/SCsub b/platform/linuxbsd/wayland/SCsub index 2fe1c36ffab..7bdf03da399 100644 --- a/platform/linuxbsd/wayland/SCsub +++ b/platform/linuxbsd/wayland/SCsub @@ -97,6 +97,21 @@ generated_sources = [ generate_from_xml( "xdg_foreign_v2", "#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml" ), + generate_from_xml("linux_dmabuf_v1", "#thirdparty/wayland-protocols/stable/linux-dmabuf/linux-dmabuf-v1.xml"), + generate_from_xml( + "linux_explicit_synchronization_unstable_v1", + "#thirdparty/wayland-protocols/unstable/linux-explicit-synchronization/linux-explicit-synchronization-unstable-v1.xml", + ), + generate_from_xml("fifo_v1", "#thirdparty/wayland-protocols/staging/fifo/fifo-v1.xml"), + generate_from_xml("commit_timing_v1", "#thirdparty/wayland-protocols/staging/commit-timing/commit-timing-v1.xml"), + generate_from_xml( + "linux_drm_syncobj_v1", "#thirdparty/wayland-protocols/staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml" + ), + generate_from_xml( + "tearing_control_v1", "#thirdparty/wayland-protocols/staging/tearing-control/tearing-control-v1.xml" + ), + generate_from_xml("wayland-drm", "#thirdparty/wayland-protocols/mesa/wayland-drm.xml"), + generate_from_xml("godot_embedding_compositor", "godot-embedding-compositor.xml"), ] source_files = generated_sources + [ @@ -104,6 +119,7 @@ source_files = generated_sources + [ File("display_server_wayland.cpp"), File("key_mapping_xkb.cpp"), File("wayland_thread.cpp"), + File("wayland_embedder.cpp"), ] if env["use_sowrap"]: diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index ea339cf6177..ee6732eb027 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -195,6 +195,7 @@ bool DisplayServerWayland::has_feature(Feature p_feature) const { case FEATURE_WINDOW_DRAG: case FEATURE_CLIPBOARD_PRIMARY: case FEATURE_SUBWINDOWS: + case FEATURE_WINDOW_EMBEDDING: case FEATURE_SELF_FITTING_WINDOWS: { return true; } break; @@ -1298,6 +1299,8 @@ void DisplayServerWayland::window_move_to_foreground(DisplayServer::WindowID p_w } bool DisplayServerWayland::window_is_focused(WindowID p_window_id) const { + MutexLock mutex_lock(wayland_thread.mutex); + return wayland_thread.pointer_get_pointed_window_id() == p_window_id; } @@ -1505,6 +1508,94 @@ bool DisplayServerWayland::get_swap_cancel_ok() { return swap_cancel_ok; } +Error DisplayServerWayland::embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) { + MutexLock mutex_lock(wayland_thread.mutex); + + struct godot_embedding_compositor *ec = wayland_thread.get_embedding_compositor(); + ERR_FAIL_NULL_V_MSG(ec, ERR_BUG, "Missing embedded compositor interface"); + + struct WaylandThread::EmbeddingCompositorState *ecs = WaylandThread::godot_embedding_compositor_get_state(ec); + ERR_FAIL_NULL_V(ecs, ERR_BUG); + + if (!ecs->mapped_clients.has(p_pid)) { + return ERR_DOES_NOT_EXIST; + } + + struct godot_embedded_client *embedded_client = ecs->mapped_clients[p_pid]; + WaylandThread::EmbeddedClientState *client_data = (WaylandThread::EmbeddedClientState *)godot_embedded_client_get_user_data(embedded_client); + ERR_FAIL_NULL_V(client_data, ERR_BUG); + + if (p_grab_focus) { + godot_embedded_client_focus_window(embedded_client); + } + + if (p_visible) { + WaylandThread::WindowState *ws = wayland_thread.window_get_state(p_window); + ERR_FAIL_NULL_V(ws, ERR_BUG); + + struct xdg_toplevel *toplevel = ws->xdg_toplevel; + + if (toplevel == nullptr && ws->libdecor_frame) { + toplevel = libdecor_frame_get_xdg_toplevel(ws->libdecor_frame); + } + + ERR_FAIL_NULL_V(toplevel, ERR_CANT_CREATE); + + godot_embedded_client_set_embedded_window_parent(embedded_client, toplevel); + + double window_scale = WaylandThread::window_state_get_scale_factor(ws); + + Rect2i scaled_rect = p_rect; + scaled_rect.position = WaylandThread::scale_vector2i(scaled_rect.position, 1 / window_scale); + scaled_rect.size = WaylandThread::scale_vector2i(scaled_rect.size, 1 / window_scale); + + print_verbose(vformat("Scaling embedded rect down by %f from %s to %s.", window_scale, p_rect, scaled_rect)); + + godot_embedded_client_set_embedded_window_rect(embedded_client, scaled_rect.position.x, scaled_rect.position.y, scaled_rect.size.width, scaled_rect.size.height); + } else { + godot_embedded_client_set_embedded_window_parent(embedded_client, nullptr); + } + + return OK; +} + +Error DisplayServerWayland::request_close_embedded_process(OS::ProcessID p_pid) { + MutexLock mutex_lock(wayland_thread.mutex); + + struct godot_embedding_compositor *ec = wayland_thread.get_embedding_compositor(); + ERR_FAIL_NULL_V_MSG(ec, ERR_BUG, "Missing embedded compositor interface"); + + struct WaylandThread::EmbeddingCompositorState *ecs = WaylandThread::godot_embedding_compositor_get_state(ec); + ERR_FAIL_NULL_V(ecs, ERR_BUG); + + if (!ecs->mapped_clients.has(p_pid)) { + return ERR_DOES_NOT_EXIST; + } + + struct godot_embedded_client *embedded_client = ecs->mapped_clients[p_pid]; + WaylandThread::EmbeddedClientState *client_data = (WaylandThread::EmbeddedClientState *)godot_embedded_client_get_user_data(embedded_client); + ERR_FAIL_NULL_V(client_data, ERR_BUG); + + godot_embedded_client_embedded_window_request_close(embedded_client); + return OK; +} + +Error DisplayServerWayland::remove_embedded_process(OS::ProcessID p_pid) { + return request_close_embedded_process(p_pid); +} + +OS::ProcessID DisplayServerWayland::get_focused_process_id() { + MutexLock mutex_lock(wayland_thread.mutex); + + OS::ProcessID embedded_pid = wayland_thread.embedded_compositor_get_focused_pid(); + + if (embedded_pid < 0) { + return OS::get_singleton()->get_process_id(); + } + + return embedded_pid; +} + int DisplayServerWayland::keyboard_get_layout_count() const { MutexLock mutex_lock(wayland_thread.mutex); diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h index 703fb8c0417..2a632c145a2 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.h +++ b/platform/linuxbsd/wayland/display_server_wayland.h @@ -328,6 +328,11 @@ public: virtual bool get_swap_cancel_ok() override; + virtual Error embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) override; + virtual Error request_close_embedded_process(OS::ProcessID p_pid) override; + virtual Error remove_embedded_process(OS::ProcessID p_pid) override; + virtual OS::ProcessID get_focused_process_id() override; + virtual int keyboard_get_layout_count() const override; virtual int keyboard_get_current_layout() const override; virtual void keyboard_set_current_layout(int p_index) override; diff --git a/platform/linuxbsd/wayland/godot-embedding-compositor.xml b/platform/linuxbsd/wayland/godot-embedding-compositor.xml new file mode 100644 index 00000000000..8e9242ba196 --- /dev/null +++ b/platform/linuxbsd/wayland/godot-embedding-compositor.xml @@ -0,0 +1,74 @@ + + + + + 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. + + + + + + + + + + + + + + + Clients have only a single embedded window at a time, subject to change. + + + + + + + + + + + + + + + + + + + + + + + This instance is no longer valid. The compositor shall ignore any + further request except destroy and stop emitting events for this object. + After this event, the client can safely destroy this object. + + + + + + + + + + diff --git a/platform/linuxbsd/wayland/wayland_embedder.cpp b/platform/linuxbsd/wayland/wayland_embedder.cpp new file mode 100644 index 00000000000..ab155c68ac1 --- /dev/null +++ b/platform/linuxbsd/wayland/wayland_embedder.cpp @@ -0,0 +1,2970 @@ +/**************************************************************************/ +/* wayland_embedder.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_embedder.h" + +#ifdef WAYLAND_ENABLED + +#ifdef TOOLS_ENABLED + +#include + +#ifdef __FreeBSD__ +#include +#else +// Assume Linux. +#include +#endif + +#include "core/os/os.h" + +#include +#include +#include + +#define WAYLAND_EMBED_ID_MAX 1000 + +//#define WAYLAND_EMBED_DEBUG_LOGS_ENABLED +#ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED + +// Gotta flush as we're doing this mess from a thread without any +// synchronization. It's awful, I know, but the `print_*` utilities hang for +// some reason during editor startup and I need some quick and dirty debugging. +#define DEBUG_LOG_WAYLAND_EMBED(...) \ + if (1) { \ + printf("[PROXY] %s\n", vformat(__VA_ARGS__).utf8().ptr()); \ + fflush(stdout); \ + } else \ + ((void)0) + +#else +#define DEBUG_LOG_WAYLAND_EMBED(...) +#endif + +// Wayland messages are structured with 32-bit words. +#define WL_WORD_SIZE (sizeof(uint32_t)) + +// Event opcodes. Request opcodes are defined in the generated client headers. +// We could generate server headers but they would clash (without modifications) +// and we use just a few constants anyways. + +#define WL_DISPLAY_ERROR 0 +#define WL_DISPLAY_DELETE_ID 1 + +#define WL_REGISTRY_GLOBAL 0 +#define WL_REGISTRY_GLOBAL_REMOVE 1 + +#define WL_CALLBACK_DONE 0 + +#define WL_KEYBOARD_ENTER 1 +#define WL_KEYBOARD_LEAVE 2 +#define WL_KEYBOARD_KEY 3 + +#define WL_POINTER_ENTER 0 +#define WL_POINTER_LEAVE 1 +#define WL_POINTER_BUTTON 3 + +#define WL_SHM_FORMAT 0 + +#define WL_DRM_DEVICE 0 +#define WL_DRM_FORMAT 1 +#define WL_DRM_AUTHENTICATED 2 +#define WL_DRM_CAPABILITIES 3 + +#define XDG_POPUP_CONFIGURE 0 + +size_t WaylandEmbedder::wl_array_word_offset(uint32_t p_size) { + uint32_t pad = (WL_WORD_SIZE - (p_size % WL_WORD_SIZE)) % WL_WORD_SIZE; + return (p_size + pad) / WL_WORD_SIZE; +} + +const struct wl_interface *WaylandEmbedder::wl_interface_from_string(const char *name, size_t size) { + for (size_t i = 0; i < (sizeof interfaces / sizeof *interfaces); ++i) { + if (strncmp(name, interfaces[i]->name, size) == 0) { + return interfaces[i]; + } + } + + return nullptr; +} + +int WaylandEmbedder::wl_interface_get_destructor_opcode(const struct wl_interface *p_iface, uint32_t version) { + ERR_FAIL_NULL_V(p_iface, -1); + + // FIXME: Figure out how to extract the destructor from the XML files. This + // value is not currently exposed by wayland-scanner. + for (int i = 0; i < p_iface->method_count; ++i) { + const struct wl_message &m = p_iface->methods[i]; + uint32_t destructor_version = String::to_int(m.signature); + if (destructor_version <= version && (strcmp(m.name, "destroy") == 0 || strcmp(m.name, "release") == 0)) { + return i; + } + } + + return -1; +} + +struct WaylandEmbedder::WaylandObject *WaylandEmbedder::get_object(uint32_t p_global_id) { + if (p_global_id == 0) { + return nullptr; + } + + // Server-allocated stuff starts at 0xff000000. + bool is_server = p_global_id & 0xff000000; + if (is_server) { + p_global_id &= ~(0xff000000); + } + +#ifdef DEV_ENABLED + if (p_global_id >= WAYLAND_EMBED_ID_MAX) { + // Oh no. Time for debug info! + +#ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED + for (uint32_t id = 1; id < objects.reserved_size(); ++id) { + WaylandObject &object = objects[id]; + DEBUG_LOG_WAYLAND_EMBED(vformat(" - g0x%x (#%d): %s version %d, data 0x%x", id, id, object.interface->name, object.version, (uintptr_t)object.data)); + } +#endif // WAYLAND_EMBED_DEBUG_LOGS_ENABLED + + CRASH_NOW_MSG(vformat("Tried to access ID bigger than debug cap (%d > %d).", p_global_id, WAYLAND_EMBED_ID_MAX)); + } +#endif // DEV_ENABLED + + if (is_server) { + if (server_objects.size() <= p_global_id) { + return nullptr; + } + + return &server_objects[p_global_id]; + } else { + if (objects.reserved_size() <= p_global_id) { + return nullptr; + } + + return &objects[p_global_id]; + } +} + +Error WaylandEmbedder::delete_object(uint32_t p_global_id) { + WaylandObject *object = get_object(p_global_id); + ERR_FAIL_NULL_V(object, ERR_DOES_NOT_EXIST); + + if (object->shared) { + ERR_FAIL_V_MSG(FAILED, vformat("Tried to delete shared object g0x%x.", p_global_id)); + } + + DEBUG_LOG_WAYLAND_EMBED(vformat("Deleting object %s g0x%x", object->interface ? object->interface->name : "UNKNOWN", p_global_id)); + + if (object->data) { + memdelete(object->data); + object->data = nullptr; + } + + bool is_server = p_global_id & 0xff000000; + if (is_server) { + server_objects[p_global_id & ~(0xff000000)] = WaylandObject(); + } else { + objects.free(p_global_id); + } + + registry_globals_names.erase(p_global_id); + + return OK; +} + +uint32_t WaylandEmbedder::Client::allocate_server_id() { + uint32_t new_id = INVALID_ID; + + if (free_server_ids.size() > 0) { + int new_size = free_server_ids.size() - 1; + new_id = free_server_ids[new_size] | 0xff000000; + free_server_ids.resize_uninitialized(new_size); + } else { + new_id = allocated_server_ids | 0xff000000; + + ++allocated_server_ids; +#ifdef DEV_ENABLED + CRASH_COND_MSG(allocated_server_ids > WAYLAND_EMBED_ID_MAX, "Max server ID reached. This might indicate a leak."); +#endif // DEV_ENABLED + } + + DEBUG_LOG_WAYLAND_EMBED(vformat("Allocated server-side id 0x%x.", new_id)); + + return new_id; +} + +struct WaylandEmbedder::WaylandObject *WaylandEmbedder::Client::get_object(uint32_t p_local_id) { + if (p_local_id == INVALID_ID) { + return nullptr; + } + + if (global_instances.has(p_local_id)) { + return &global_instances[p_local_id]; + } + + if (fake_objects.has(p_local_id)) { + return &fake_objects[p_local_id]; + } + + if (!global_ids.has(p_local_id)) { + return nullptr; + } + + ERR_FAIL_NULL_V(embedder, nullptr); + return embedder->get_object(get_global_id(p_local_id)); +} + +Error WaylandEmbedder::Client::bind_global_id(uint32_t p_global_id, uint32_t p_local_id) { + ERR_FAIL_COND_V(local_ids.has(p_global_id), ERR_ALREADY_EXISTS); + ERR_FAIL_COND_V(global_ids.has(p_local_id), ERR_ALREADY_EXISTS); + + GlobalIdInfo gid_info; + gid_info.id = p_global_id; + DEBUG_LOG_WAYLAND_EMBED(vformat("Pushing g0x%x in the global id history", p_global_id)); + gid_info.history_elem = global_id_history.push_back(p_global_id); + global_ids[p_local_id] = gid_info; + + local_ids[p_global_id] = p_local_id; + + return OK; +} + +Error WaylandEmbedder::Client::delete_object(uint32_t p_local_id) { + if (fake_objects.has(p_local_id)) { +#ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED + WaylandObject *object = &fake_objects[p_local_id]; + DEBUG_LOG_WAYLAND_EMBED(vformat("Deleting fake object %s l0x%x", object->interface ? object->interface->name : "UNKNOWN", p_local_id)); +#endif + + if (!(p_local_id & 0xff000000)) { + // wl_display::delete_id + send_wayland_message(socket, DISPLAY_ID, 1, { p_local_id }); + } + + fake_objects.erase(p_local_id); + + // We can skip everything else below, as fake objects don't have a global id. + return OK; + } + + ERR_FAIL_COND_V(!global_ids.has(p_local_id), ERR_DOES_NOT_EXIST); + GlobalIdInfo gid_info = global_ids[p_local_id]; + uint32_t global_id = gid_info.id; + + DEBUG_LOG_WAYLAND_EMBED(vformat("Erasing g0x%x from the global id history", global_id)); + global_id_history.erase(gid_info.history_elem); + + if (global_instances.has(p_local_id)) { +#ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED + WaylandObject *object = &global_instances[p_local_id]; + DEBUG_LOG_WAYLAND_EMBED(vformat("Deleting global instance %s l0x%x", object->interface ? object->interface->name : "UNKNOWN", p_local_id)); +#endif + + // wl_display::delete_id + send_wayland_message(socket, DISPLAY_ID, 1, { p_local_id }); + + // We don't want to delete the global object tied to this instance, so we'll only get rid of the local stuff. + global_instances.erase(p_local_id); + global_ids.erase(p_local_id); + + if (global_id != INVALID_ID) { + local_ids.erase(global_id); + } + + // We're done here. + return OK; + } + + if (wl_registry_instances.has(p_local_id)) { + wl_registry_instances.erase(p_local_id); + } + + WaylandObject *object = embedder->get_object(global_id); + ERR_FAIL_NULL_V(object, ERR_DOES_NOT_EXIST); + + ERR_FAIL_COND_V_MSG(object->shared, ERR_INVALID_PARAMETER, vformat("Tried to delete shared object g0x%x.", global_id)); + + global_ids.erase(p_local_id); + local_ids.erase(global_id); + + if (p_local_id & 0xff000000) { + free_server_ids.push_back(p_local_id & ~(0xff000000)); + } + + uint32_t *global_name = embedder->registry_globals_names.getptr(global_id); + if (global_name) { + { + RegistryGlobalInfo &info = embedder->registry_globals[*global_name]; + ERR_FAIL_COND_V_MSG(info.instance_counter == 0, ERR_BUG, "Instance counter inconsistency."); + --info.instance_counter; + + if (info.destroyed && info.instance_counter == 0) { + embedder->registry_globals.erase(*global_name); + } + } + + registry_globals_instances[*global_name].erase(p_local_id); + } + + return embedder->delete_object(global_id); +} + +// Returns INVALID_ID if the creation fails. In that case, the user can assume +// that the client got kicked out. +uint32_t WaylandEmbedder::Client::new_object(uint32_t p_local_id, const struct wl_interface *p_interface, int p_version, WaylandObjectData *p_data) { + if (embedder == nullptr) { + socket_error(socket, p_local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, "No embedder set."); + ERR_FAIL_V(INVALID_ID); + } + + if (get_object(p_local_id) != nullptr) { + socket_error(socket, p_local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, vformat("Tried to create %s l0x%x but it already exists as %s", p_interface->name, p_local_id, get_object(p_local_id)->interface->name)); + ERR_FAIL_V(INVALID_ID); + } + + uint32_t new_global_id = embedder->new_object(p_interface, p_version, p_data); + + bind_global_id(new_global_id, p_local_id); + + return new_global_id; +} + +uint32_t WaylandEmbedder::Client::new_server_object(uint32_t p_global_id, const struct wl_interface *p_interface, int p_version, WaylandObjectData *p_data) { + if (embedder == nullptr) { + socket_error(socket, get_local_id(p_global_id), WL_DISPLAY_ERROR_IMPLEMENTATION, "No embedder set."); + ERR_FAIL_V(INVALID_ID); + } + + uint32_t new_local_id = allocate_server_id(); + + embedder->new_server_object(p_global_id, p_interface, p_version, p_data); + + bind_global_id(p_global_id, new_local_id); + + return new_local_id; +} + +WaylandEmbedder::WaylandObject *WaylandEmbedder::Client::new_fake_object(uint32_t p_local_id, const struct wl_interface *p_interface, int p_version, WaylandObjectData *p_data) { + if (embedder == nullptr) { + socket_error(socket, p_local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, "No embedder set."); + ERR_FAIL_V(nullptr); + } + + if (get_object(p_local_id) != nullptr) { + socket_error(socket, p_local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, vformat("Object l0x%x already exists", p_local_id)); + ERR_FAIL_V(nullptr); + } + + WaylandObject &new_object = fake_objects[p_local_id]; + new_object.interface = p_interface; + new_object.version = p_version; + new_object.data = p_data; + + return &new_object; +} + +WaylandEmbedder::WaylandObject *WaylandEmbedder::Client::new_global_instance(uint32_t p_local_id, uint32_t p_global_id, const struct wl_interface *p_interface, int p_version, WaylandObjectData *p_data) { + if (embedder == nullptr) { + socket_error(socket, p_local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, "No embedder set."); + ERR_FAIL_V(nullptr); + } + + if (get_object(p_local_id) != nullptr) { + socket_error(socket, p_local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, vformat("Object l0x%x already exists", p_local_id)); + ERR_FAIL_V(nullptr); + } + + WaylandObject &new_object = global_instances[p_local_id]; + new_object.interface = p_interface; + new_object.version = p_version; + new_object.data = p_data; + + // FIXME: Track each instance properly. Global instances (the compatibility + // mechanism) are particular as they're the only case where a global ID might + // map to multiple local objects. In that case we need to mirror each event + // which passes a registry object as an argument for each instance. + GlobalIdInfo gid_info; + gid_info.id = p_global_id; + gid_info.history_elem = global_id_history.push_back(p_global_id); + global_ids[p_local_id] = gid_info; + + // NOTE: Normally, for each client, there's a single local object per global + // object, but global instances break this expectation. This is technically + // wrong but should work fine, as we have special logic whenever needed. + // + // TODO: it might be nice to enforce that this table is never looked up for + // global instances or even just log attempts. + local_ids[p_global_id] = p_local_id; + + return &new_object; +} + +Error WaylandEmbedder::Client::send_wl_drm_state(uint32_t p_id, WaylandDrmGlobalData *p_state) { + ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + + if (p_state->device.is_empty()) { + // Not yet initialized. + return OK; + } + + LocalVector args; + args.push_back(wl_arg_string(p_state->device.utf8().get_data())); + send_wayland_event(socket, p_id, wl_drm_interface, WL_DRM_DEVICE, args); + + for (uint32_t format : p_state->formats) { + Error err = send_wayland_message(socket, p_id, WL_DRM_FORMAT, { format }); + ERR_FAIL_COND_V(err != OK, err); + } + + if (p_state->authenticated) { + Error err = send_wayland_message(socket, p_id, WL_DRM_AUTHENTICATED, {}); + ERR_FAIL_COND_V(err != OK, err); + } + + Error err = send_wayland_message(socket, p_id, WL_DRM_CAPABILITIES, { p_state->capabilities }); + ERR_FAIL_COND_V(err != OK, err); + + return OK; +} + +void WaylandEmbedder::cleanup_socket(int p_socket) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Cleaning up socket %d.", p_socket)); + + close(p_socket); + + for (size_t i = 0; i < pollfds.size(); ++i) { + if (pollfds[i].fd == p_socket) { + pollfds.remove_at_unordered(i); + break; + } + } + + ERR_FAIL_COND(!clients.has(p_socket)); + + Client &client = clients[p_socket]; + + for (KeyValue &pair : client.fake_objects) { + WaylandObject &object = pair.value; + + if (object.interface == &xdg_toplevel_interface) { + XdgToplevelData *data = (XdgToplevelData *)object.data; + CRASH_COND(data == nullptr); + + if (data->wl_subsurface_id != INVALID_ID) { + // wl_subsurface::destroy() - xdg_toplevels are mapped to subsurfaces. + send_wayland_message(compositor_socket, data->wl_subsurface_id, 0, {}); + } + + if (!data->xdg_surface_handle.get()) { + continue; + } + + XdgSurfaceData *xdg_surf_data = (XdgSurfaceData *)data->xdg_surface_handle.get()->data; + if (xdg_surf_data == nullptr) { + continue; + } + + if (!data->parent_handle.get()) { + continue; + } + + XdgToplevelData *parent_data = (XdgToplevelData *)data->parent_handle.get()->data; + if (parent_data == nullptr) { + continue; + } + + if (!parent_data->xdg_surface_handle.get()) { + continue; + } + + XdgSurfaceData *parent_xdg_surf_data = (XdgSurfaceData *)parent_data->xdg_surface_handle.get()->data; + if (parent_xdg_surf_data == nullptr) { + continue; + } + + for (uint32_t wl_seat_name : wl_seat_names) { + WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)registry_globals[wl_seat_name].data; + if (global_seat_data == nullptr) { + continue; + } + + if (global_seat_data->focused_surface_id == xdg_surf_data->wl_surface_id) { + seat_name_leave_surface(wl_seat_name, xdg_surf_data->wl_surface_id); + seat_name_enter_surface(wl_seat_name, parent_xdg_surf_data->wl_surface_id); + } + } + } + } + + for (List::Element *E = client.global_id_history.back(); E;) { + uint32_t global_id = E->get(); + E = E->prev(); + + WaylandObject *object = get_object(global_id); + if (object == nullptr) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Skipping deletability check of object g0x%x as it's null.", global_id)); + continue; + } + + if (object->interface == nullptr) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Skipping deletability check of object g0x%x as it's invalid.", global_id)); + continue; + } + + DEBUG_LOG_WAYLAND_EMBED(vformat("Checking deletability of %s#g0x%x version %s", object->interface->name, global_id, object->version)); + + if (object->shared) { + DEBUG_LOG_WAYLAND_EMBED("Shared, skipping."); + continue; + } + + if (object->interface == &wl_callback_interface) { + // Those things self-destruct. + DEBUG_LOG_WAYLAND_EMBED("wl_callback self destructs."); + continue; + } + + if (object->destroyed) { + DEBUG_LOG_WAYLAND_EMBED("Already destroyed, skipping."); + continue; + } + + int destructor = wl_interface_get_destructor_opcode(object->interface, object->version); + if (destructor >= 0) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Destroying %s#g0x%x", object->interface->name, global_id)); + + if (object->interface == &wl_surface_interface) { + for (uint32_t wl_seat_name : wl_seat_names) { + WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)registry_globals[wl_seat_name].data; + if (global_seat_data) { + if (global_seat_data->pointed_surface_id == global_id) { + global_seat_data->pointed_surface_id = INVALID_ID; + } + + if (global_seat_data->focused_surface_id == global_id) { + global_seat_data->focused_surface_id = INVALID_ID; + } + } + } + } + + send_wayland_message(compositor_socket, global_id, destructor, {}); + object->destroyed = true; + + if (global_id & 0xff000000) { + delete_object(global_id); + object = nullptr; + } + } + + if (object && !object->destroyed) { + ERR_PRINT(vformat("Unreferenced object %s g0x%x (leak!)", object->interface->name, global_id)); + } + } + + uint32_t eclient_id = client.embedded_client_id; + + clients.erase(client.socket); + + WaylandObject *eclient = main_client->get_object(eclient_id); + + if (eclient) { + EmbeddedClientData *eclient_data = (EmbeddedClientData *)eclient->data; + ERR_FAIL_NULL(eclient_data); + + if (!eclient_data->disconnected) { + // godot_embedded_client::disconnected + send_wayland_message(main_client->socket, eclient_id, 0, {}); + } + + eclient_data->disconnected = true; + } +} + +void WaylandEmbedder::socket_error(int p_socket, uint32_t p_object_id, uint32_t p_code, const String &p_message) { + const char *err_name = "unknown"; + switch (p_code) { + case WL_DISPLAY_ERROR_INVALID_OBJECT: { + err_name = "invalid_object"; + } break; + + case WL_DISPLAY_ERROR_INVALID_METHOD: { + err_name = "invalid_method"; + } break; + + case WL_DISPLAY_ERROR_NO_MEMORY: { + err_name = "no_memory"; + } break; + + case WL_DISPLAY_ERROR_IMPLEMENTATION: { + err_name = "implementation"; + } break; + } + + ERR_PRINT(vformat("Socket %d %s error: %s", p_socket, err_name, p_message)); + + LocalVector args; + args.push_back(wl_arg_object(p_object_id)); + args.push_back(wl_arg_uint(p_code)); + args.push_back(wl_arg_string(vformat("[Godot Embedder] %s", p_message).utf8().get_data())); + + send_wayland_event(p_socket, DISPLAY_ID, wl_display_interface, WL_DISPLAY_ERROR, args); + + // So, here's the deal: from some extensive research I did, there are + // absolutely zero safeguards for ensuring that the error message ends to the + // client. It's absolutely tiny and takes _nothing_ to get there (less than + // 4µs with a debug build on my machine), but still enough to get truncated in + // the distance between `send_wayland_event` and `close`. + // + // Because of this we're going to give the client some slack: we're going to + // wait for its socket to close (or whatever) or 1s, whichever happens first. + // + // Hopefully it's good enough for <1000 bytes :P + struct pollfd pollfd = {}; + pollfd.fd = p_socket; + + int ret = poll(&pollfd, 1, 1'000); + if (ret == 0) { + ERR_PRINT("Client timeout while disconnecting."); + } + if (ret < 0) { + ERR_PRINT(vformat("Client error while disconnecting: %s", strerror(errno))); + } + + close(p_socket); +} + +void WaylandEmbedder::poll_sockets() { + if (poll(pollfds.ptr(), pollfds.size(), -1) == -1) { + CRASH_NOW_MSG(vformat("poll() failed, errno %d.", errno)); + } + + // First handle everything but the listening socket (which is always the first + // element), so that we can cleanup closed sockets before accidentally reusing + // them (and breaking everything). + for (size_t i = 1; i < pollfds.size(); ++i) { + handle_fd(pollfds[i].fd, pollfds[i].revents); + } + + handle_fd(pollfds[0].fd, pollfds[0].revents); +} + +Error WaylandEmbedder::send_raw_message(int p_socket, std::initializer_list p_vecs, const LocalVector &p_fds) { + struct msghdr msg = {}; + msg.msg_iov = (struct iovec *)p_vecs.begin(); + msg.msg_iovlen = p_vecs.size(); + + if (!p_fds.is_empty()) { + size_t data_size = p_fds.size() * sizeof(int); + + msg.msg_control = Memory::alloc_aligned_static(CMSG_SPACE(data_size), CMSG_ALIGN(1)); + msg.msg_controllen = CMSG_SPACE(data_size); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(data_size); + + // NOTE: According to the linux man page cmsg(5), we shall not access the + // pointer returned CMSG_DATA directly, due to alignment concerns. We should + // copy data from a suitably aligned object instead. + memcpy(CMSG_DATA(cmsg), p_fds.ptr(), data_size); + } + +#ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED + printf("[PROXY] Sending: "); + + for (const struct iovec &vec : p_vecs) { + for (size_t i = 0; i < vec.iov_len; ++i) { + printf("%.2x", ((const uint8_t *)vec.iov_base)[i]); + } + } + printf("\n"); +#endif + + sendmsg(p_socket, &msg, MSG_NOSIGNAL); + + if (msg.msg_control) { + Memory::free_aligned_static(msg.msg_control); + } + + return OK; +} + +Error WaylandEmbedder::send_wayland_message(int p_socket, uint32_t p_id, uint32_t p_opcode, const uint32_t *p_args, const size_t p_args_words) { + ERR_FAIL_COND_V(p_socket < 0, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_id == INVALID_ID, ERR_INVALID_PARAMETER); + + uint32_t args_size = p_args_words * sizeof *p_args; + + // Header is always 8 bytes long. + uint32_t total_size = 8 + (args_size); + + uint32_t header[2] = { p_id, (total_size << 16) + p_opcode }; + + struct iovec vecs[2] = { + { header, 8 }, + // According to the sendmsg manual, these buffers should never be written to, + // so this cast should be safe. + { (void *)p_args, args_size }, + }; + + struct msghdr msg = {}; + msg.msg_iov = vecs; + msg.msg_iovlen = std_size(vecs); + +#ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED + printf("[PROXY] Sending: "); + + for (struct iovec &vec : vecs) { + for (size_t i = 0; i < vec.iov_len; ++i) { + printf("%.2x", ((const uint8_t *)vec.iov_base)[i]); + } + } + printf("\n"); +#endif + + if (sendmsg(p_socket, &msg, MSG_NOSIGNAL) < 0) { + return FAILED; + } + + return OK; +} + +Error WaylandEmbedder::send_wayland_message(ProxyDirection p_direction, int p_socket, uint32_t p_id, const struct wl_interface &p_interface, uint32_t p_opcode, const LocalVector &p_args) { + ERR_FAIL_COND_V(p_direction == ProxyDirection::CLIENT && p_opcode >= (uint32_t)p_interface.event_count, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_direction == ProxyDirection::COMPOSITOR && p_opcode >= (uint32_t)p_interface.method_count, ERR_INVALID_PARAMETER); + + const struct wl_message &msg = p_direction == ProxyDirection::CLIENT ? p_interface.events[p_opcode] : p_interface.methods[p_opcode]; + + LocalVector arg_buf; + + size_t arg_idx = 0; + for (size_t sig_idx = 0; sig_idx < strlen(msg.signature); ++sig_idx) { + if (arg_idx >= p_args.size()) { + String err_msg = vformat("Not enough arguments for r0x%d %s.%s(%s) (only got %d)", p_id, p_interface.name, msg.name, msg.signature, p_args.size()); + ERR_FAIL_COND_V_MSG(arg_idx >= p_args.size(), ERR_INVALID_PARAMETER, err_msg); + } + + char sym = msg.signature[sig_idx]; + if (sym >= '0' && sym <= '?') { + // We don't care about version notices and nullability symbols. We can skip + // those. + continue; + } + + const union wl_argument &arg = p_args[arg_idx]; + + switch (sym) { + case 'i': { + arg_buf.push_back((uint32_t)arg.i); + } break; + + case 'u': { + arg_buf.push_back(arg.u); + } break; + + case 'f': { + arg_buf.push_back((uint32_t)arg.f); + } break; + + case 'o': { + // We're encoding object arguments as uints because I don't think we can + // reuse the whole opaque struct thing. + arg_buf.push_back(arg.u); + } break; + + case 'n': { + arg_buf.push_back(arg.n); + } break; + + case 's': { + const char *str = p_args[arg_idx].s; + // Wayland requires the string length to include the null terminator. + uint32_t str_len = strlen(str) + 1; + + arg_buf.push_back(str_len); + + size_t data_begin_idx = arg_buf.size(); + + uint32_t str_words = wl_array_word_offset(str_len); + + arg_buf.resize(arg_buf.size() + str_words); + strcpy((char *)(arg_buf.ptr() + data_begin_idx), str); + } break; + + case 'a': { + const wl_array *arr = p_args[arg_idx].a; + + arg_buf.push_back(arr->size); + + size_t data_begin_idx = arg_buf.size(); + + uint32_t words = wl_array_word_offset(arr->size); + + arg_buf.resize(arg_buf.size() + words); + memcpy(arg_buf.ptr() + data_begin_idx, arr->data, arr->size); + } break; + + // FDs (h) are encoded out-of-band. + } + + ++arg_idx; + } + + send_wayland_message(p_socket, p_id, p_opcode, arg_buf.ptr(), arg_buf.size()); + + return OK; +} + +uint32_t WaylandEmbedder::new_object(const struct wl_interface *p_interface, int p_version, WaylandObjectData *p_data) { + uint32_t new_global_id = allocate_global_id(); + + DEBUG_LOG_WAYLAND_EMBED(vformat("New object g0x%x %s", new_global_id, p_interface->name)); + + WaylandObject *new_object = get_object(new_global_id); + new_object->interface = p_interface; + new_object->version = p_version; + new_object->data = p_data; + + return new_global_id; +} + +WaylandEmbedder::WaylandObject *WaylandEmbedder::new_server_object(uint32_t p_global_id, const struct wl_interface *p_interface, int p_version, WaylandObjectData *p_data) { + // The max ID will never increment more than one at a time, due to the + // packed nature of IDs. libwayland already does similar assertions so it + // just makes sense to double-check to avoid messing memory up or + // allocating a huge buffer for nothing. + uint32_t stripped_id = p_global_id & ~(0xff000000); + + ERR_FAIL_COND_V_MSG(stripped_id > server_objects.size(), nullptr, "Invalid new server id requested."); + ERR_FAIL_COND_V_MSG(get_object(p_global_id) && get_object(p_global_id)->interface, nullptr, vformat("Tried to create %s g0x%x but it already exists as %s.", p_interface->name, p_global_id, get_object(p_global_id)->interface->name)); + + if (stripped_id == server_objects.size()) { + server_objects.resize(server_objects.size() + 1); + } + + DEBUG_LOG_WAYLAND_EMBED(vformat("New server object %s g0x%x", p_interface->name, p_global_id)); + + WaylandObject *new_object = get_object(p_global_id); + new_object->interface = p_interface; + new_object->version = p_version; + new_object->data = p_data; + + return new_object; +} + +void WaylandEmbedder::sync() { + CRASH_COND_MSG(sync_callback_id, "Sync already in progress."); + + sync_callback_id = allocate_global_id(); + get_object(sync_callback_id)->interface = &wl_callback_interface; + get_object(sync_callback_id)->version = 1; + send_wayland_message(compositor_socket, DISPLAY_ID, 0, { sync_callback_id }); + + DEBUG_LOG_WAYLAND_EMBED("Synchronizing"); + + while (true) { + poll_sockets(); + + if (!sync_callback_id) { + // Obj got deleted - sync is done. + return; + } + } +} + +// Returns the gid for the newly bound object, or an existing shared object if +// necessary. +uint32_t WaylandEmbedder::wl_registry_bind(uint32_t p_registry_id, uint32_t p_name, int p_version) { + RegistryGlobalInfo &info = registry_globals[p_name]; + + uint32_t id = INVALID_ID; + + if (wl_interface_get_destructor_opcode(info.interface, p_version) < 0) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Binding instanced global %s %d", info.interface->name, p_version)); + + // Reusable object. + if (info.reusable_objects.has(p_version) && info.reusable_objects[p_version] != INVALID_ID) { + DEBUG_LOG_WAYLAND_EMBED("Already bound."); + return info.reusable_objects[p_version]; + } + + id = new_object(info.interface, p_version); + ERR_FAIL_COND_V(id == INVALID_ID, INVALID_ID); + + info.reusable_objects[p_version] = id; + get_object(id)->shared = true; + } else { + DEBUG_LOG_WAYLAND_EMBED(vformat("Binding global %s as g0x%x version %d", info.interface->name, id, p_version)); + id = new_object(info.interface, p_version); + } + + ERR_FAIL_COND_V(id == INVALID_ID, INVALID_ID); + + registry_globals_names[id] = p_name; + + LocalVector args; + args.push_back(wl_arg_uint(info.compositor_name)); + args.push_back(wl_arg_string(info.interface->name)); + args.push_back(wl_arg_int(p_version)); + args.push_back(wl_arg_new_id(id)); + + Error err = send_wayland_method(compositor_socket, p_registry_id, wl_registry_interface, WL_REGISTRY_BIND, args); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_ID, "Error while sending bind request."); + + return id; +} + +void WaylandEmbedder::seat_name_enter_surface(uint32_t p_seat_name, uint32_t p_wl_surface_id) { + WaylandSurfaceData *surf_data = (WaylandSurfaceData *)get_object(p_wl_surface_id)->data; + CRASH_COND(surf_data == nullptr); + + Client *client = surf_data->client; + CRASH_COND(client == nullptr); + + if (!client->local_ids.has(p_wl_surface_id)) { + DEBUG_LOG_WAYLAND_EMBED("Called seat_name_enter_surface with an unknown surface"); + return; + } + + uint32_t local_surface_id = client->get_local_id(p_wl_surface_id); + + DEBUG_LOG_WAYLAND_EMBED(vformat("KB: Entering surface g0x%x", p_wl_surface_id)); + + for (uint32_t local_seat_id : client->registry_globals_instances[p_seat_name]) { + WaylandSeatInstanceData *seat_data = (WaylandSeatInstanceData *)client->get_object(local_seat_id)->data; + CRASH_COND(seat_data == nullptr); + + uint32_t local_keyboard_id = client->get_local_id(seat_data->wl_keyboard_id); + + if (local_keyboard_id != INVALID_ID) { + // TODO: track keys. Not super important at the time of writing, since we + // don't use that in the engine, although we should. + + // wl_keyboard::enter(serial, surface, keys) - keys will be empty for now + send_wayland_message(client->socket, local_keyboard_id, 1, { serial_counter++, local_surface_id, 0 }); + } + } + + if (client->socket != main_client->socket) { + // godot_embedded_client::window_focus_in + send_wayland_message(main_client->socket, client->embedded_client_id, 2, {}); + } +} + +void WaylandEmbedder::seat_name_leave_surface(uint32_t p_seat_name, uint32_t p_wl_surface_id) { + WaylandSurfaceData *surf_data = (WaylandSurfaceData *)get_object(p_wl_surface_id)->data; + CRASH_COND(surf_data == nullptr); + + Client *client = surf_data->client; + CRASH_COND(client == nullptr); + + if (!client->local_ids.has(p_wl_surface_id)) { + DEBUG_LOG_WAYLAND_EMBED("Called seat_name_leave_surface with an unknown surface!"); + return; + } + + uint32_t local_surface_id = client->get_local_id(p_wl_surface_id); + + DEBUG_LOG_WAYLAND_EMBED(vformat("KB: Leaving surface g0x%x", p_wl_surface_id)); + + for (uint32_t local_seat_id : client->registry_globals_instances[p_seat_name]) { + WaylandSeatInstanceData *seat_data = (WaylandSeatInstanceData *)client->get_object(local_seat_id)->data; + CRASH_COND(seat_data == nullptr); + + uint32_t local_keyboard_id = client->get_local_id(seat_data->wl_keyboard_id); + + if (local_keyboard_id != INVALID_ID) { + // wl_keyboard::enter(serial, surface, keys) - keys will be empty for now + send_wayland_message(client->socket, local_keyboard_id, 2, { serial_counter++, local_surface_id }); + } + } + + if (client != main_client) { + // godot_embedded_client::window_focus_out + send_wayland_message(main_client->socket, client->embedded_client_id, 3, {}); + } +} + +int WaylandEmbedder::allocate_global_id() { + uint32_t id = INVALID_ID; + objects.request(id); + objects[id] = WaylandObject(); + + DEBUG_LOG_WAYLAND_EMBED(vformat("Allocated new global id g0x%x", id)); + +#ifdef DEV_ENABLED + if (id > WAYLAND_EMBED_ID_MAX) { + // Oh no. Time for debug info! + +#ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED + for (uint32_t i = 1; i < objects.reserved_size(); ++i) { + WaylandObject &object = objects[id]; + DEBUG_LOG_WAYLAND_EMBED(vformat(" - g0x%x (#%d): %s version %d, data 0x%x", i, i, object.interface->name, object.version, (uintptr_t)object.data)); + } +#endif // WAYLAND_EMBED_DEBUG_LOGS_ENABLED + + CRASH_NOW_MSG("Max ID reached. This might indicate a leak."); + } +#endif // DEV_ENABLED + + return id; +} + +bool WaylandEmbedder::global_surface_is_window(uint32_t p_wl_surface_id) { + WaylandObject *surface_object = get_object(p_wl_surface_id); + ERR_FAIL_NULL_V(surface_object, false); + if (surface_object->interface != &wl_surface_interface || surface_object->data == nullptr) { + return false; + } + + WaylandSurfaceData *surface_data = (WaylandSurfaceData *)surface_object->data; + if (!surface_data->role_object_handle.get()) { + return false; + } + + WaylandObject *role_object = surface_data->role_object_handle.get(); + + return (role_object && role_object->interface == &xdg_toplevel_interface); +} + +bool WaylandEmbedder::handle_generic_msg(Client *client, const WaylandObject *p_object, const struct wl_message *message, const struct msg_info *info, uint32_t *buf, uint32_t instance_id) { + // We allow client-less events. + CRASH_COND(client == nullptr && info->direction == ProxyDirection::COMPOSITOR); + + ERR_FAIL_NULL_V(p_object, false); + + bool valid = true; + + // Let's strip the header. + uint32_t *body = buf + 2; + + size_t arg_idx = 0; + size_t buf_idx = 0; + size_t last_str_buf_idx = -1; + uint32_t last_str_len = 0; + for (size_t i = 0; i < strlen(message->signature); ++i) { + ERR_FAIL_COND_V(buf_idx > (info->size / sizeof *body), false); + + char sym = message->signature[i]; + if (sym >= '0' && sym <= '?') { + // We don't care about version notices and nullability symbols. We can skip + // those. + continue; + } + + switch (sym) { + case 'a': { + uint32_t array_len = body[buf_idx]; + + // We can't obviously go forward by just one byte. Let's skip to the end of + // the array. + buf_idx += wl_array_word_offset(array_len); + } break; + + case 's': { + uint32_t string_len = body[buf_idx]; + + last_str_buf_idx = buf_idx; + last_str_len = string_len; + + // Same as the array. + buf_idx += wl_array_word_offset(string_len); + } break; + + case 'n': { + uint32_t arg = body[buf_idx]; + + const struct wl_interface *new_interface = message->types[arg_idx]; + uint32_t new_version = p_object->version; + + if (!new_interface && last_str_len != 0) { + // When the protocol definition does not define an interface it reports a + // string and an unsigned integer representing the interface and the + // version requested. + new_interface = wl_interface_from_string((char *)(body + last_str_buf_idx + 1), last_str_len); + new_version = body[arg_idx - 1]; + } + + if (new_interface == nullptr) { + if (last_str_len > 0) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Unknown interface %s, marking packet as invalid.", (char *)(body + last_str_buf_idx + 1))); + } else { + DEBUG_LOG_WAYLAND_EMBED("Unknown interface, marking packet as invalid."); + } + valid = false; + break; + } + + if (info->direction == ProxyDirection::COMPOSITOR) { + // FIXME: Create objects only if the packet is valid. + uint32_t new_local_id = arg; + body[buf_idx] = client->new_object(new_local_id, new_interface, new_version); + + if (body[buf_idx] == INVALID_ID) { + valid = false; + break; + } + + } else if (info->direction == ProxyDirection::CLIENT) { + uint32_t new_global_id = arg; + + if (client) { + body[buf_idx] = client->new_server_object(new_global_id, new_interface, new_version); + } else { + new_server_object(new_global_id, new_interface, new_version); + } + + if (body[buf_idx] == INVALID_ID) { + valid = false; + break; + } + } + } break; + + case 'o': { + if (!client) { + break; + } + + uint32_t obj_id = body[buf_idx]; + if (obj_id == 0) { + // Object arguments can be nil. + break; + } + + if (info->direction == ProxyDirection::CLIENT) { + if (!client->local_ids.has(obj_id)) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Object argument g0x%x not found, marking packet as invalid.", obj_id)); + valid = false; + break; + } + body[buf_idx] = instance_id != INVALID_ID ? instance_id : client->get_local_id(obj_id); + } else if (info->direction == ProxyDirection::COMPOSITOR) { + if (!client->global_ids.has(obj_id)) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Object argument l0x%x not found, marking packet as invalid.", obj_id)); + valid = false; + break; + } + body[buf_idx] = client->get_global_id(obj_id); + } + } break; + } + + ++arg_idx; + ++buf_idx; + } + + return valid; +} + +WaylandEmbedder::MessageStatus WaylandEmbedder::handle_request(LocalObjectHandle p_object, uint32_t p_opcode, const uint32_t *msg_data, size_t msg_len) { + ERR_FAIL_COND_V(!p_object.is_valid(), MessageStatus::HANDLED); + + WaylandObject *object = p_object.get(); + Client *client = p_object.get_client(); + + ERR_FAIL_NULL_V(object, MessageStatus::HANDLED); + + // NOTE: Global ID may be null. + uint32_t global_id = p_object.get_global_id(); + uint32_t local_id = p_object.get_local_id(); + + ERR_FAIL_NULL_V(object->interface, MessageStatus::ERROR); + const struct wl_interface *interface = object->interface; + + ERR_FAIL_COND_V((int)p_opcode >= interface->method_count, MessageStatus::ERROR); + const struct wl_message message = interface->methods[p_opcode]; + + DEBUG_LOG_WAYLAND_EMBED(vformat("Client #%d -> %s::%s(%s) l0x%x g0x%x", client->socket, interface->name, message.name, message.signature, local_id, global_id)); + + const uint32_t *body = msg_data + 2; + size_t body_len = msg_len - (WL_WORD_SIZE * 2); + + if (registry_globals_names.has(global_id)) { + int global_name = registry_globals_names[global_id]; + ERR_FAIL_COND_V(!registry_globals.has(global_name), MessageStatus::ERROR); + RegistryGlobalInfo &global_info = registry_globals[global_name]; + + if (global_info.destroyed) { + DEBUG_LOG_WAYLAND_EMBED("Skipping request for destroyed global object"); + return MessageStatus::HANDLED; + } + } + + if (object->interface == &wl_display_interface && p_opcode == WL_DISPLAY_GET_REGISTRY) { + // The gist of this is that the registry is a global and the compositor can + // quite simply take for granted that a single client can access any global + // bound from any registry. Let's remove all doubts by using a single + // registry (also for efficiency) and doing fancy remaps. + uint32_t local_registry_id = body[0]; + + // Note that the registry has already been allocated in the initialization + // routine. + + for (KeyValue &pair : registry_globals) { + uint32_t global_name = pair.key; + RegistryGlobalInfo &global_info = pair.value; + + if (global_info.destroyed) { + continue; + } + + const struct wl_interface *global_interface = global_info.interface; + + if (client != main_client && embedded_interface_deny_list.has(global_interface)) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Skipped global announcement %s for embedded client.", global_interface->name)); + continue; + } + + LocalVector args; + args.push_back(wl_arg_uint(global_name)); + args.push_back(wl_arg_string(global_interface->name)); + args.push_back(wl_arg_uint(global_info.version)); + + send_wayland_event(client->socket, local_registry_id, wl_registry_interface, WL_REGISTRY_GLOBAL, args); + } + + client->wl_registry_instances.insert(local_registry_id); + client->new_global_instance(local_registry_id, REGISTRY_ID, &wl_registry_interface, 1); + + return MessageStatus::HANDLED; + } + + if (object->interface == &wl_registry_interface) { + if (p_opcode == WL_REGISTRY_BIND) { + // [Request] wl_registry::bind(usun) + uint32_t global_name = body[0]; + uint32_t interface_name_len = body[1]; + //const char *interface_name = (const char *)(body + 2); + uint32_t version = body[2 + wl_array_word_offset(interface_name_len)]; + uint32_t new_local_id_idx = 2 + wl_array_word_offset(interface_name_len) + 1; + uint32_t new_local_id = body[new_local_id_idx]; + + if (!registry_globals.has(global_name)) { + socket_error(client->socket, local_id, WL_DISPLAY_ERROR_INVALID_METHOD, vformat("Invalid global object #%d", global_name)); + return MessageStatus::HANDLED; + } + + RegistryGlobalInfo &global_info = registry_globals[global_name]; + ERR_FAIL_NULL_V(global_info.interface, MessageStatus::ERROR); + + version = MIN(global_info.version, version); + + if (global_info.interface == &godot_embedding_compositor_interface) { + if (!client->registry_globals_instances.has(global_name)) { + client->registry_globals_instances[global_name] = {}; + } + + client->registry_globals_instances[global_name].insert(new_local_id); + ++global_info.instance_counter; + DEBUG_LOG_WAYLAND_EMBED("Bound embedded compositor interface."); + client->new_fake_object(new_local_id, &godot_embedding_compositor_interface, 1); + return MessageStatus::HANDLED; + } + + WaylandObject *instance = nullptr; + + client->registry_globals_instances[global_name].insert(new_local_id); + ++global_info.instance_counter; + + if (!client->registry_globals_instances.has(global_name)) { + client->registry_globals_instances[global_name] = {}; + } + + uint32_t bind_gid = wl_registry_bind(REGISTRY_ID, global_name, version); + if (bind_gid == INVALID_ID) { + socket_error(client->socket, local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, "Bind failed."); + return MessageStatus::HANDLED; + } + + WaylandObject *bind_obj = get_object(bind_gid); + if (bind_obj == nullptr) { + socket_error(client->socket, local_id, WL_DISPLAY_ERROR_IMPLEMENTATION, "Bind failed."); + return MessageStatus::HANDLED; + } + + if (!bind_obj->shared) { + client->bind_global_id(bind_gid, new_local_id); + instance = bind_obj; + } else { + instance = client->new_global_instance(new_local_id, global_info.reusable_objects[version], global_info.interface, version); + DEBUG_LOG_WAYLAND_EMBED(vformat("Instancing global #%d iface %s ver %d new id l0x%x g0x%x", global_name, global_info.interface->name, version, new_local_id, global_info.reusable_objects[version])); + + // Some interfaces report their state as soon as they're bound. Since + // instances are handled by us, we need to track and report the relevant + // data ourselves. + if (global_info.interface == &wl_drm_interface) { + Error err = client->send_wl_drm_state(new_local_id, (WaylandDrmGlobalData *)global_info.data); + if (err != OK) { + return MessageStatus::ERROR; + } + } else if (global_info.interface == &wl_shm_interface) { + WaylandShmGlobalData *global_data = (WaylandShmGlobalData *)global_info.data; + ERR_FAIL_NULL_V(global_data, MessageStatus::ERROR); + + for (uint32_t format : global_data->formats) { + send_wayland_message(client->socket, new_local_id, WL_SHM_FORMAT, { format }); + } + } + } + + ERR_FAIL_NULL_V(instance, MessageStatus::UNHANDLED); + + if (global_info.interface == &wl_seat_interface) { + WaylandSeatInstanceData *new_data = memnew(WaylandSeatInstanceData); + instance->data = new_data; + } + + return MessageStatus::HANDLED; + } + } + + if (object->interface == &wl_compositor_interface && p_opcode == WL_COMPOSITOR_CREATE_SURFACE) { + uint32_t new_local_id = body[0]; + + WaylandSurfaceData *data = memnew(WaylandSurfaceData); + data->client = client; + + uint32_t new_global_id = client->new_object(new_local_id, &wl_surface_interface, object->version, data); + ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); + + DEBUG_LOG_WAYLAND_EMBED(vformat("Keeping track of surface l0x%x g0x%x.", new_local_id, new_global_id)); + + send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id }); + return MessageStatus::HANDLED; + } + + if (object->interface == &wl_surface_interface) { + WaylandSurfaceData *surface_data = (WaylandSurfaceData *)object->data; + ERR_FAIL_NULL_V(surface_data, MessageStatus::ERROR); + + if (p_opcode == WL_SURFACE_DESTROY) { + for (uint32_t wl_seat_name : wl_seat_names) { + WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)registry_globals[wl_seat_name].data; + ERR_FAIL_NULL_V(global_seat_data, MessageStatus::ERROR); + + if (global_seat_data->pointed_surface_id == global_id) { + global_seat_data->pointed_surface_id = INVALID_ID; + } + + if (global_seat_data->focused_surface_id == global_id) { + global_seat_data->focused_surface_id = INVALID_ID; + } + } + } else if (p_opcode == WL_SURFACE_COMMIT) { + if (surface_data->role_object_handle.is_valid()) { + WaylandObject *role_object = surface_data->role_object_handle.get(); + if (role_object && role_object->interface) { + DEBUG_LOG_WAYLAND_EMBED(vformat("!!!!! Committed surface g0x%x with role object %s id l0x%x", global_id, role_object->interface->name, surface_data->role_object_handle.get_local_id())); + } + + if (role_object && role_object->interface == &xdg_toplevel_interface) { + XdgToplevelData *toplevel_data = (XdgToplevelData *)role_object->data; + ERR_FAIL_NULL_V(toplevel_data, MessageStatus::ERROR); + // xdg shell spec requires clients to first send data and then commit the + // surface. + + if (toplevel_data->is_embedded() && !toplevel_data->configured) { + toplevel_data->configured = true; + // xdg_surface::configure + send_wayland_message(client->socket, toplevel_data->xdg_surface_handle.get_local_id(), 0, { serial_counter++ }); + } + } + } + + send_wayland_message(compositor_socket, global_id, p_opcode, {}); + return MessageStatus::HANDLED; + } + } + + if (object->interface == &wl_seat_interface) { + uint32_t global_seat_name = registry_globals_names[global_id]; + + RegistryGlobalInfo &seat_global_info = registry_globals[global_seat_name]; + WaylandSeatGlobalData *global_data = (WaylandSeatGlobalData *)seat_global_info.data; + ERR_FAIL_NULL_V(global_data, MessageStatus::ERROR); + + WaylandSeatInstanceData *instance_data = (WaylandSeatInstanceData *)object->data; + ERR_FAIL_NULL_V(instance_data, MessageStatus::ERROR); + + if (p_opcode == WL_SEAT_GET_POINTER) { + ERR_FAIL_COND_V(global_id == INVALID_ID, MessageStatus::ERROR); + // [Request] wl_seat::get_pointer(n); + uint32_t new_local_id = body[0]; + + WaylandPointerData *new_data = memnew(WaylandPointerData); + new_data->wl_seat_id = global_id; + + uint32_t new_global_id = client->new_object(new_local_id, &wl_pointer_interface, object->version, new_data); + ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); + + instance_data->wl_pointer_id = new_global_id; + + send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id }); + + return MessageStatus::HANDLED; + } + + if (p_opcode == WL_SEAT_GET_KEYBOARD) { + ERR_FAIL_COND_V(global_id == INVALID_ID, MessageStatus::ERROR); + // [Request] wl_seat::get_pointer(n); + uint32_t new_local_id = body[0]; + + WaylandKeyboardData *new_data = memnew(WaylandKeyboardData); + new_data->wl_seat_id = global_id; + + uint32_t new_global_id = client->new_object(new_local_id, &wl_keyboard_interface, object->version, new_data); + ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); + + instance_data->wl_keyboard_id = new_global_id; + + send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id }); + + return MessageStatus::HANDLED; + } + } + + if (object->interface == &xdg_wm_base_interface) { + if (p_opcode == XDG_WM_BASE_CREATE_POSITIONER) { + uint32_t new_local_id = body[0]; + uint32_t new_global_id = client->new_object(new_local_id, &xdg_positioner_interface, object->version, memnew(XdgPositionerData)); + ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); + + send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id }); + return MessageStatus::HANDLED; + } + + if (p_opcode == XDG_WM_BASE_GET_XDG_SURFACE) { + // [Request] xdg_wm_base::get_xdg_surface(no). + uint32_t new_local_id = body[0]; + uint32_t surface_id = body[1]; + + uint32_t global_surface_id = client->get_global_id(surface_id); + + bool fake = (client != main_client); + + XdgSurfaceData *data = memnew(XdgSurfaceData); + data->wl_surface_id = global_surface_id; + + if (fake) { + client->new_fake_object(new_local_id, &xdg_surface_interface, object->version, data); + DEBUG_LOG_WAYLAND_EMBED(vformat("Created fake xdg_surface l0x%x for surface l0x%x", new_local_id, surface_id)); + } else { + uint32_t new_global_id = client->new_object(new_local_id, &xdg_surface_interface, object->version, data); + ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); + + DEBUG_LOG_WAYLAND_EMBED(vformat("Created real xdg_surface l0x%x g0x%x for surface l0x%x", new_local_id, new_global_id, surface_id)); + + send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id, global_surface_id }); + } + + return MessageStatus::HANDLED; + } + } + + if (object->interface == &xdg_surface_interface) { + XdgSurfaceData *xdg_surf_data = (XdgSurfaceData *)object->data; + ERR_FAIL_NULL_V(xdg_surf_data, MessageStatus::ERROR); + + WaylandSurfaceData *surface_data = (WaylandSurfaceData *)get_object(xdg_surf_data->wl_surface_id)->data; + ERR_FAIL_NULL_V(surface_data, MessageStatus::ERROR); + + bool is_embedded = client->fake_objects.has(local_id); + + if (p_opcode == XDG_SURFACE_GET_POPUP) { + // [Request] xdg_surface::get_popup(no?o). + + uint32_t new_local_id = body[0]; + uint32_t local_parent_id = body[1]; + uint32_t local_positioner_id = body[2]; + + surface_data->role_object_handle = LocalObjectHandle(client, new_local_id); + + XdgPopupData *popup_data = memnew(XdgPopupData); + popup_data->parent_handle = LocalObjectHandle(client, local_parent_id); + + if (!is_embedded) { + uint32_t new_global_id = client->new_object(new_local_id, &xdg_popup_interface, object->version, popup_data); + ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); + + uint32_t global_parent_id = client->get_global_id(local_parent_id); + uint32_t global_positioner_id = client->get_global_id(local_positioner_id); + send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id, global_parent_id, global_positioner_id }); + + return MessageStatus::HANDLED; + } + + { + // Popups are real, time to actually instantiate an xdg_surface. + WaylandObject copy = *object; + client->fake_objects.erase(local_id); + + global_id = client->new_object(local_id, copy.interface, copy.version, copy.data); + ERR_FAIL_COND_V(global_id == INVALID_ID, MessageStatus::HANDLED); + object = get_object(global_id); + + // xdg_wm_base::get_xdg_surface(no); + send_wayland_message(compositor_socket, xdg_wm_base_id, 2, { global_id, xdg_surf_data->wl_surface_id }); + } + + uint32_t new_global_id = client->new_object(new_local_id, &xdg_popup_interface, object->version, popup_data); + ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); + + uint32_t global_parent_id = INVALID_ID; + if (local_parent_id != INVALID_ID) { + XdgSurfaceData *parent_xdg_surf_data = (XdgSurfaceData *)client->get_object(local_parent_id)->data; + ERR_FAIL_NULL_V(parent_xdg_surf_data, MessageStatus::ERROR); + + WaylandSurfaceData *parent_surface_data = (WaylandSurfaceData *)get_object(parent_xdg_surf_data->wl_surface_id)->data; + ERR_FAIL_NULL_V(parent_surface_data, MessageStatus::ERROR); + + WaylandObject *parent_role_obj = parent_surface_data->role_object_handle.get(); + ERR_FAIL_NULL_V(parent_role_obj, MessageStatus::ERROR); + + XdgPositionerData *pos_data = (XdgPositionerData *)client->get_object(local_positioner_id)->data; + ERR_FAIL_NULL_V(pos_data, MessageStatus::ERROR); + + if (parent_role_obj->interface == &xdg_toplevel_interface) { + XdgToplevelData *parent_toplevel_data = (XdgToplevelData *)parent_role_obj->data; + ERR_FAIL_NULL_V(parent_toplevel_data, MessageStatus::ERROR); + + if (parent_toplevel_data->is_embedded()) { + // Embedded windows are subsurfaces of a parent window. We need to + // "redirect" the popup request on the parent window and adjust the + // positioner properly if needed. + + XdgToplevelData *main_parent_toplevel_data = (XdgToplevelData *)parent_toplevel_data->parent_handle.get()->data; + ERR_FAIL_NULL_V(main_parent_toplevel_data, MessageStatus::ERROR); + + global_parent_id = main_parent_toplevel_data->xdg_surface_handle.get_global_id(); + + WaylandSubsurfaceData *subsurf_data = (WaylandSubsurfaceData *)get_object(parent_toplevel_data->wl_subsurface_id)->data; + ERR_FAIL_NULL_V(subsurf_data, MessageStatus::ERROR); + + Point2i adj_pos = subsurf_data->position + pos_data->anchor_rect.position; + + // xdg_positioner::set_anchor_rect + send_wayland_message(compositor_socket, client->get_global_id(local_positioner_id), 2, { (uint32_t)adj_pos.x, (uint32_t)adj_pos.y, (uint32_t)pos_data->anchor_rect.size.width, (uint32_t)pos_data->anchor_rect.size.height }); + } + } else { + global_parent_id = client->get_global_id(local_parent_id); + } + } + + send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id, global_parent_id, client->get_global_id(local_positioner_id) }); + return MessageStatus::HANDLED; + } + + if (p_opcode == XDG_SURFACE_GET_TOPLEVEL) { + // [Request] xdg_surface::get_toplevel(n). + uint32_t new_local_id = body[0]; + + surface_data->role_object_handle = LocalObjectHandle(client, new_local_id); + + XdgToplevelData *data = memnew(XdgToplevelData); + data->xdg_surface_handle = LocalObjectHandle(client, local_id); + + if (is_embedded) { + client->new_fake_object(new_local_id, &xdg_toplevel_interface, object->version, data); + client->embedded_window_id = new_local_id; + + // godot_embedded_client::window_embedded() + send_wayland_message(main_client->socket, client->embedded_client_id, 1, {}); + } else { + uint32_t new_global_id = client->new_object(new_local_id, &xdg_toplevel_interface, object->version, data); + ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); + + if (main_toplevel_id == 0) { + main_toplevel_id = new_global_id; + DEBUG_LOG_WAYLAND_EMBED(vformat("main toplevel set to gx0%x.", main_toplevel_id)); + } + + send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id }); + } + + return MessageStatus::HANDLED; + } + } + + if (object->interface == &xdg_positioner_interface) { + XdgPositionerData *pos_data = (XdgPositionerData *)object->data; + ERR_FAIL_NULL_V(pos_data, MessageStatus::ERROR); + + if (p_opcode == XDG_POSITIONER_SET_ANCHOR_RECT) { + // Args: int x, int y, int width, int height. + pos_data->anchor_rect = Rect2i(body[0], body[1], body[2], body[3]); + + send_wayland_message(compositor_socket, global_id, p_opcode, body, body_len); + return MessageStatus::HANDLED; + } + } + + if (object->interface == &xdg_toplevel_interface && p_opcode == XDG_TOPLEVEL_DESTROY) { + if (client->fake_objects.has(local_id)) { + XdgToplevelData *data = (XdgToplevelData *)object->data; + ERR_FAIL_NULL_V(data, MessageStatus::ERROR); + + XdgSurfaceData *xdg_surf_data = nullptr; + if (data->xdg_surface_handle.is_valid()) { + xdg_surf_data = (XdgSurfaceData *)data->xdg_surface_handle.get()->data; + ERR_FAIL_NULL_V(xdg_surf_data, MessageStatus::ERROR); + } + ERR_FAIL_NULL_V(xdg_surf_data, MessageStatus::ERROR); + + XdgSurfaceData *parent_xdg_surf_data = nullptr; + { + XdgToplevelData *parent_data = nullptr; + if (data->parent_handle.get()) { + parent_data = (XdgToplevelData *)data->parent_handle.get()->data; + ERR_FAIL_NULL_V(parent_data, MessageStatus::ERROR); + } + + if (parent_data && parent_data->xdg_surface_handle.get()) { + parent_xdg_surf_data = (XdgSurfaceData *)parent_data->xdg_surface_handle.get()->data; + ERR_FAIL_NULL_V(parent_xdg_surf_data, MessageStatus::ERROR); + } + } + + for (uint32_t wl_seat_name : wl_seat_names) { + WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)registry_globals[wl_seat_name].data; + ERR_FAIL_NULL_V(global_seat_data, MessageStatus::ERROR); + + if (global_seat_data->focused_surface_id == xdg_surf_data->wl_surface_id) { + if (xdg_surf_data) { + seat_name_leave_surface(wl_seat_name, xdg_surf_data->wl_surface_id); + } + + if (parent_xdg_surf_data) { + seat_name_enter_surface(wl_seat_name, parent_xdg_surf_data->wl_surface_id); + } + } + } + + // wl_display::delete_id + send_wayland_message(client->socket, local_id, p_opcode, {}); + + if (local_id == client->embedded_window_id) { + client->embedded_window_id = 0; + } + + if (data->wl_subsurface_id != INVALID_ID) { + send_wayland_message(compositor_socket, data->wl_subsurface_id, WL_SUBSURFACE_DESTROY, {}); + } + + client->delete_object(local_id); + + return MessageStatus::HANDLED; + } + } + + if (interface == &zwp_pointer_constraints_v1_interface) { + // FIXME: This implementation leaves no way of unlocking the pointer when + // embedded into the main window. We might need to be a bit more invasive. + if (p_opcode == ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER) { + // [Request] zwp_pointer_constraints_v1::lock_pointer(nooou). + + uint32_t new_local_id = body[0]; + uint32_t local_surface_id = body[1]; + uint32_t local_pointer_id = body[2]; + uint32_t lifetime = body[4]; + + WaylandSurfaceData *surf_data = (WaylandSurfaceData *)client->get_object(local_surface_id)->data; + ERR_FAIL_NULL_V(surf_data, MessageStatus::ERROR); + + WaylandObject *role_obj = surf_data->role_object_handle.get(); + ERR_FAIL_NULL_V(role_obj, MessageStatus::ERROR); + + if (role_obj->interface == &xdg_toplevel_interface) { + XdgToplevelData *toplevel_data = (XdgToplevelData *)role_obj->data; + ERR_FAIL_NULL_V(toplevel_data, MessageStatus::ERROR); + + if (!toplevel_data->is_embedded()) { + // Passthrough. + return MessageStatus::UNHANDLED; + } + + // Subsurfaces don't normally work, at least on sway, as the locking + // condition might rely on focus, which they don't get. We can remap them to + // the parent surface and set a region though. + + XdgToplevelData *parent_data = (XdgToplevelData *)toplevel_data->parent_handle.get()->data; + ERR_FAIL_NULL_V(parent_data, MessageStatus::ERROR); + + XdgSurfaceData *parent_xdg_surf_data = (XdgSurfaceData *)parent_data->xdg_surface_handle.get()->data; + ERR_FAIL_NULL_V(parent_xdg_surf_data, MessageStatus::ERROR); + + WaylandSubsurfaceData *subsurf_data = (WaylandSubsurfaceData *)get_object(toplevel_data->wl_subsurface_id)->data; + ERR_FAIL_NULL_V(subsurf_data, MessageStatus::ERROR); + + uint32_t new_global_id = client->new_object(new_local_id, &zwp_locked_pointer_v1_interface, object->version); + ERR_FAIL_COND_V(new_global_id == INVALID_ID, MessageStatus::HANDLED); + + uint32_t x = subsurf_data->position.x; + uint32_t y = subsurf_data->position.y; + uint32_t width = toplevel_data->size.width; + uint32_t height = toplevel_data->size.height; + + // NOTE: At least on sway I can't seem to be able to get this region + // working but the calls check out. + DEBUG_LOG_WAYLAND_EMBED(vformat("Creating custom region x%d y%d w%d h%d", x, y, width, height)); + + uint32_t new_region_id = allocate_global_id(); + get_object(new_region_id)->interface = &wl_region_interface; + get_object(new_region_id)->version = get_object(wl_compositor_id)->version; + + // wl_compostor::create_region(n). + send_wayland_message(compositor_socket, wl_compositor_id, 1, { new_region_id }); + + // wl_region::add(iiii). + send_wayland_message(compositor_socket, new_region_id, 1, { x, y, width, height }); + + send_wayland_message(compositor_socket, global_id, p_opcode, { new_global_id, parent_xdg_surf_data->wl_surface_id, client->get_global_id(local_pointer_id), new_region_id, lifetime }); + + // wl_region::destroy(). + send_wayland_message(compositor_socket, new_region_id, 0, {}); + + return MessageStatus::HANDLED; + } + } + } + + if (interface == &godot_embedded_client_interface) { + EmbeddedClientData *eclient_data = (EmbeddedClientData *)object->data; + ERR_FAIL_NULL_V(eclient_data, MessageStatus::ERROR); + + Client *eclient = eclient_data->client; + ERR_FAIL_NULL_V(eclient, MessageStatus::ERROR); + + if (p_opcode == GODOT_EMBEDDED_CLIENT_DESTROY) { + if (!eclient_data->disconnected) { + close(eclient->socket); + } + + client->delete_object(local_id); + + return MessageStatus::HANDLED; + } + + if (eclient_data->disconnected) { + // Object is inert. + return MessageStatus::HANDLED; + } + + ERR_FAIL_COND_V(eclient->embedded_window_id == 0, MessageStatus::ERROR); + + XdgToplevelData *toplevel_data = (XdgToplevelData *)eclient->get_object(eclient->embedded_window_id)->data; + ERR_FAIL_NULL_V(toplevel_data, MessageStatus::ERROR); + + if (p_opcode == GODOT_EMBEDDED_CLIENT_SET_EMBEDDED_WINDOW_RECT && toplevel_data->wl_subsurface_id != INVALID_ID) { + uint32_t x = body[0]; + uint32_t y = body[1]; + uint32_t width = body[2]; + uint32_t height = body[3]; + + WaylandSubsurfaceData *subsurf_data = (WaylandSubsurfaceData *)get_object(toplevel_data->wl_subsurface_id)->data; + ERR_FAIL_NULL_V(subsurf_data, MessageStatus::ERROR); + + toplevel_data->size.width = width; + toplevel_data->size.height = height; + + subsurf_data->position.x = x; + subsurf_data->position.y = y; + + // wl_subsurface::set_position + send_wayland_message(compositor_socket, toplevel_data->wl_subsurface_id, 1, { x, y }); + + // xdg_toplevel::configure + send_wayland_message(eclient->socket, eclient->embedded_window_id, 0, { width, height, 0 }); + + // xdg_surface::configure + send_wayland_message(eclient->socket, toplevel_data->xdg_surface_handle.get_local_id(), 0, { configure_serial_counter++ }); + + return MessageStatus::HANDLED; + } else if (p_opcode == GODOT_EMBEDDED_CLIENT_SET_EMBEDDED_WINDOW_PARENT) { + uint32_t main_client_parent_id = body[0]; + + if (toplevel_data->parent_handle.get_local_id() == main_client_parent_id) { + return MessageStatus::HANDLED; + } + + if (main_client_parent_id == INVALID_ID && toplevel_data->wl_subsurface_id != INVALID_ID) { + // Window hiding logic. + + // wl_subsurface::destroy() + send_wayland_message(compositor_socket, toplevel_data->wl_subsurface_id, 0, {}); + + toplevel_data->parent_handle.invalidate(); + toplevel_data->wl_subsurface_id = INVALID_ID; + + return MessageStatus::HANDLED; + } + + XdgToplevelData *parent_toplevel_data = (XdgToplevelData *)client->get_object(main_client_parent_id)->data; + ERR_FAIL_NULL_V(parent_toplevel_data, MessageStatus::ERROR); + XdgSurfaceData *parent_xdg_surf_data = (XdgSurfaceData *)parent_toplevel_data->xdg_surface_handle.get()->data; + ERR_FAIL_NULL_V(parent_xdg_surf_data, MessageStatus::ERROR); + + XdgSurfaceData *xdg_surf_data = (XdgSurfaceData *)toplevel_data->xdg_surface_handle.get()->data; + ERR_FAIL_NULL_V(xdg_surf_data, MessageStatus::ERROR); + + if (toplevel_data->wl_subsurface_id != INVALID_ID) { + // wl_subsurface::destroy() + send_wayland_message(compositor_socket, toplevel_data->wl_subsurface_id, 0, {}); + } + + uint32_t new_sub_id = allocate_global_id(); + WaylandObject *new_sub_object = get_object(new_sub_id); + new_sub_object->interface = &wl_subsurface_interface; + new_sub_object->data = memnew(WaylandSubsurfaceData); + new_sub_object->version = get_object(wl_subcompositor_id)->version; + + toplevel_data->wl_subsurface_id = new_sub_id; + toplevel_data->parent_handle = LocalObjectHandle(main_client, main_client_parent_id); + + DEBUG_LOG_WAYLAND_EMBED(vformat("Binding subsurface g0x%x.", new_sub_id)); + + // wl_subcompositor::get_subsurface + send_wayland_message(compositor_socket, wl_subcompositor_id, 1, { new_sub_id, xdg_surf_data->wl_surface_id, parent_xdg_surf_data->wl_surface_id }); + + // wl_subsurface::set_desync + send_wayland_message(compositor_socket, new_sub_id, 5, {}); + + return MessageStatus::HANDLED; + } else if (p_opcode == GODOT_EMBEDDED_CLIENT_FOCUS_WINDOW) { + XdgSurfaceData *xdg_surf_data = (XdgSurfaceData *)toplevel_data->xdg_surface_handle.get()->data; + ERR_FAIL_NULL_V(xdg_surf_data, MessageStatus::ERROR); + + for (uint32_t wl_seat_name : wl_seat_names) { + RegistryGlobalInfo &global_seat_info = registry_globals[wl_seat_name]; + WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)global_seat_info.data; + + if (global_seat_data->focused_surface_id != INVALID_ID) { + seat_name_leave_surface(wl_seat_name, global_seat_data->focused_surface_id); + } + global_seat_data->focused_surface_id = xdg_surf_data->wl_surface_id; + + seat_name_enter_surface(wl_seat_name, xdg_surf_data->wl_surface_id); + } + } else if (p_opcode == GODOT_EMBEDDED_CLIENT_EMBEDDED_WINDOW_REQUEST_CLOSE) { + // xdg_toplevel::close + send_wayland_message(eclient->socket, eclient->embedded_window_id, 1, {}); + + return MessageStatus::HANDLED; + } + } + + // Server-allocated objects are a bit annoying to handle for us. Right now we + // use a heuristic. See: https://ppaalanen.blogspot.com/2014/07/wayland-protocol-design-object-lifespan.html + if (strcmp(message.name, "destroy") == 0 || strcmp(message.name, "release") == 0) { + if (object->shared) { + // We must not delete shared objects. + client->delete_object(local_id); + return MessageStatus::HANDLED; + } + + if (global_id != INVALID_ID) { + send_wayland_message(compositor_socket, global_id, p_opcode, {}); + object->destroyed = true; + } + + if (local_id & 0xff000000) { + DEBUG_LOG_WAYLAND_EMBED(vformat("!!!!!! Deallocating server object l0x%x", local_id)); + client->delete_object(local_id); + } + + return MessageStatus::HANDLED; + } + + if (client->fake_objects.has(local_id)) { + // Object is fake, we're done. + DEBUG_LOG_WAYLAND_EMBED("Dropping unhandled request for fake object."); + return MessageStatus::HANDLED; + } + + if (global_id == INVALID_ID) { + DEBUG_LOG_WAYLAND_EMBED("Dropping request with invalid global object id"); + return MessageStatus::HANDLED; + } + + return MessageStatus::UNHANDLED; +} + +WaylandEmbedder::MessageStatus WaylandEmbedder::handle_event(uint32_t p_global_id, LocalObjectHandle p_local_handle, uint32_t p_opcode, const uint32_t *msg_data, size_t msg_len) { + WaylandObject *global_object = get_object(p_global_id); + ERR_FAIL_NULL_V_MSG(global_object, MessageStatus::ERROR, "Compositor messages must always have a global object."); + +#ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED + ERR_FAIL_NULL_V(global_object->interface, MessageStatus::ERROR); + const struct wl_interface *interface = global_object->interface; + + ERR_FAIL_COND_V((int)p_opcode >= interface->event_count, MessageStatus::ERROR); + const struct wl_message message = interface->events[p_opcode]; + + if (p_local_handle.is_valid()) { + int socket = p_local_handle.get_client()->socket; + DEBUG_LOG_WAYLAND_EMBED(vformat("Client #%d <- %s::%s(%s) g0x%x", socket, interface->name, message.name, message.signature, p_global_id)); + } else { + DEBUG_LOG_WAYLAND_EMBED(vformat("Client N/A <- %s::%s(%s) g0x%x", interface->name, message.name, message.signature, p_global_id)); + } +#endif //WAYLAND_EMBED_DEBUG_LOGS_ENABLED + + const uint32_t *body = msg_data + 2; + //size_t body_len = msg_len - (WL_WORD_SIZE * 2); + + // FIXME: Make sure that it makes sense to track this protocol. Not only is it + // old and getting deprecated, but I can't even get this code branch to hit + // probably because, at the time of writing, we only get the "main" display + // through the proxy. + if (global_object->interface == &wl_drm_interface) { + // wl_drm can't ever be destroyed, so we need to track its state as it's going + // to be instanced at least few times. + uint32_t global_name = registry_globals_names[p_global_id]; + WaylandDrmGlobalData *global_data = (WaylandDrmGlobalData *)registry_globals[global_name].data; + ERR_FAIL_NULL_V(global_data, MessageStatus::ERROR); + + if (p_opcode == WL_DRM_DEVICE) { + // signature: s + uint32_t name_len = body[0]; + uint8_t *name = (uint8_t *)(body + 1); + global_data->device = String::utf8((const char *)name, name_len); + + return MessageStatus::UNHANDLED; + } + + if (p_opcode == WL_DRM_FORMAT) { + // signature: u + uint32_t format = body[0]; + global_data->formats.push_back(format); + + return MessageStatus::UNHANDLED; + } + + if (p_opcode == WL_DRM_AUTHENTICATED) { + // signature: N/A + global_data->authenticated = true; + + return MessageStatus::UNHANDLED; + } + + if (p_opcode == WL_DRM_CAPABILITIES) { + // signature: u + uint32_t capabilities = body[0]; + global_data->capabilities = capabilities; + } + + return MessageStatus::UNHANDLED; + } + + if (global_object->interface == &wl_shm_interface) { + uint32_t global_name = registry_globals_names[p_global_id]; + WaylandShmGlobalData *global_data = (WaylandShmGlobalData *)registry_globals[global_name].data; + ERR_FAIL_NULL_V(global_data, MessageStatus::ERROR); + + if (p_opcode == WL_SHM_FORMAT) { + // Signature: u + uint32_t format = body[0]; + global_data->formats.push_back(format); + } + } + + if (!p_local_handle.is_valid()) { + // Some requests might not have a valid local object handle for various + // reasons, such as when certain events are directed to this proxy or when the + // destination client of a message disconnected in the meantime. + + if (global_object->interface == &wl_display_interface) { + if (p_opcode == WL_DISPLAY_DELETE_ID) { + // [Event] wl_display::delete_id(u) + uint32_t global_delete_id = body[0]; + DEBUG_LOG_WAYLAND_EMBED(vformat("Compositor requested deletion of g0x%x (no client)", global_delete_id)); + + delete_object(global_delete_id); + + return MessageStatus::HANDLED; + } else if (p_opcode == WL_DISPLAY_ERROR) { + // [Event] wl_display::error(ous) + uint32_t obj_id = body[0]; + uint32_t err_code = body[1]; + + CRASH_NOW_MSG(vformat("Error obj g0x%x code %d: %s", obj_id, err_code, (const char *)(body + 3))); + } + } + + if (global_object->interface == &wl_callback_interface && p_opcode == WL_CALLBACK_DONE) { + if (sync_callback_id != INVALID_ID && p_global_id == sync_callback_id) { + sync_callback_id = 0; + DEBUG_LOG_WAYLAND_EMBED("Sync response received"); + return MessageStatus::HANDLED; + } + } + + if (global_object->interface == &wl_registry_interface) { + if (p_opcode == WL_REGISTRY_GLOBAL) { + // [Event] wl_registry::global(usu). + + uint32_t global_name = body[0]; + uint32_t interface_name_len = body[1]; + const char *interface_name = (const char *)(body + 2); + uint32_t global_version = body[2 + wl_array_word_offset(interface_name_len)]; + + DEBUG_LOG_WAYLAND_EMBED("Global c#%d %s %d", global_name, interface_name, global_version); + + const struct wl_interface *global_interface = wl_interface_from_string(interface_name, interface_name_len); + if (global_interface) { + RegistryGlobalInfo global_info = {}; + global_info.interface = global_interface; + global_info.version = MIN(global_version, (uint32_t)global_interface->version); + DEBUG_LOG_WAYLAND_EMBED("Clamped global %s to version %d.", interface_name, global_info.version); + global_info.compositor_name = global_name; + + int new_global_name = registry_globals_counter++; + + if (global_info.interface == &wl_shm_interface) { + DEBUG_LOG_WAYLAND_EMBED("Allocating global wl_shm data."); + global_info.data = memnew(WaylandShmGlobalData); + } + + if (global_info.interface == &wl_seat_interface) { + DEBUG_LOG_WAYLAND_EMBED("Allocating global wl_seat data."); + global_info.data = memnew(WaylandSeatGlobalData); + wl_seat_names.push_back(new_global_name); + } + + if (global_info.interface == &wl_drm_interface) { + DEBUG_LOG_WAYLAND_EMBED("Allocating global wl_drm data."); + global_info.data = memnew(WaylandDrmGlobalData); + } + + registry_globals[new_global_name] = global_info; + + // We need some interfaces directly. It's better to bind a "copy" ourselves + // than to wait for the client to ask one. + if (global_interface == &xdg_wm_base_interface && xdg_wm_base_id == 0) { + xdg_wm_base_id = wl_registry_bind(p_global_id, new_global_name, global_info.version); + ERR_FAIL_COND_V(xdg_wm_base_id == INVALID_ID, MessageStatus::ERROR); + } else if (global_interface == &wl_compositor_interface && wl_compositor_id == 0) { + wl_compositor_id = wl_registry_bind(p_global_id, new_global_name, global_info.version); + ERR_FAIL_COND_V(wl_compositor_id == INVALID_ID, MessageStatus::ERROR); + } else if (global_interface == &wl_subcompositor_interface && wl_subcompositor_id == 0) { + wl_subcompositor_id = wl_registry_bind(p_global_id, new_global_name, global_info.version); + ERR_FAIL_COND_V(wl_subcompositor_id == INVALID_ID, MessageStatus::ERROR); + } + + DEBUG_LOG_WAYLAND_EMBED(vformat("Local registry object name: l#%d", new_global_name)); + + if (clients.is_empty()) { + // Let's not waste time. + return MessageStatus::HANDLED; + } + + // Notify all clients. + LocalVector args; + args.push_back(wl_arg_uint(new_global_name)); + args.push_back(wl_arg_string(interface_name)); + args.push_back(wl_arg_uint(global_info.version)); + for (KeyValue &pair : clients) { + Client &client = pair.value; + for (uint32_t local_registry_id : client.wl_registry_instances) { + send_wayland_event(client.socket, local_registry_id, wl_registry_interface, WL_REGISTRY_GLOBAL, args); + } + } + + return MessageStatus::HANDLED; + } else { + DEBUG_LOG_WAYLAND_EMBED("Skipping unknown global %s version %d.", interface_name, global_version); + + return MessageStatus::HANDLED; + } + } else if (p_opcode == WL_REGISTRY_GLOBAL_REMOVE) { + uint32_t compositor_name = body[0]; + uint32_t local_name = 0; + RegistryGlobalInfo *global_info = nullptr; + + // FIXME: Use a map or something. + for (KeyValue &pair : registry_globals) { + uint32_t name = pair.key; + RegistryGlobalInfo &info = pair.value; + + if (info.compositor_name == compositor_name) { + local_name = name; + global_info = &info; + break; + } + } + + ERR_FAIL_NULL_V(global_info, MessageStatus::ERROR); + + if (global_info->instance_counter == 0) { + memdelete(global_info->data); + registry_globals.erase(local_name); + } else { + global_info->destroyed = true; + } + + // Notify all clients. + LocalVector args; + args.push_back(wl_arg_uint(local_name)); + for (KeyValue &pair : clients) { + Client &client = pair.value; + for (uint32_t local_registry_id : client.wl_registry_instances) { + send_wayland_event(client.socket, local_registry_id, wl_registry_interface, WL_REGISTRY_GLOBAL_REMOVE, args); + } + } + + return MessageStatus::HANDLED; + } + } + + DEBUG_LOG_WAYLAND_EMBED("No valid local object handle, falling back to generic handler."); + return MessageStatus::UNHANDLED; + } + + Client *client = p_local_handle.get_client(); + + ERR_FAIL_NULL_V(client, MessageStatus::ERROR); + + WaylandObject *object = p_local_handle.get(); + uint32_t local_id = p_local_handle.get_local_id(); + + if (global_object->interface == &wl_display_interface) { + if (p_opcode == WL_DISPLAY_DELETE_ID) { + // [Event] wl_display::delete_id(u) + uint32_t global_delete_id = body[0]; + uint32_t local_delete_id = client->get_local_id(global_delete_id); + DEBUG_LOG_WAYLAND_EMBED(vformat("Compositor requested delete of g0x%x l0x%x", global_delete_id, local_delete_id)); + if (local_delete_id == INVALID_ID) { + // No idea what this object is, might be of the other client. This + // definitely does not make sense to us, so we're done. + return MessageStatus::INVALID; + } + + client->delete_object(local_delete_id); + + send_wayland_message(client->socket, DISPLAY_ID, WL_DISPLAY_DELETE_ID, { local_delete_id }); + + return MessageStatus::HANDLED; + } + + return MessageStatus::UNHANDLED; + } + + if (object->interface == &wl_keyboard_interface) { + WaylandKeyboardData *data = (WaylandKeyboardData *)object->data; + ERR_FAIL_NULL_V(data, MessageStatus::ERROR); + + uint32_t global_seat_name = registry_globals_names[data->wl_seat_id]; + RegistryGlobalInfo &global_seat_info = registry_globals[global_seat_name]; + WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)global_seat_info.data; + ERR_FAIL_NULL_V(global_seat_data, MessageStatus::ERROR); + + if (p_opcode == WL_KEYBOARD_ENTER) { + // [Event] wl_keyboard::enter(uoa) + uint32_t surface = body[1]; + + if (global_seat_data->focused_surface_id != surface) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Focused g0x%x", surface)); + global_seat_data->focused_surface_id = surface; + } + } else if (p_opcode == WL_KEYBOARD_LEAVE) { + // [Event] wl_keyboard::leave(uo) + uint32_t surface = body[1]; + + if (global_seat_data->focused_surface_id == surface) { + global_seat_data->focused_surface_id = INVALID_ID; + } + } else if (p_opcode == WL_KEYBOARD_KEY) { + // NOTE: modifiers event can be sent even without focus, according to the + // spec, so there's no need to skip it. + if (global_seat_data->focused_surface_id != INVALID_ID && !client->local_ids.has(global_seat_data->focused_surface_id)) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Skipped wl_keyboard event due to unfocused surface 0x%x", global_seat_data->focused_surface_id)); + return MessageStatus::HANDLED; + } + } + + return MessageStatus::UNHANDLED; + } + + if (object->interface == &wl_pointer_interface) { + WaylandPointerData *data = (WaylandPointerData *)object->data; + ERR_FAIL_NULL_V(data, MessageStatus::ERROR); + + uint32_t global_seat_name = registry_globals_names[data->wl_seat_id]; + RegistryGlobalInfo &global_seat_info = registry_globals[global_seat_name]; + WaylandSeatGlobalData *global_seat_data = (WaylandSeatGlobalData *)global_seat_info.data; + ERR_FAIL_NULL_V(global_seat_data, MessageStatus::ERROR); + + WaylandSeatInstanceData *seat_data = (WaylandSeatInstanceData *)object->data; + ERR_FAIL_NULL_V(seat_data, MessageStatus::ERROR); + + if (p_opcode == WL_POINTER_BUTTON && global_seat_data->pointed_surface_id != INVALID_ID) { + // [Event] wl_pointer::button(uuuu); + uint32_t button = body[2]; + uint32_t state = body[3]; + + DEBUG_LOG_WAYLAND_EMBED(vformat("Button %d state %d on surface g0x%x (focused g0x%x)", button, state, global_seat_data->pointed_surface_id, global_seat_data->focused_surface_id)); + + bool client_pointed = client->local_ids.has(global_seat_data->pointed_surface_id); + + if (button != BTN_LEFT || state != WL_POINTER_BUTTON_STATE_RELEASED) { + return client_pointed ? MessageStatus::UNHANDLED : MessageStatus::HANDLED; + } + + if (global_seat_data->focused_surface_id == global_seat_data->pointed_surface_id) { + return client_pointed ? MessageStatus::UNHANDLED : MessageStatus::HANDLED; + } + + if (!global_surface_is_window(global_seat_data->pointed_surface_id)) { + return client_pointed ? MessageStatus::UNHANDLED : MessageStatus::HANDLED; + } + + if (global_seat_data->focused_surface_id != INVALID_ID) { + seat_name_leave_surface(global_seat_name, global_seat_data->focused_surface_id); + } + + global_seat_data->focused_surface_id = global_seat_data->pointed_surface_id; + seat_name_enter_surface(global_seat_name, global_seat_data->focused_surface_id); + } else if (p_opcode == WL_POINTER_ENTER) { + // [Event] wl_pointer::enter(uoff). + uint32_t surface = body[1]; + WaylandSurfaceData *surface_data = (WaylandSurfaceData *)get_object(surface)->data; + ERR_FAIL_NULL_V(surface_data, MessageStatus::ERROR); + + if (global_seat_data->pointed_surface_id != surface) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Pointer (g0x%x seat g0x%x): pointed surface old g0x%x new g0x%x", p_global_id, data->wl_seat_id, global_seat_data->pointed_surface_id, surface)); + + global_seat_data->pointed_surface_id = surface; + } + } else if (p_opcode == WL_POINTER_LEAVE) { + // [Event] wl_pointer::leave(uo). + uint32_t surface = body[1]; + WaylandSurfaceData *surface_data = (WaylandSurfaceData *)get_object(surface)->data; + ERR_FAIL_NULL_V(surface_data, MessageStatus::ERROR); + + if (global_seat_data->pointed_surface_id == surface) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Pointer (g0x%x seat g0x%x): g0x%x -> g0x%x", p_global_id, data->wl_seat_id, global_seat_data->pointed_surface_id, INVALID_ID)); + global_seat_data->pointed_surface_id = INVALID_ID; + } + } + + return MessageStatus::UNHANDLED; + } + + if (object->interface == &xdg_popup_interface) { + if (p_opcode == XDG_POPUP_CONFIGURE) { + // [Event] xdg_popup::configure(iiii); + int32_t x = body[0]; + int32_t y = body[1]; + int32_t width = body[2]; + int32_t height = body[3]; + + XdgPopupData *data = (XdgPopupData *)object->data; + ERR_FAIL_NULL_V(data, MessageStatus::ERROR); + + XdgSurfaceData *parent_xdg_surf_data = (XdgSurfaceData *)data->parent_handle.get()->data; + ERR_FAIL_NULL_V(parent_xdg_surf_data, MessageStatus::ERROR); + + WaylandSurfaceData *parent_surface_data = (WaylandSurfaceData *)get_object(parent_xdg_surf_data->wl_surface_id)->data; + ERR_FAIL_NULL_V(parent_surface_data, MessageStatus::ERROR); + + WaylandObject *parent_role_obj = parent_surface_data->role_object_handle.get(); + ERR_FAIL_NULL_V(parent_role_obj, MessageStatus::ERROR); + + if (parent_role_obj->interface == &xdg_toplevel_interface) { + XdgToplevelData *parent_toplevel_data = (XdgToplevelData *)parent_role_obj->data; + ERR_FAIL_NULL_V(parent_toplevel_data, MessageStatus::ERROR); + + if (parent_toplevel_data->is_embedded()) { + WaylandSubsurfaceData *subsurf_data = (WaylandSubsurfaceData *)get_object(parent_toplevel_data->wl_subsurface_id)->data; + ERR_FAIL_NULL_V(subsurf_data, MessageStatus::ERROR); + + // The coordinates passed will be shifted by the embedded window position, + // so we need to fix them back. + Point2i fixed_position = Point2i(x, y) - subsurf_data->position; + + DEBUG_LOG_WAYLAND_EMBED(vformat("Correcting popup configure position to %s", fixed_position)); + + send_wayland_message(client->socket, local_id, p_opcode, { (uint32_t)fixed_position.x, (uint32_t)fixed_position.y, (uint32_t)width, (uint32_t)height }); + + return MessageStatus::HANDLED; + } + } + } + } + + return MessageStatus::UNHANDLED; +} + +void WaylandEmbedder::shutdown() { + thread_done.set(); + + { + // First making a list of all clients so that we can iteratively delete them. + LocalVector sockets; + for (KeyValue &pair : clients) { + sockets.push_back(pair.key); + } + + for (int socket : sockets) { + cleanup_socket(socket); + } + } + + close(compositor_socket); + compositor_socket = -1; + + for (KeyValue &pair : registry_globals) { + RegistryGlobalInfo &info = pair.value; + if (info.data) { + memdelete(info.data); + info.data = nullptr; + } + } +} + +Error WaylandEmbedder::handle_msg_info(Client *client, const struct msg_info *info, uint32_t *buf, int *fds_requested) { + ERR_FAIL_NULL_V(info, ERR_BUG); + ERR_FAIL_NULL_V(fds_requested, ERR_BUG); + ERR_FAIL_NULL_V_MSG(info->direction == ProxyDirection::COMPOSITOR && client, ERR_BUG, "Wait, where did this message come from?"); + + *fds_requested = 0; + + WaylandObject *object = nullptr; + + uint32_t global_id = INVALID_ID; + if (info->direction == ProxyDirection::CLIENT) { + global_id = info->raw_id; + } else if (info->direction == ProxyDirection::COMPOSITOR) { + global_id = client->get_global_id(info->raw_id); + } + + if (global_id != INVALID_ID) { + object = get_object(global_id); + } else if (client) { + object = client->get_object(info->raw_id); + } + + if (object == nullptr) { + if (info->direction == ProxyDirection::COMPOSITOR) { + uint32_t local_id = info->raw_id; + ERR_PRINT(vformat("Couldn't find requested object l0x%x for client %d, disconnecting.", local_id, client->socket)); + + socket_error(client->socket, local_id, WL_DISPLAY_ERROR_INVALID_OBJECT, vformat("Object l0x%x not found.", local_id)); + return OK; + } else { + CRASH_NOW_MSG(vformat("No object found for r0x%x", info->raw_id)); + } + } + + const struct wl_interface *interface = nullptr; + interface = object->interface; + + if (interface == nullptr && info->raw_id & 0xff000000) { + // Regular clients have no confirmation about deleted server objects (why + // should they?) but since we share connections there's the risk of receiving + // messages about deleted server objects. The simplest solution is to ignore + // unknown server-side objects. Not the safest thing, I know, but it should do + // the job. + DEBUG_LOG_WAYLAND_EMBED(vformat("Ignoring unknown server-side object r0x%x", info->raw_id)); + return OK; + } + + ERR_FAIL_NULL_V_MSG(interface, ERR_BUG, vformat("Object r0x%x has no interface", info->raw_id)); + + const struct wl_message *message = nullptr; + if (info->direction == ProxyDirection::CLIENT) { + ERR_FAIL_COND_V(info->opcode >= interface->event_count, ERR_BUG); + message = &interface->events[info->opcode]; + } else { + ERR_FAIL_COND_V(info->opcode >= interface->method_count, ERR_BUG); + message = &interface->methods[info->opcode]; + } + ERR_FAIL_NULL_V(message, ERR_BUG); + + *fds_requested = String(message->signature).count("h"); + LocalVector sent_fds; + + if (*fds_requested > 0) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Requested %d FDs.", *fds_requested)); + + List &fd_queue = info->direction == ProxyDirection::COMPOSITOR ? client->fds : compositor_fds; + for (int i = 0; i < *fds_requested; ++i) { + ERR_FAIL_COND_V_MSG(fd_queue.is_empty(), ERR_BUG, "Out of FDs."); + DEBUG_LOG_WAYLAND_EMBED(vformat("Fetching FD %d.", fd_queue.front()->get())); + sent_fds.push_back(fd_queue.front()->get()); + fd_queue.pop_front(); + } + + DEBUG_LOG_WAYLAND_EMBED(vformat("Remaining FDs: %d.", fd_queue.size())); + } + + if (object->destroyed) { + DEBUG_LOG_WAYLAND_EMBED("Ignoring message for inert object."); + // Inert object. + return OK; + } + + if (info->direction == ProxyDirection::COMPOSITOR) { + MessageStatus request_status = handle_request(LocalObjectHandle(client, info->raw_id), info->opcode, buf, info->size); + if (request_status == MessageStatus::ERROR) { + return ERR_BUG; + } + + if (request_status == MessageStatus::HANDLED) { + DEBUG_LOG_WAYLAND_EMBED("Custom handler success."); + return OK; + } + + if (global_id != INVALID_ID) { + buf[0] = global_id; + } + + DEBUG_LOG_WAYLAND_EMBED("Falling back to generic handler."); + + if (handle_generic_msg(client, object, message, info, buf)) { + send_raw_message(compositor_socket, { { buf, info->size } }, sent_fds); + } + } else { + uint32_t global_name = 0; + + bool is_global = false; + if (registry_globals_names.has(global_id)) { + global_name = registry_globals_names[global_id]; + is_global = true; + } + + // FIXME: For compatibility, mirror events with instanced registry globals as + // object arguments. For example, `wl_surface.enter` returns a `wl_output`. If + // said `wl_output` has been instanced multiple times, we need to resend the + // same event with each instance as the argument, or the client might miss the + // event by looking for the "wrong" instance. + // + // Note that this missing behavior is exclusively a compatibility mechanism + // for old compositors which only implement undestroyable globals. We + // otherwise passthrough every bind request and then the compositor takes care + // of everything. + // See: https://lore.freedesktop.org/wayland-devel/20190326121421.06732fd2@eldfell.localdomain/ + if (object->shared) { + bool handled = false; + + for (KeyValue &pair : clients) { + Client &c = pair.value; + if (c.socket < 0) { + continue; + } + + if (!c.local_ids.has(global_id)) { + DEBUG_LOG_WAYLAND_EMBED("!!!!!!!!!!! Instance missing?"); + continue; + } + + if (is_global) { + if (!c.registry_globals_instances.has(global_name)) { + continue; + } + + DEBUG_LOG_WAYLAND_EMBED(vformat("Broadcasting to all global instances for client %d (socket %d)", c.pid, c.socket)); + for (uint32_t instance_id : c.registry_globals_instances[global_name]) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Global instance l0x%x", instance_id)); + + LocalObjectHandle local_obj = LocalObjectHandle(&c, instance_id); + if (!local_obj.is_valid()) { + continue; + } + + MessageStatus event_status = handle_event(global_id, local_obj, info->opcode, buf, info->size); + + if (event_status == MessageStatus::ERROR) { + return ERR_BUG; + } + + if (event_status == MessageStatus::HANDLED) { + DEBUG_LOG_WAYLAND_EMBED("Custom handler success."); + handled = true; + continue; + } + + if (event_status == MessageStatus::INVALID) { + continue; + } + + DEBUG_LOG_WAYLAND_EMBED("Falling back to generic handler."); + + buf[0] = instance_id; + + if (handle_generic_msg(&c, local_obj.get(), message, info, buf, instance_id)) { + send_raw_message(c.socket, { { buf, info->size } }, sent_fds); + } + + handled = true; + } + } else if (interface == &wl_display_interface) { + // NOTE: The only shared non-global objects are `wl_display` and + // `wl_registry`, both of which require custom handlers. Additionally, of + // those only `wl_display` has client-specific handlers, which is what this + // branch manages. + + LocalObjectHandle local_obj = LocalObjectHandle(&c, c.get_local_id(global_id)); + if (!local_obj.is_valid()) { + continue; + } + + DEBUG_LOG_WAYLAND_EMBED(vformat("Shared non-global l0x%x g0x%x", c.get_local_id(global_id), global_id)); + + MessageStatus event_status = handle_event(global_id, local_obj, info->opcode, buf, info->size); + if (event_status == MessageStatus::ERROR) { + return ERR_BUG; + } + + if (event_status == MessageStatus::HANDLED) { + DEBUG_LOG_WAYLAND_EMBED("Custom handler success."); + handled = true; + continue; + } + + if (event_status == MessageStatus::INVALID) { + continue; + } + + DEBUG_LOG_WAYLAND_EMBED("Falling back to generic handler."); + + if (handle_generic_msg(&c, local_obj.get(), message, info, buf)) { + send_raw_message(c.socket, { { buf, info->size } }, sent_fds); + } + + handled = true; + } + } + + if (!handled) { + // No client handled this, it's going to be handled as a client-less event. + // We do this only at the end to avoid handling certain events (e.g. + // deletion) twice. + handle_event(global_id, LocalObjectHandle(nullptr, INVALID_ID), info->opcode, buf, info->size); + } + } else { + LocalObjectHandle local_obj = LocalObjectHandle(client, client ? client->get_local_id(global_id) : INVALID_ID); + + MessageStatus event_status = handle_event(global_id, local_obj, info->opcode, buf, info->size); + if (event_status == MessageStatus::ERROR) { + return ERR_BUG; + } + + if (event_status == MessageStatus::HANDLED || event_status == MessageStatus::INVALID) { + // We're done. + return OK; + } + + // Generic passthrough. + + if (client) { + uint32_t local_id = client->get_local_id(global_id); + ERR_FAIL_COND_V(local_id == INVALID_ID, OK); + + DEBUG_LOG_WAYLAND_EMBED(vformat("%s::%s(%s) g0x%x -> l0x%x", interface->name, message->name, message->signature, global_id, local_id)); + buf[0] = local_id; + + if (handle_generic_msg(client, local_obj.get(), message, info, buf)) { + send_raw_message(client->socket, { { buf, info->size } }, sent_fds); + } + } else { + WARN_PRINT_ONCE(vformat("[Wayland Embedder] Unexpected client-less event from %s#g0x%x. Object has probably leaked.", object->interface->name, global_id)); + handle_generic_msg(nullptr, object, message, info, buf); + } + } + } + + for (int fd : sent_fds) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Closing fd %d.", fd)); + close(fd); + } + + return OK; +} + +Error WaylandEmbedder::handle_sock(int p_fd) { + ERR_FAIL_COND_V(p_fd < 0, ERR_INVALID_PARAMETER); + + struct msg_info info = {}; + + { + struct msghdr head_msg = {}; + uint32_t header[2]; + struct iovec vec = { header, sizeof header }; + + head_msg.msg_iov = &vec; + head_msg.msg_iovlen = 1; + + ssize_t head_rec = recvmsg(p_fd, &head_msg, MSG_PEEK); + + if (head_rec == 0) { + // Client disconnected. + return ERR_CONNECTION_ERROR; + } + + if (head_rec == -1) { + if (errno == ECONNRESET) { + // No need to print the error, the client forcefully disconnected, that's + // fine. + return ERR_CONNECTION_ERROR; + } + + ERR_FAIL_V_MSG(FAILED, vformat("Can't read message header: %s", strerror(errno))); + } + + ERR_FAIL_COND_V_MSG(((size_t)head_rec) != vec.iov_len, ERR_CONNECTION_ERROR, vformat("Should've received %d bytes, instead got %d bytes", vec.iov_len, head_rec)); + + // Header is two 32-bit words: first is ID, second has size in most significant + // half and opcode in the other half. + info.raw_id = header[0]; + info.size = header[1] >> 16; + info.opcode = header[1] & 0xFFFF; + info.direction = p_fd != compositor_socket ? ProxyDirection::COMPOSITOR : ProxyDirection::CLIENT; + } + + if (msg_buf.size() < info.words()) { + msg_buf.resize(info.words()); + } + + ERR_FAIL_COND_V_MSG(info.size % WL_WORD_SIZE != 0, ERR_CONNECTION_ERROR, "Invalid message length."); + + struct msghdr full_msg = {}; + struct iovec vec = { msg_buf.ptr(), info.size }; + { + full_msg.msg_iov = &vec; + full_msg.msg_iovlen = 1; + full_msg.msg_control = ancillary_buf.ptr(); + full_msg.msg_controllen = ancillary_buf.size(); + + ssize_t full_rec = recvmsg(p_fd, &full_msg, 0); + + if (full_rec == -1) { + if (errno == ECONNRESET) { + // No need to print the error, the client forcefully disconnected, that's + // fine. + return ERR_CONNECTION_ERROR; + } + + ERR_FAIL_V_MSG(FAILED, vformat("Can't read message: %s", strerror(errno))); + } + + ERR_FAIL_COND_V_MSG(((size_t)full_rec) != info.size, ERR_CONNECTION_ERROR, "Invalid message length."); + + DEBUG_LOG_WAYLAND_EMBED(" === START PACKET === "); + +#ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED + printf("[PROXY] Received bytes: "); + for (ssize_t i = 0; i < full_rec; ++i) { + printf("%.2x", ((const uint8_t *)msg_buf.ptr())[i]); + } + printf("\n"); +#endif + } + + if (full_msg.msg_controllen > 0) { + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&full_msg); + while (cmsg) { + // TODO: Check for validity of message fields. + size_t data_len = cmsg->cmsg_len - sizeof *cmsg; + + if (cmsg->cmsg_type == SCM_RIGHTS) { + // NOTE: Linux docs say that we can't just cast data to pointer type because + // of alignment concerns. So we have to memcpy into a new buffer. + int *cmsg_fds = (int *)malloc(data_len); + memcpy(cmsg_fds, CMSG_DATA(cmsg), data_len); + + size_t cmsg_fds_count = data_len / sizeof *cmsg_fds; + for (size_t i = 0; i < cmsg_fds_count; ++i) { + int fd = cmsg_fds[i]; + + if (info.direction == ProxyDirection::COMPOSITOR) { + clients[p_fd].fds.push_back(fd); + } else { + compositor_fds.push_back(fd); + } + } + +#ifdef WAYLAND_EMBED_DEBUG_LOGS_ENABLED + printf("[PROXY] Received %ld file descriptors: ", cmsg_fds_count); + for (size_t i = 0; i < cmsg_fds_count; ++i) { + printf("%d ", cmsg_fds[i]); + } + printf("\n"); +#endif + + free(cmsg_fds); + } + + cmsg = CMSG_NXTHDR(&full_msg, cmsg); + } + } + full_msg.msg_control = nullptr; + full_msg.msg_controllen = 0; + + int fds_requested = 0; + + Client *client = nullptr; + if (p_fd == compositor_socket) { + // Let's figure out the recipient of the message. + for (KeyValue &pair : clients) { + Client &c = pair.value; + + if (c.local_ids.has(info.raw_id)) { + client = &c; + } + } + } else { + CRASH_COND(!clients.has(p_fd)); + client = &clients[p_fd]; + } + + if (handle_msg_info(client, &info, msg_buf.ptr(), &fds_requested) != OK) { + return ERR_BUG; + } + + DEBUG_LOG_WAYLAND_EMBED(" === END PACKET === "); + + return OK; +} + +void WaylandEmbedder::_thread_loop(void *p_data) { + Thread::set_name("Wayland Embed"); + + ERR_FAIL_NULL(p_data); + WaylandEmbedder *proxy = (WaylandEmbedder *)p_data; + + DEBUG_LOG_WAYLAND_EMBED("Proxy thread started"); + + while (!proxy->thread_done.is_set()) { + proxy->poll_sockets(); + } +} + +Error WaylandEmbedder::init() { + ancillary_buf.resize(EMBED_ANCILLARY_BUF_SIZE); + + proxy_socket = socket(AF_UNIX, SOCK_STREAM, 0); + + struct sockaddr_un addr = {}; + addr.sun_family = AF_UNIX; + + String runtime_dir_path = OS::get_singleton()->get_environment("XDG_RUNTIME_DIR"); + ERR_FAIL_COND_V_MSG(runtime_dir_path.is_empty(), ERR_DOES_NOT_EXIST, "XDG_RUNTIME_DIR is not set or empty."); + + runtime_dir = DirAccess::create_for_path(runtime_dir_path); + ERR_FAIL_COND_V(!runtime_dir.is_valid(), ERR_BUG); + ERR_FAIL_COND_V_MSG(!runtime_dir->is_writable(runtime_dir_path), ERR_FILE_CANT_WRITE, "XDG_RUNTIME_DIR points to an invalid directory."); + + int socket_id = 0; + while (socket_path.is_empty()) { + String test_socket_path = runtime_dir_path + "/godot-wayland-" + itos(socket_id); + String test_socket_lock_path = test_socket_path + ".lock"; + + print_verbose(vformat("Trying to get socket %s", test_socket_path)); + print_verbose(vformat("Opening lock %s", test_socket_lock_path)); + int test_lock_fd = open(test_socket_lock_path.utf8().get_data(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + + if (flock(test_lock_fd, LOCK_EX | LOCK_NB) == -1) { + print_verbose(vformat("Can't lock %s", test_socket_lock_path)); + close(test_lock_fd); + ++socket_id; + continue; + } else { + lock_fd = test_lock_fd; + socket_path = test_socket_path; + socket_lock_path = test_socket_lock_path; + + break; + } + } + + DirAccess::remove_absolute(socket_path); + strncpy(addr.sun_path, socket_path.utf8().get_data(), sizeof(addr.sun_path) - 1); + + if (bind(proxy_socket, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't bind embedding socket."); + } + + if (listen(proxy_socket, 1) == -1) { + ERR_FAIL_V_MSG(ERR_CANT_OPEN, "Can't listen embedding socket."); + } + + struct wl_display *display = wl_display_connect(nullptr); + ERR_FAIL_NULL_V(display, ERR_CANT_OPEN); + compositor_socket = wl_display_get_fd(display); + + pollfds.push_back({ proxy_socket, POLLIN, 0 }); + pollfds.push_back({ compositor_socket, POLLIN, 0 }); + + RegistryGlobalInfo control_global_info = {}; + control_global_info.interface = &godot_embedding_compositor_interface; + control_global_info.version = godot_embedding_compositor_interface.version; + + godot_embedding_compositor_name = registry_globals_counter++; + registry_globals[godot_embedding_compositor_name] = control_global_info; + + { + uint32_t invalid_id = INVALID_ID; + objects.request(invalid_id); + + CRASH_COND(invalid_id != INVALID_ID); + } + + { + uint32_t display_id = new_object(&wl_display_interface); + CRASH_COND(display_id != DISPLAY_ID); + + get_object(DISPLAY_ID)->shared = true; + } + + { + uint32_t registry_id = new_object(&wl_registry_interface); + CRASH_COND(registry_id != REGISTRY_ID); + + get_object(REGISTRY_ID)->shared = true; + } + + // wl_display::get_registry(n) + send_wayland_message(compositor_socket, DISPLAY_ID, 1, { REGISTRY_ID }); + + sync(); + + proxy_thread.start(_thread_loop, this); + + return OK; +} + +void WaylandEmbedder::handle_fd(int p_fd, int p_revents) { + if (p_fd == proxy_socket && p_revents & POLLIN) { + // Client init. + int new_fd = accept(proxy_socket, nullptr, nullptr); + ERR_FAIL_COND_MSG(new_fd == -1, "Failed to accept client."); + + struct ucred cred = {}; + socklen_t cred_size = sizeof cred; + getsockopt(new_fd, SOL_SOCKET, SO_PEERCRED, &cred, &cred_size); + + Client &client = clients.insert_new(new_fd, {})->value; + + client.embedder = this; + client.socket = new_fd; + client.pid = cred.pid; + + client.global_ids[DISPLAY_ID] = Client::GlobalIdInfo(DISPLAY_ID, nullptr); + client.local_ids[DISPLAY_ID] = DISPLAY_ID; + + pollfds.push_back({ new_fd, POLLIN, 0 }); + + if (main_client == nullptr) { + main_client = &client; + } + + if (new_fd != main_client->socket && main_client->registry_globals_instances.has(godot_embedding_compositor_name)) { + uint32_t new_local_id = main_client->allocate_server_id(); + + client.embedded_client_id = new_local_id; + + for (uint32_t local_id : main_client->registry_globals_instances[godot_embedding_compositor_name]) { + EmbeddedClientData *eclient_data = memnew(EmbeddedClientData); + eclient_data->client = &client; + + main_client->new_fake_object(new_local_id, &godot_embedded_client_interface, 1, eclient_data); + + // godot_embedding_compositor::client(nu) + send_wayland_message(main_client->socket, local_id, 0, { new_local_id, (uint32_t)cred.pid }); + } + } + + DEBUG_LOG_WAYLAND_EMBED(vformat("New client %d (pid %d) initialized.", client.socket, cred.pid)); + return; + } + + if (p_fd == compositor_socket && p_revents & POLLIN) { + Error err = handle_sock(p_fd); + + if (err == ERR_BUG) { + ERR_PRINT("Unexpected error while handling socket, shutting down."); + shutdown(); + return; + } + + return; + } + + const Client *client = clients.getptr(p_fd); + if (client) { + if (main_client && client == main_client && p_revents & (POLLHUP | POLLERR)) { + DEBUG_LOG_WAYLAND_EMBED("Main client disconnected, shutting down."); + shutdown(); + return; + } + + if (p_revents & POLLIN) { + Error err = handle_sock(p_fd); + if (err == ERR_BUG) { + ERR_PRINT("Unexpected error while handling socket, shutting down."); + shutdown(); + return; + } + + if (err != OK) { + DEBUG_LOG_WAYLAND_EMBED("disconnecting"); + cleanup_socket(p_fd); + return; + } + + return; + } else if (p_revents & (POLLHUP | POLLERR | POLLNVAL)) { + if (p_revents & POLLHUP) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Socket %d hangup.", p_fd)); + } + if (p_revents & POLLERR) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Socket %d error.", p_fd)); + } + if (p_revents & POLLNVAL) { + DEBUG_LOG_WAYLAND_EMBED(vformat("Socket %d invalid FD.", p_fd)); + } + + cleanup_socket(p_fd); + + return; + } + } +} + +WaylandEmbedder::~WaylandEmbedder() { + shutdown(); + proxy_thread.wait_to_finish(); +} + +#endif // TOOLS_ENABLED + +#endif // WAYLAND_ENABLED diff --git a/platform/linuxbsd/wayland/wayland_embedder.h b/platform/linuxbsd/wayland/wayland_embedder.h new file mode 100644 index 00000000000..62861d96744 --- /dev/null +++ b/platform/linuxbsd/wayland/wayland_embedder.h @@ -0,0 +1,630 @@ +/**************************************************************************/ +/* wayland_embedder.h */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#ifdef WAYLAND_ENABLED + +#ifdef TOOLS_ENABLED + +#include "core/templates/a_hash_map.h" +#include "core/templates/pooled_list.h" + +#ifdef SOWRAP_ENABLED +#include "wayland/dynwrappers/wayland-client-core-so_wrap.h" +#else +#include +#endif + +#include "protocol/wayland.gen.h" + +#include "protocol/linux_dmabuf_v1.gen.h" +#include "protocol/xdg_shell.gen.h" + +#include "protocol/commit_timing_v1.gen.h" +#include "protocol/cursor_shape.gen.h" +#include "protocol/fifo_v1.gen.h" +#include "protocol/fractional_scale.gen.h" +#include "protocol/godot_embedding_compositor.gen.h" +#include "protocol/idle_inhibit.gen.h" +#include "protocol/linux_drm_syncobj_v1.gen.h" +#include "protocol/linux_explicit_synchronization_unstable_v1.gen.h" +#include "protocol/pointer_constraints.gen.h" +#include "protocol/pointer_gestures.gen.h" +#include "protocol/primary_selection.gen.h" +#include "protocol/relative_pointer.gen.h" +#include "protocol/tablet.gen.h" +#include "protocol/tearing_control_v1.gen.h" +#include "protocol/text_input.gen.h" +#include "protocol/viewporter.gen.h" +#include "protocol/wayland-drm.gen.h" +#include "protocol/xdg_activation.gen.h" +#include "protocol/xdg_decoration.gen.h" +#include "protocol/xdg_foreign_v1.gen.h" +#include "protocol/xdg_foreign_v2.gen.h" +#include "protocol/xdg_shell.gen.h" +#include "protocol/xdg_system_bell.gen.h" +#include "protocol/xdg_toplevel_icon.gen.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "core/io/dir_access.h" +#include "core/os/thread.h" + +// TODO: Consider resizing the ancillary buffer dynamically. +#define EMBED_ANCILLARY_BUF_SIZE 4096 + +class WaylandEmbedder { + enum class ProxyDirection { + CLIENT, + COMPOSITOR, + }; + + enum class MessageStatus { + HANDLED, + UNHANDLED, + INVALID, + ERROR, + }; + + struct msg_info { + uint32_t raw_id = 0; + uint16_t size = 0; + uint16_t opcode = 0; + + pid_t pid = 0; + + ProxyDirection direction = ProxyDirection::CLIENT; + + constexpr size_t words() const { return (size / sizeof(uint32_t)); } + }; + + struct WaylandObjectData { + virtual ~WaylandObjectData() = default; + }; + + struct WaylandObject { + const struct wl_interface *interface = nullptr; + int version = 0; + + // Inert, awaiting confirmation from server. + bool destroyed = false; + + // Other objects might depend on it and must not be destroyed. + bool shared = false; + + WaylandObjectData *data = nullptr; + }; + + struct WaylandDrmGlobalData : WaylandObjectData { + String device; + LocalVector formats; + bool authenticated; + uint32_t capabilities; + }; + + struct WaylandShmGlobalData : WaylandObjectData { + LocalVector formats; + }; + + struct Client { + struct GlobalIdInfo { + uint32_t id = INVALID_ID; + List::Element *history_elem = nullptr; + + GlobalIdInfo() = default; + GlobalIdInfo(uint32_t p_id, List::Element *p_history_elem) : + id(p_id), history_elem(p_history_elem) {} + }; + + WaylandEmbedder *embedder = nullptr; + + int socket = -1; + + // NOTE: PIDs are not unique per client! + pid_t pid = 0; + + // FIXME: Names suck. + AHashMap> registry_globals_instances; + HashSet wl_registry_instances; + + List global_id_history; + AHashMap global_ids; + AHashMap local_ids; + + // Objects with no equivalent on the real compositor. + AHashMap fake_objects; + + // Objects which mirror events of a global object. + AHashMap global_instances; + + uint32_t embedded_client_id = INVALID_ID; + uint32_t embedded_window_id = INVALID_ID; + + List fds; + + // Clients obviously expect properly packed server IDs, so we need to allocate + // them somehow. This approach mimics the one used in PooledList. + uint32_t allocated_server_ids = INVALID_ID; + LocalVector free_server_ids; + + uint32_t get_global_id(uint32_t p_local_id) const { return global_ids.has(p_local_id) ? global_ids[p_local_id].id : INVALID_ID; } + uint32_t get_local_id(uint32_t p_global_id) const { return local_ids.has(p_global_id) ? local_ids[p_global_id] : INVALID_ID; } + + uint32_t allocate_server_id(); + WaylandObject *get_object(uint32_t p_local_id); + Error delete_object(uint32_t p_local_id); + + Error bind_global_id(uint32_t p_global_id, uint32_t p_local_id); + + uint32_t new_object(uint32_t p_local_id, const struct wl_interface *p_interface, int p_version = 1, WaylandObjectData *p_data = nullptr); + uint32_t new_server_object(uint32_t p_global_id, const struct wl_interface *p_interface, int p_version = 1, WaylandObjectData *p_data = nullptr); + WaylandObject *new_fake_object(uint32_t p_local_id, const struct wl_interface *p_interface, int p_version = 1, WaylandObjectData *p_data = nullptr); + WaylandObject *new_global_instance(uint32_t p_local_id, uint32_t p_global_id, const struct wl_interface *p_interface, int p_version = 1, WaylandObjectData *p_data = nullptr); + + Error send_wl_drm_state(uint32_t p_id, WaylandDrmGlobalData *p_state); + }; + + // Local IDs are a mess to handle as they strictly depend on their client of + // origin. This wrapper helps with that. + class LocalObjectHandle { + Client *client = nullptr; + uint32_t local_id = INVALID_ID; + + public: + constexpr LocalObjectHandle() = default; + + constexpr LocalObjectHandle(Client *p_client, uint32_t p_id) : + client(p_client), local_id(p_id) {} + + void invalidate() { + client = nullptr; + local_id = INVALID_ID; + } + constexpr bool is_valid() const { return client != nullptr && local_id != INVALID_ID; } + + WaylandObject *get() { return is_valid() ? client->get_object(local_id) : nullptr; } + constexpr Client *get_client() const { return client; } + constexpr uint32_t get_local_id() const { return local_id; } + uint32_t get_global_id() const { return (is_valid() && client->global_ids.has(local_id)) ? client->global_ids[local_id].id : INVALID_ID; } + }; + + struct WaylandSeatInstanceData : WaylandObjectData { + uint32_t wl_keyboard_id = INVALID_ID; + uint32_t wl_pointer_id = INVALID_ID; + }; + + struct WaylandSeatGlobalData : WaylandObjectData { + uint32_t capabilities = 0; + + uint32_t pointed_surface_id = INVALID_ID; + uint32_t focused_surface_id = INVALID_ID; + }; + + struct WaylandKeyboardData : WaylandObjectData { + uint32_t wl_seat_id = INVALID_ID; + }; + + struct WaylandPointerData : WaylandObjectData { + uint32_t wl_seat_id = INVALID_ID; + }; + + struct WaylandSurfaceData : WaylandObjectData { + Client *client = nullptr; + LocalObjectHandle role_object_handle; + }; + + struct XdgSurfaceData : WaylandObjectData { + uint32_t wl_surface_id = INVALID_ID; + }; + + struct WaylandSubsurfaceData : WaylandObjectData { + Point2i position; + }; + + struct XdgToplevelData : WaylandObjectData { + LocalObjectHandle xdg_surface_handle; + LocalObjectHandle parent_handle; + uint32_t wl_subsurface_id = INVALID_ID; + + Size2i size; + + bool configured = false; + + constexpr bool is_embedded() const { return wl_subsurface_id != INVALID_ID; } + }; + + struct XdgPopupData : WaylandObjectData { + LocalObjectHandle parent_handle; + }; + + struct XdgPositionerData : WaylandObjectData { + Rect2i anchor_rect; + }; + + struct EmbeddedClientData : WaylandObjectData { + Client *client = nullptr; + bool disconnected = false; + }; + + struct RegistryGlobalInfo { + const struct wl_interface *interface = nullptr; + uint32_t version = 0; + uint32_t compositor_name = 0; + + // The specs requires for us to ignore requests for destroyed global + // objects until all instances are gone, to avoid races. + bool destroyed = false; + int instance_counter = 0; + + // Key is version. + HashMap reusable_objects; + + WaylandObjectData *data = nullptr; + }; + + // These are the interfaces that the embedder understands and exposes. We do + // not implement handlers for all of them (that's the point), but we need to + // list them anyways to query their signatures at runtime, which include file + // descriptors count. Additionally, even if we could go without specifying + // them, having a "known good" list avoids unpleasant incompatibilities with + // future compositors. + const static constexpr struct wl_interface *interfaces[] = { + // wayland + &wl_buffer_interface, + &wl_callback_interface, + &wl_compositor_interface, + &wl_data_device_interface, + &wl_data_device_manager_interface, + &wl_data_offer_interface, + &wl_data_source_interface, + &wl_display_interface, + &wl_keyboard_interface, + &wl_output_interface, + &wl_pointer_interface, + &wl_region_interface, + &wl_registry_interface, + &wl_seat_interface, + //&wl_shell_interface, // Deprecated. + //&wl_shell_surface_interface, // Deprecated. + &wl_shm_interface, + &wl_shm_pool_interface, + &wl_subcompositor_interface, + &wl_subsurface_interface, + &wl_surface_interface, + //&wl_touch_interface, // Unused (at the moment). + + // xdg-shell + &xdg_wm_base_interface, + &xdg_positioner_interface, + &xdg_surface_interface, + &xdg_toplevel_interface, + &xdg_popup_interface, + + // linux-dmabuf-v1 + &zwp_linux_dmabuf_v1_interface, + &zwp_linux_buffer_params_v1_interface, + &zwp_linux_dmabuf_feedback_v1_interface, + + // linux-explicit-synchronization-unstable-v1 + &zwp_linux_explicit_synchronization_v1_interface, + &zwp_linux_surface_synchronization_v1_interface, + &zwp_linux_buffer_release_v1_interface, + + // fractional-scale + &wp_fractional_scale_manager_v1_interface, + &wp_fractional_scale_v1_interface, + + // idle-inhibit + &zwp_idle_inhibit_manager_v1_interface, + &zwp_idle_inhibitor_v1_interface, + + // pointer-constraints + &zwp_pointer_constraints_v1_interface, + &zwp_locked_pointer_v1_interface, + &zwp_confined_pointer_v1_interface, + + // pointer-gestures + &zwp_pointer_gestures_v1_interface, + &zwp_pointer_gesture_swipe_v1_interface, + &zwp_pointer_gesture_pinch_v1_interface, + &zwp_pointer_gesture_hold_v1_interface, + + // primary-selection + &zwp_primary_selection_device_manager_v1_interface, + &zwp_primary_selection_device_v1_interface, + &zwp_primary_selection_offer_v1_interface, + &zwp_primary_selection_source_v1_interface, + + // relative-pointer + &zwp_relative_pointer_manager_v1_interface, + &zwp_relative_pointer_v1_interface, + + // tablet + // TODO: Needs some extra work + //&zwp_tablet_manager_v2_interface, + //&zwp_tablet_seat_v2_interface, + //&zwp_tablet_tool_v2_interface, + //&zwp_tablet_v2_interface, + //&zwp_tablet_pad_ring_v2_interface, + //&zwp_tablet_pad_strip_v2_interface, + //&zwp_tablet_pad_group_v2_interface, + //&zwp_tablet_pad_v2_interface, + + // text-input + &zwp_text_input_v3_interface, + &zwp_text_input_manager_v3_interface, + + // viewporter + &wp_viewporter_interface, + &wp_viewport_interface, + + // xdg-activation + &xdg_activation_v1_interface, + &xdg_activation_token_v1_interface, + + // xdg-decoration + &zxdg_decoration_manager_v1_interface, + &zxdg_toplevel_decoration_v1_interface, + + // xdg-foreign + &zxdg_exporter_v1_interface, + &zxdg_importer_v1_interface, + + // xdg-foreign-v1 + &zxdg_exporter_v1_interface, + &zxdg_importer_v1_interface, + + // xdg-foreign-v2 + &zxdg_exporter_v2_interface, + &zxdg_importer_v2_interface, + + // xdg-shell + &xdg_wm_base_interface, + &xdg_positioner_interface, + &xdg_surface_interface, + &xdg_toplevel_interface, + &xdg_popup_interface, + + // xdg-system-bell + &xdg_system_bell_v1_interface, + + // xdg-toplevel-icon-v1 + &xdg_toplevel_icon_manager_v1_interface, + &xdg_toplevel_icon_v1_interface, + + // wp-cursor-shape-v1 + &wp_cursor_shape_manager_v1_interface, + + // wayland-drm + &wl_drm_interface, + + // linux-drm-syncobj-v1 + &wp_linux_drm_syncobj_manager_v1_interface, + &wp_linux_drm_syncobj_surface_v1_interface, + &wp_linux_drm_syncobj_timeline_v1_interface, + + // fifo-v1 + &wp_fifo_manager_v1_interface, + &wp_fifo_v1_interface, + + // commit-timing-v1 + &wp_commit_timing_manager_v1_interface, + &wp_commit_timer_v1_interface, + + // tearing-control-v1 + &wp_tearing_control_manager_v1_interface, + &wp_tearing_control_v1_interface, + + // Our custom things. + &godot_embedding_compositor_interface, + &godot_embedded_client_interface, + }; + + // These interfaces will not be reported to embedded clients. This includes + // stuff that interacts with toplevels or other emulated objects that would + // have been filtered out manually anyways. + HashSet embedded_interface_deny_list = HashSet({ + &zxdg_decoration_manager_v1_interface, + &zxdg_decoration_manager_v1_interface, + &zxdg_exporter_v1_interface, + &zxdg_exporter_v2_interface, + &xdg_toplevel_icon_manager_v1_interface, + &godot_embedding_compositor_interface, + }); + + static constexpr uint32_t INVALID_ID = 0; + static constexpr uint32_t DISPLAY_ID = 1; + static constexpr uint32_t REGISTRY_ID = 2; + + int proxy_socket = -1; + int compositor_socket = -1; + + // NOTE: First element must be the listening socket! This allows us to process + // it last, cleaning up closed sockets before it reuses their handles. + LocalVector pollfds; + + // Key is socket. + AHashMap clients; + + Client *main_client = nullptr; + + PooledList objects; + // Proxies allocated by the compositor. Their ID starts from 0xff000000. + LocalVector server_objects; + + uint32_t wl_compositor_id = 0; + uint32_t wl_subcompositor_id = 0; + uint32_t main_toplevel_id = 0; + uint32_t xdg_wm_base_id = 0; + + // Global id to name + HashMap registry_globals_names; + + HashMap registry_globals; + uint32_t registry_globals_counter = 0; + + uint32_t godot_embedding_compositor_name = 0; + + LocalVector wl_seat_names; + + Thread proxy_thread; + + List client_fds; + List compositor_fds; + + uint32_t serial_counter = 0; + uint32_t configure_serial_counter = 0; + + uint32_t sync_callback_id = 0; + + Ref runtime_dir; + int lock_fd = -1; + String socket_path; + String socket_lock_path; + + LocalVector msg_buf; + LocalVector ancillary_buf; + + SafeFlag thread_done; + + static size_t wl_array_word_offset(uint32_t p_size); + const static struct wl_interface *wl_interface_from_string(const char *name, size_t size); + static int wl_interface_get_destructor_opcode(const struct wl_interface *p_iface, uint32_t p_version); + + static Error send_raw_message(int p_socket, std::initializer_list p_vecs, const LocalVector &p_fds = LocalVector()); + + static Error send_wayland_message(int p_socket, uint32_t p_id, uint32_t p_opcode, const uint32_t *p_args, const size_t p_args_words); + static Error send_wayland_message(ProxyDirection p_direction, int p_socket, uint32_t p_id, const struct wl_interface &p_interface, uint32_t p_opcode, const LocalVector &p_args); + + // Utility aliases. + + static Error send_wayland_message(int p_socket, uint32_t p_id, uint32_t p_opcode, std::initializer_list p_args) { + return send_wayland_message(p_socket, p_id, p_opcode, p_args.begin(), p_args.size()); + } + + static Error send_wayland_method(int p_socket, uint32_t p_id, const struct wl_interface &p_interface, uint32_t p_opcode, const LocalVector &p_args) { + return send_wayland_message(ProxyDirection::COMPOSITOR, p_socket, p_id, p_interface, p_opcode, p_args); + } + + static Error send_wayland_event(int p_socket, uint32_t p_id, const struct wl_interface &p_interface, uint32_t p_opcode, const LocalVector &p_args) { + return send_wayland_message(ProxyDirection::CLIENT, p_socket, p_id, p_interface, p_opcode, p_args); + } + + // Closes the socket. + static void socket_error(int p_socket, uint32_t p_object_id, uint32_t p_code, const String &p_message); + + // NOTE: Yes, in our case object arguments are actually uints for now. + // Best way I found to reuse the Wayland stuff. Might need to make our + // own eventually. + static constexpr union wl_argument wl_arg_int(int32_t p_value) { + union wl_argument arg = {}; + arg.i = p_value; + return arg; + } + static constexpr union wl_argument wl_arg_uint(uint32_t p_value) { + union wl_argument arg = {}; + arg.u = p_value; + return arg; + } + static constexpr union wl_argument wl_arg_fixed(wl_fixed_t p_value) { + union wl_argument arg = {}; + arg.f = p_value; + return arg; + } + static constexpr union wl_argument wl_arg_string(const char *p_value) { + union wl_argument arg = {}; + arg.s = p_value; + return arg; + } + static constexpr union wl_argument wl_arg_object(uint32_t p_value) { + union wl_argument arg = {}; + arg.u = p_value; + return arg; + } + static constexpr union wl_argument wl_arg_new_id(uint32_t p_value) { + union wl_argument arg = {}; + arg.n = p_value; + return arg; + } + + uint32_t new_object(const struct wl_interface *p_interface, int p_version = 1, WaylandObjectData *p_data = nullptr); + WaylandObject *new_server_object(uint32_t p_global_id, const struct wl_interface *p_interface, int p_version = 1, WaylandObjectData *p_data = nullptr); + + void poll_sockets(); + + int allocate_global_id(); + + bool global_surface_is_window(uint32_t p_global_surface_id); + + WaylandObject *get_object(uint32_t id); + Error delete_object(uint32_t id); + + void cleanup_socket(int p_socket); + + void sync(); + + uint32_t wl_registry_bind(uint32_t p_registry_id, uint32_t p_name, int p_version); + + void seat_name_enter_surface(uint32_t p_seat_name, uint32_t p_global_surface_id); + void seat_name_leave_surface(uint32_t p_seat_name, uint32_t p_global_surface_id); + + MessageStatus handle_request(LocalObjectHandle p_object, uint32_t p_opcode, const uint32_t *msg_data, size_t msg_len); + MessageStatus handle_event(uint32_t p_global_id, LocalObjectHandle p_local_handle, uint32_t p_opcode, const uint32_t *msg_data, size_t msg_len); + + void shutdown(); + + bool handle_generic_msg(Client *client, const WaylandObject *p_object, const struct wl_message *message, const struct msg_info *info, uint32_t *buf, uint32_t instance_id = INVALID_ID); + Error handle_msg_info(Client *client, const struct msg_info *info, uint32_t *buf, int *fds_requested); + Error handle_sock(int p_fd); + void handle_fd(int p_fd, int p_revents); + + static void _thread_loop(void *p_data); + +public: + // Returns path to socket. + Error init(); + + String get_socket_path() const { return socket_path; } + + ~WaylandEmbedder(); +}; + +#endif // TOOLS_ENABLED + +#endif // WAYLAND_ENABLED diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 37ab02573b3..47decf05aad 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -662,6 +662,13 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re if (strcmp(interface, FIFO_INTERFACE_NAME) == 0) { registry->wp_fifo_manager_name = name; } + + if (strcmp(interface, godot_embedding_compositor_interface.name) == 0) { + registry->godot_embedding_compositor = (struct godot_embedding_compositor *)wl_registry_bind(wl_registry, name, &godot_embedding_compositor_interface, 1); + registry->godot_embedding_compositor_name = name; + + godot_embedding_compositor_add_listener(registry->godot_embedding_compositor, &godot_embedding_compositor_listener, memnew(EmbeddingCompositorState)); + } } void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) { @@ -1092,6 +1099,25 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry if (name == registry->wp_fifo_manager_name) { registry->wp_fifo_manager_name = 0; } + + if (name == registry->godot_embedding_compositor_name) { + registry->godot_embedding_compositor_name = 0; + + EmbeddingCompositorState *es = godot_embedding_compositor_get_state(registry->godot_embedding_compositor); + ERR_FAIL_NULL(es); + + es->mapped_clients.clear(); + + for (struct godot_embedded_client *client : es->clients) { + godot_embedded_client_destroy(client); + } + es->clients.clear(); + + memdelete(es); + + godot_embedding_compositor_destroy(registry->godot_embedding_compositor); + registry->godot_embedding_compositor = nullptr; + } } void WaylandThread::_wl_surface_on_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { @@ -2064,6 +2090,8 @@ void WaylandThread::_wl_keyboard_on_keymap(void *data, struct wl_keyboard *wl_ke xkb_state_unref(ss->xkb_state); ss->xkb_state = xkb_state_new(ss->xkb_keymap); + + xkb_state_update_mask(ss->xkb_state, ss->mods_depressed, ss->mods_latched, ss->mods_locked, 0, 0, ss->current_layout_index); } void WaylandThread::_wl_keyboard_on_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { @@ -2124,6 +2152,15 @@ void WaylandThread::_wl_keyboard_on_leave(void *data, struct wl_keyboard *wl_key msg->event = DisplayServer::WINDOW_EVENT_FOCUS_OUT; wayland_thread->push_message(msg); + ss->shift_pressed = false; + ss->ctrl_pressed = false; + ss->alt_pressed = false; + ss->meta_pressed = false; + + if (ss->xkb_state != nullptr) { + xkb_state_update_mask(ss->xkb_state, 0, 0, 0, 0, 0, 0); + } + DEBUG_LOG_WAYLAND_THREAD(vformat("Keyboard unfocused window %d.", ws->id)); } @@ -2177,14 +2214,21 @@ void WaylandThread::_wl_keyboard_on_modifiers(void *data, struct wl_keyboard *wl 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->mods_depressed = mods_depressed; + ss->mods_latched = mods_latched; + ss->mods_locked = mods_locked; ss->current_layout_index = group; + + if (ss->xkb_state == nullptr) { + return; + } + + xkb_state_update_mask(ss->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); + + ss->shift_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE); + ss->ctrl_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE); + ss->alt_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE); + ss->meta_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE); } void WaylandThread::_wl_keyboard_on_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) { @@ -3036,15 +3080,82 @@ void WaylandThread::_xdg_activation_token_on_done(void *data, struct xdg_activat DEBUG_LOG_WAYLAND_THREAD(vformat("Received activation token and requested window activation.")); } +void WaylandThread::_godot_embedding_compositor_on_client(void *data, struct godot_embedding_compositor *godot_embedding_compositor, struct godot_embedded_client *godot_embedded_client, int32_t pid) { + EmbeddingCompositorState *state = (EmbeddingCompositorState *)data; + ERR_FAIL_NULL(state); + + EmbeddedClientState *client_state = memnew(EmbeddedClientState); + client_state->embedding_compositor = godot_embedding_compositor; + client_state->pid = pid; + godot_embedded_client_add_listener(godot_embedded_client, &godot_embedded_client_listener, client_state); + + DEBUG_LOG_WAYLAND_THREAD(vformat("New client %d.", pid)); + state->clients.push_back(godot_embedded_client); +} + +void WaylandThread::_godot_embedded_client_on_disconnected(void *data, struct godot_embedded_client *godot_embedded_client) { + EmbeddedClientState *state = (EmbeddedClientState *)data; + ERR_FAIL_NULL(state); + + EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(state->embedding_compositor); + ERR_FAIL_NULL(ecomp_state); + + ecomp_state->clients.erase_unordered(godot_embedded_client); + ecomp_state->mapped_clients.erase(state->pid); + + memfree(state); + godot_embedded_client_destroy(godot_embedded_client); + + DEBUG_LOG_WAYLAND_THREAD(vformat("Client %d disconnected.", state->pid)); +} + +void WaylandThread::_godot_embedded_client_on_window_embedded(void *data, struct godot_embedded_client *godot_embedded_client) { + EmbeddedClientState *state = (EmbeddedClientState *)data; + ERR_FAIL_NULL(state); + + EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(state->embedding_compositor); + ERR_FAIL_NULL(ecomp_state); + + state->window_mapped = true; + + ERR_FAIL_COND_MSG(ecomp_state->mapped_clients.has(state->pid), "More than one Wayland client per PID tried to create a window."); + + ecomp_state->mapped_clients[state->pid] = godot_embedded_client; +} + +void WaylandThread::_godot_embedded_client_on_window_focus_in(void *data, struct godot_embedded_client *godot_embedded_client) { + EmbeddedClientState *state = (EmbeddedClientState *)data; + ERR_FAIL_NULL(state); + + EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(state->embedding_compositor); + ERR_FAIL_NULL(ecomp_state); + + ecomp_state->focused_pid = state->pid; + DEBUG_LOG_WAYLAND_THREAD(vformat("Embedded client pid %d focus in", state->pid)); +} + +void WaylandThread::_godot_embedded_client_on_window_focus_out(void *data, struct godot_embedded_client *godot_embedded_client) { + EmbeddedClientState *state = (EmbeddedClientState *)data; + ERR_FAIL_NULL(state); + + EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(state->embedding_compositor); + ERR_FAIL_NULL(ecomp_state); + + ecomp_state->focused_pid = -1; + DEBUG_LOG_WAYLAND_THREAD(vformat("Embedded client pid %d focus out", state->pid)); +} + // NOTE: This must be started after a valid wl_display is loaded. void WaylandThread::_poll_events_thread(void *p_data) { + Thread::set_name("Wayland Events"); + ThreadData *data = (ThreadData *)p_data; ERR_FAIL_NULL(data); ERR_FAIL_NULL(data->wl_display); - struct pollfd poll_fd; + struct pollfd poll_fd = {}; poll_fd.fd = wl_display_get_fd(data->wl_display); - poll_fd.events = POLLIN | POLLHUP; + poll_fd.events = POLLIN; while (true) { // Empty the event queue while it's full. @@ -3188,6 +3299,15 @@ WaylandThread::OfferState *WaylandThread::wp_primary_selection_offer_get_offer_s return nullptr; } +WaylandThread::EmbeddingCompositorState *WaylandThread::godot_embedding_compositor_get_state(struct godot_embedding_compositor *p_compositor) { + // NOTE: No need for tag check as it's a "fake" interface - nothing else exposes it. + if (p_compositor) { + return (EmbeddingCompositorState *)godot_embedding_compositor_get_user_data(p_compositor); + } + + return nullptr; +} + // This is implemented as a method because this is the simplest way of // accounting for dynamic output scale changes. int WaylandThread::window_state_get_preferred_buffer_scale(WindowState *p_ws) { @@ -3356,15 +3476,20 @@ void WaylandThread::seat_state_lock_pointer(SeatState *p_ss) { ERR_FAIL_NULL(p_ss); if (p_ss->wl_pointer == nullptr) { + WARN_PRINT("Can't lock - no pointer?"); return; } if (registry.wp_pointer_constraints == nullptr) { + WARN_PRINT("Can't lock - no constraints global."); return; } if (p_ss->wp_locked_pointer == nullptr) { struct wl_surface *locked_surface = window_get_wl_surface(p_ss->pointer_data.last_pointed_id); + if (locked_surface == nullptr) { + locked_surface = window_get_wl_surface(DisplayServer::MAIN_WINDOW_ID); + } ERR_FAIL_NULL(locked_surface); p_ss->wp_locked_pointer = zwp_pointer_constraints_v1_lock_pointer(registry.wp_pointer_constraints, locked_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); @@ -4445,7 +4570,52 @@ Error WaylandThread::init() { KeyMappingXKB::initialize(); - wl_display = wl_display_connect(nullptr); +#ifdef TOOLS_ENABLED + String embedder_socket_path; + + bool embedder_enabled = true; + + if (OS::get_singleton()->get_environment("GODOT_WAYLAND_DISABLE_EMBEDDER") == "1") { + print_verbose("Disabling Wayland embedder as per GODOT_WAYLAND_DISABLE_EMBEDDER."); + embedder_enabled = false; + } + + if (embedder_enabled && Engine::get_singleton()->is_editor_hint() && !Engine::get_singleton()->is_project_manager_hint()) { + print_verbose("Initializing Wayland embedder."); + Error embedder_status = embedder.init(); + ERR_FAIL_COND_V_MSG(embedder_status != OK, ERR_CANT_CREATE, "Can't initialize Wayland embedder."); + + embedder_socket_path = embedder.get_socket_path(); + ERR_FAIL_COND_V_MSG(embedder_socket_path.is_empty(), ERR_CANT_CREATE, "Wayland embedder returned invalid path."); + + OS::get_singleton()->set_environment("GODOT_WAYLAND_DISPLAY", embedder_socket_path); + + // Debug + if (OS::get_singleton()->get_environment("GODOT_DEBUG_EMBEDDER_SINGLE_INSTANCE") == "1") { + print_line("Pausing as per GODOT_DEBUG_EMBEDDER_SINGLE_INSTANCE."); + pause(); + } + } else if (Engine::get_singleton()->is_embedded_in_editor()) { + embedder_socket_path = OS::get_singleton()->get_environment("GODOT_WAYLAND_DISPLAY"); + +#if 0 + // Debug + OS::get_singleton()->set_environment("WAYLAND_DEBUG", "1"); + int fd = open("/tmp/gdembedded.log", O_CREAT | O_RDWR, 0666); + dup2(fd, 1); + dup2(fd, 2); +#endif + } + + if (embedder_socket_path.is_empty()) { + print_verbose("Connecting to the default Wayland display."); + wl_display = wl_display_connect(nullptr); + } else { + print_verbose("Connecting to the Wayland embedder display."); + wl_display = wl_display_connect(embedder_socket_path.utf8().get_data()); + } +#endif // TOOLS_ENABLED + ERR_FAIL_NULL_V_MSG(wl_display, ERR_CANT_CREATE, "Can't connect to a Wayland display."); thread_data.wl_display = wl_display; @@ -4465,12 +4635,19 @@ Error WaylandThread::init() { ERR_FAIL_NULL_V_MSG(registry.wl_compositor, ERR_UNAVAILABLE, "Can't obtain the Wayland compositor global."); ERR_FAIL_NULL_V_MSG(registry.xdg_wm_base, ERR_UNAVAILABLE, "Can't obtain the Wayland XDG shell global."); - if (!registry.xdg_decoration_manager) { + // Embedded games can't access the decoration and icon protocol. + if (!Engine::get_singleton()->is_embedded_in_editor()) { + if (!registry.xdg_decoration_manager) { #ifdef LIBDECOR_ENABLED - WARN_PRINT("Can't obtain the XDG decoration manager. Libdecor will be used for drawing CSDs, if available."); + WARN_PRINT("Can't obtain the XDG decoration manager. Libdecor will be used for drawing CSDs, if available."); #else - WARN_PRINT("Can't obtain the XDG decoration manager. Decorations won't show up."); + WARN_PRINT("Can't obtain the XDG decoration manager. Decorations won't show up."); #endif // LIBDECOR_ENABLED + } + + if (!registry.xdg_toplevel_icon_manager_name) { + WARN_PRINT("xdg-toplevel-icon protocol not found! Cannot set window icon."); + } } if (!registry.xdg_activation) { @@ -4487,10 +4664,6 @@ Error WaylandThread::init() { WARN_PRINT("FIFO protocol not found! Frame pacing will be degraded."); } - if (!registry.xdg_toplevel_icon_manager_name) { - WARN_PRINT("xdg-toplevel-icon protocol not found! Cannot set window icon."); - } - // Wait for seat capabilities. wl_display_roundtrip(wl_display); @@ -5010,6 +5183,17 @@ bool WaylandThread::is_suspended() const { return true; } +struct godot_embedding_compositor *WaylandThread::get_embedding_compositor() { + return registry.godot_embedding_compositor; +} + +OS::ProcessID WaylandThread::embedded_compositor_get_focused_pid() { + EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(registry.godot_embedding_compositor); + ERR_FAIL_NULL_V(ecomp_state, -1); + + return ecomp_state->focused_pid; +} + void WaylandThread::destroy() { if (!initialized) { return; @@ -5045,6 +5229,10 @@ void WaylandThread::destroy() { } #endif // LIBDECOR_ENABLED + if (ws.xdg_toplevel_decoration) { + zxdg_toplevel_decoration_v1_destroy(ws.xdg_toplevel_decoration); + } + if (ws.xdg_toplevel) { xdg_toplevel_destroy(ws.xdg_toplevel); } @@ -5132,6 +5320,22 @@ void WaylandThread::destroy() { wl_output_destroy(wl_output); } + if (registry.godot_embedding_compositor) { + EmbeddingCompositorState *es = godot_embedding_compositor_get_state(registry.godot_embedding_compositor); + ERR_FAIL_NULL(es); + + es->mapped_clients.clear(); + + for (struct godot_embedded_client *client : es->clients) { + godot_embedded_client_destroy(client); + } + es->clients.clear(); + + memdelete(es); + + godot_embedding_compositor_destroy(registry.godot_embedding_compositor); + } + if (wl_cursor_theme) { wl_cursor_theme_destroy(wl_cursor_theme); } @@ -5212,6 +5416,8 @@ void WaylandThread::destroy() { wl_registry_destroy(wl_registry); } + wl_display_roundtrip(wl_display); + if (wl_display) { wl_display_disconnect(wl_display); } diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h index b0add5ec89c..30c6c8022da 100644 --- a/platform/linuxbsd/wayland/wayland_thread.h +++ b/platform/linuxbsd/wayland/wayland_thread.h @@ -72,6 +72,8 @@ #include "wayland/protocol/xdg_system_bell.gen.h" #include "wayland/protocol/xdg_toplevel_icon.gen.h" +#include "wayland/protocol/godot_embedding_compositor.gen.h" + // NOTE: Deprecated. #include "wayland/protocol/xdg_foreign_v1.gen.h" @@ -86,6 +88,8 @@ #include "core/os/thread.h" #include "servers/display/display_server.h" +#include "wayland_embedder.h" + class WaylandThread { public: // Messages used for exchanging information between Godot's and Wayland's thread. @@ -228,6 +232,9 @@ public: // We're really not meant to use this one directly but we still need to know // whether it's available. uint32_t wp_fifo_manager_name = 0; + + struct godot_embedding_compositor *godot_embedding_compositor = nullptr; + uint32_t godot_embedding_compositor_name = 0; }; // General Wayland-specific states. Shouldn't be accessed directly. @@ -477,6 +484,10 @@ public: uint64_t last_repeat_start_msec = 0; uint64_t last_repeat_msec = 0; + uint32_t mods_depressed = 0; + uint32_t mods_latched = 0; + uint32_t mods_locked = 0; + bool shift_pressed = false; bool ctrl_pressed = false; bool alt_pressed = false; @@ -529,6 +540,22 @@ public: Point2i hotspot; }; + struct EmbeddingCompositorState { + LocalVector clients; + + // Only a client per PID can create a window. + HashMap mapped_clients; + + OS::ProcessID focused_pid = -1; + }; + + struct EmbeddedClientState { + struct godot_embedding_compositor *embedding_compositor = nullptr; + + uint32_t pid = 0; + bool window_mapped = false; + }; + private: struct ThreadData { SafeFlag thread_done; @@ -604,6 +631,10 @@ private: bool initialized = false; +#ifdef TOOLS_ENABLED + WaylandEmbedder embedder; +#endif + #ifdef LIBDECOR_ENABLED struct libdecor *libdecor_context = nullptr; #endif // LIBDECOR_ENABLED @@ -742,6 +773,13 @@ private: static void _xdg_activation_token_on_done(void *data, struct xdg_activation_token_v1 *xdg_activation_token, const char *token); + static void _godot_embedding_compositor_on_client(void *data, struct godot_embedding_compositor *godot_embedding_compositor, struct godot_embedded_client *godot_embedded_client, int32_t pid); + + static void _godot_embedded_client_on_disconnected(void *data, struct godot_embedded_client *godot_embedded_client); + static void _godot_embedded_client_on_window_embedded(void *data, struct godot_embedded_client *godot_embedded_client); + static void _godot_embedded_client_on_window_focus_in(void *data, struct godot_embedded_client *godot_embedded_client); + static void _godot_embedded_client_on_window_focus_out(void *data, struct godot_embedded_client *godot_embedded_client); + // Core Wayland event listeners. static constexpr struct wl_registry_listener wl_registry_listener = { .global = _wl_registry_on_global, @@ -929,6 +967,18 @@ private: .done = _xdg_activation_token_on_done, }; + // Godot interfaces. + static constexpr struct godot_embedding_compositor_listener godot_embedding_compositor_listener = { + .client = _godot_embedding_compositor_on_client, + }; + + static constexpr struct godot_embedded_client_listener godot_embedded_client_listener = { + .disconnected = _godot_embedded_client_on_disconnected, + .window_embedded = _godot_embedded_client_on_window_embedded, + .window_focus_in = _godot_embedded_client_on_window_focus_in, + .window_focus_out = _godot_embedded_client_on_window_focus_out, + }; + #ifdef LIBDECOR_ENABLED // libdecor event handlers. static void libdecor_on_error(struct libdecor *context, enum libdecor_error error, const char *message); @@ -1009,6 +1059,8 @@ public: static OfferState *wp_primary_selection_offer_get_offer_state(struct zwp_primary_selection_offer_v1 *p_offer); + static EmbeddingCompositorState *godot_embedding_compositor_get_state(struct godot_embedding_compositor *p_compositor); + void seat_state_unlock_pointer(SeatState *p_ss); void seat_state_lock_pointer(SeatState *p_ss); void seat_state_set_hint(SeatState *p_ss, int p_x, int p_y); @@ -1116,6 +1168,10 @@ public: bool window_is_suspended(DisplayServer::WindowID p_window_id) const; bool is_suspended() const; + struct godot_embedding_compositor *get_embedding_compositor(); + + OS::ProcessID embedded_compositor_get_focused_pid(); + Error init(); void destroy(); }; diff --git a/thirdparty/README.md b/thirdparty/README.md index dc7524be8d3..32574438207 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -1192,6 +1192,10 @@ Files extracted from upstream source: - `unstable/xdg-foreign/xdg-foreign-unstable-v1.xml` - `COPYING` +The following files are extracted from thirdparty sources: + +- `mesa/wayland-drm.xml`: https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-25.3.0/src/egl/wayland/wayland-drm/wayland-drm.xml + ## wslay diff --git a/thirdparty/wayland-protocols/mesa/wayland-drm.xml b/thirdparty/wayland-protocols/mesa/wayland-drm.xml new file mode 100644 index 00000000000..eaf2654ab26 --- /dev/null +++ b/thirdparty/wayland-protocols/mesa/wayland-drm.xml @@ -0,0 +1,189 @@ + + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2011 Intel Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that\n the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bitmask of capabilities. + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/wayland-protocols/stable/linux-dmabuf/README b/thirdparty/wayland-protocols/stable/linux-dmabuf/README new file mode 100644 index 00000000000..cdedf98a5a8 --- /dev/null +++ b/thirdparty/wayland-protocols/stable/linux-dmabuf/README @@ -0,0 +1,5 @@ +Linux DMA-BUF protocol + +Maintainers: +Pekka Paalanen +Daniel Stone diff --git a/thirdparty/wayland-protocols/stable/linux-dmabuf/feedback.rst b/thirdparty/wayland-protocols/stable/linux-dmabuf/feedback.rst new file mode 100644 index 00000000000..a3f94ed456d --- /dev/null +++ b/thirdparty/wayland-protocols/stable/linux-dmabuf/feedback.rst @@ -0,0 +1,218 @@ +.. Copyright 2021 Simon Ser + +.. contents:: + + +linux-dmabuf feedback introduction +================================== + +linux-dmabuf feedback allows compositors and clients to negotiate optimal buffer +allocation parameters. This document will assume that the compositor is using a +rendering API such as OpenGL or Vulkan and KMS as the presentation API: even if +linux-dmabuf feedback isn't restricted to this use-case, it's the most common. + +linux-dmabuf feedback introduces the following concepts: + +1. A main device. This is the render device that the compositor is using to + perform composition. Compositors should always be able to display a buffer + submitted by a client, so this device can be used as a fallback in case none + of the more optimized code-paths work. Clients should allocate buffers such + that they can be imported and textured from the main device. + +2. One or more tranches. Each tranche consists of a target device, allocation + flags and a set of format/modifier pairs. A tranche can be seen as a set of + formats/modifier pairs that are compatible with the target device. + + A tranche can have the ``scanout`` flag. It means that the target device is + a KMS device, and that buffers allocated with one of the format/modifier + pairs in the tranche are eligible for direct scanout. + + Clients should use the tranches in order to allocate buffers with the most + appropriate format/modifier and also to avoid allocating in private device + memory when cross-device operations are going to happen. + +linux-dmabuf feedback implementation notes +========================================== + +This section contains recommendations for client and compositor implementations. + +For clients +----------- + +Clients are expected to either pick a fixed DRM format beforehand, or +perform the following steps repeatedly until they find a suitable format. + +Basic clients may only support static buffer allocation on startup. These +clients should do the following: + +1. Send a ``get_default_feedback`` request to get global feedback. +2. Select the device indicated by ``main_device`` for allocation. +3. For each tranche: + + 1. If ``tranche_target_device`` doesn't match the allocation device, ignore + the tranche. + 2. Accumulate allocation flags from ``tranche_flags``. + 3. Accumulate format/modifier pairs received via ``tranche_formats`` in a + list. + 4. When the ``tranche_done`` event is received, try to allocate the buffer + with the accumulated list of modifiers and allocation flags. If that + fails, proceed with the next tranche. If that succeeds, stop the loop. + +4. Destroy the feedback object. + +Tranches are ordered by preference: the more optimized tranches come first. As +such, clients should use the first tranche that happens to work. + +Some clients may have already selected the device they want to use beforehand. +These clients can ignore the ``main_device`` event, and ignore tranches whose +``tranche_target_device`` doesn't match the selected device. Such clients need +to be prepared for the ``wp_linux_buffer_params.create`` request to potentially +fail. + +If the client allocates a buffer without specifying explicit modifiers on a +device different from the one indicated by ``main_device``, then the client +must force a linear layout. + +Some clients might support re-negotiating the buffer format/modifier on the +fly. These clients should send a ``get_surface_feedback`` request and keep the +feedback object alive after the initial allocation. Each time a new set of +feedback parameters is received (ended by the ``done`` event), they should +perform the same steps as basic clients described above. They should detect +when the optimal allocation parameters didn't change (same +format/modifier/flags) to avoid needlessly re-allocating their buffers. + +Some clients might additionally support switching the device used for +allocations on the fly. Such clients should send a ``get_surface_feedback`` +request. For each tranche, select the device indicated by +``tranche_target_device`` for allocation. Accumulate allocation flags (received +via ``tranche_flags``) and format/modifier pairs (received via +``tranche_formats``) as usual. When the ``tranche_done`` event is received, try +to allocate the buffer with the accumulated list of modifiers and the +allocation flags. Try to import the resulting buffer by sending a +``wp_linux_buffer_params.create`` request (this might fail). Repeat with each +tranche until an allocation and import succeeds. Each time a new set of +feedback parameters is received, they should perform these steps again. They +should detect when the optimal allocation parameters didn't change (same +device/format/modifier/flags) to avoid needlessly re-allocating their buffers. + +For compositors +--------------- + +Basic compositors may only support texturing the DMA-BUFs via a rendering API +such as OpenGL or Vulkan. Such compositors can send a single tranche as a reply +to both ``get_default_feedback`` and ``get_surface_feedback``. Set the +``main_device`` to the rendering device. Send the tranche with +``tranche_target_device`` set to the rendering device and all of the DRM +format/modifier pairs supported by the rendering API. Do not set the +``scanout`` flag in the ``tranche_flags`` event. + +Some compositors may support direct scan-out for full-screen surfaces. These +compositors can re-send the feedback parameters when a surface becomes +full-screen or leaves full-screen mode if the client has used the +``get_surface_feedback`` request. The non-full-screen feedback parameters are +the same as basic compositors described above. The full-screen feedback +parameters have two tranches: one with the format/modifier pairs supported by +the KMS plane, with the ``scanout`` flag set in the ``tranche_flags`` event and +with ``tranche_target_device`` set to the KMS scan-out device; the other with +the rest of the format/modifier pairs (supported for texturing, but not for +scan-out), without the ``scanout`` flag set in the ``tranche_flags`` event, and +with the ``tranche_target_device`` set to the rendering device. + +Some compositors may support direct scan-out for all surfaces. These +compositors can send two tranches for surfaces that become candidates for +direct scan-out, similarly to compositors supporting direct scan-out for +fullscreen surfaces. When a surface stops being a candidate for direct +scan-out, compositors should re-send the feedback parameters optimized for +texturing only. The way candidates for direct scan-out are selected is +compositor policy, a possible implementation is to select as many surfaces as +there are available hardware planes, starting from surfaces closer to the eye. + +Some compositors may support multiple devices at the same time. If the +compositor supports rendering with a fixed device and direct scan-out on a +secondary device, it may send a separate tranche for surfaces displayed on +the secondary device that are candidates for direct scan-out. The +``tranche_target_device`` for this tranche will be the secondary device and +will not match the ``main_device``. + +Some compositors may support switching their rendering device at runtime or +changing their rendering device depending on the surface. When the rendering +device changes for a surface, such compositors may re-send the feedback +parameters with a different ``main_device``. However there is a risk that +clients don't support switching their device at runtime and continue using the +previous device. For this reason, compositors should always have a fallback +rendering device that they initially send as ``main_device``, such that these +clients use said fallback device. + +Compositors should not change the ``main_device`` on-the-fly when explicit +modifiers are not supported, because there's a risk of importing buffers +with an implicit non-linear modifier as a linear buffer, resulting in +misinterpreted buffer contents. + +Compositors should not send feedback parameters if they don't have a fallback +path. For instance, compositors shouldn't send a format/modifier supported for +direct scan-out but not supported by the rendering API for texturing. + +Compositors can decide to use multiple tranches to describe the allocation +parameters optimized for texturing. For example, if there are formats which +have a fast texturing path and formats which have a slower texturing path, the +compositor can decide to expose two separate tranches. + +Compositors can decide to use intermediate tranches to describe code-paths +slower than direct scan-out but faster than texturing. For instance, a +compositor could insert an intermediate tranche if it's possible to use a +mem2mem device to convert buffers to be able to use scan-out. + +``dev_t`` encoding +================== + +The protocol carries ``dev_t`` values on the wire using arrays. A compositor +written in C can encode the values as follows: + +.. code-block:: c + + struct stat drm_node_stat; + struct wl_array dev_array = { + .size = sizeof(drm_node_stat.st_rdev), + .data = &drm_node_stat.st_rdev, + }; + +A client can decode the values as follows: + +.. code-block:: c + + dev_t dev; + assert(dev_array->size == sizeof(dev)); + memcpy(&dev, dev_array->data, sizeof(dev)); + +Because two DRM nodes can refer to the same DRM device while having different +``dev_t`` values, clients should use ``drmDevicesEqual`` to compare two +devices. + +``format_table`` encoding +========================= + +The ``format_table`` event carries a file descriptor containing a list of +format + modifier pairs. The list is an array of pairs which can be accessed +with this C structure definition: + +.. code-block:: c + + struct dmabuf_format_modifier { + uint32_t format; + uint32_t pad; /* unused */ + uint64_t modifier; + }; + +Integration with other APIs +=========================== + +- libdrm: ``drmGetDeviceFromDevId`` returns a ``drmDevice`` from a device ID. +- EGL: the `EGL_EXT_device_drm_render_node`_ extension may be used to query the + DRM device render node used by a given EGL display. When unavailable, the + older `EGL_EXT_device_drm`_ extension may be used as a fallback. +- Vulkan: the `VK_EXT_physical_device_drm`_ extension may be used to query the + DRM device used by a given ``VkPhysicalDevice``. + +.. _EGL_EXT_device_drm: https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_drm.txt +.. _EGL_EXT_device_drm_render_node: https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_drm_render_node.txt +.. _VK_EXT_physical_device_drm: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_EXT_physical_device_drm.html diff --git a/thirdparty/wayland-protocols/stable/linux-dmabuf/linux-dmabuf-v1.xml b/thirdparty/wayland-protocols/stable/linux-dmabuf/linux-dmabuf-v1.xml new file mode 100644 index 00000000000..38e06f5b6e3 --- /dev/null +++ b/thirdparty/wayland-protocols/stable/linux-dmabuf/linux-dmabuf-v1.xml @@ -0,0 +1,585 @@ + + + + + Copyright © 2014, 2015 Collabora, Ltd. + + 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 (including the next + paragraph) 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. + + + + + Following the interfaces from: + https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt + https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt + and the Linux DRM sub-system's AddFb2 ioctl. + + This interface offers ways to create generic dmabuf-based wl_buffers. + + Clients can use the get_surface_feedback request to get dmabuf feedback + for a particular surface. If the client wants to retrieve feedback not + tied to a surface, they can use the get_default_feedback request. + + The following are required from clients: + + - Clients must ensure that either all data in the dma-buf is + coherent for all subsequent read access or that coherency is + correctly handled by the underlying kernel-side dma-buf + implementation. + + - Don't make any more attachments after sending the buffer to the + compositor. Making more attachments later increases the risk of + the compositor not being able to use (re-import) an existing + dmabuf-based wl_buffer. + + The underlying graphics stack must ensure the following: + + - The dmabuf file descriptors relayed to the server will stay valid + for the whole lifetime of the wl_buffer. This means the server may + at any time use those fds to import the dmabuf into any kernel + sub-system that might accept it. + + However, when the underlying graphics stack fails to deliver the + promise, because of e.g. a device hot-unplug which raises internal + errors, after the wl_buffer has been successfully created the + compositor must not raise protocol errors to the client when dmabuf + import later fails. + + To create a wl_buffer from one or more dmabufs, a client creates a + zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params + request. All planes required by the intended format are added with + the 'add' request. Finally, a 'create' or 'create_immed' request is + issued, which has the following outcome depending on the import success. + + The 'create' request, + - on success, triggers a 'created' event which provides the final + wl_buffer to the client. + - on failure, triggers a 'failed' event to convey that the server + cannot use the dmabufs received from the client. + + For the 'create_immed' request, + - on success, the server immediately imports the added dmabufs to + create a wl_buffer. No event is sent from the server in this case. + - on failure, the server can choose to either: + - terminate the client by raising a fatal error. + - mark the wl_buffer as failed, and send a 'failed' event to the + client. If the client uses a failed wl_buffer as an argument to any + request, the behaviour is compositor implementation-defined. + + For all DRM formats and unless specified in another protocol extension, + pre-multiplied alpha is used for pixel values. + + Unless specified otherwise in another protocol extension, implicit + synchronization is used. In other words, compositors and clients must + wait and signal fences implicitly passed via the DMA-BUF's reservation + mechanism. + + + + + Objects created through this interface, especially wl_buffers, will + remain valid. + + + + + + This temporary object is used to collect multiple dmabuf handles into + a single batch to create a wl_buffer. It can only be used once and + should be destroyed after a 'created' or 'failed' event has been + received. + + + + + + + This event advertises one buffer format that the server supports. + All the supported formats are advertised once when the client + binds to this interface. A roundtrip after binding guarantees + that the client has received all supported formats. + + For the definition of the format codes, see the + zwp_linux_buffer_params_v1::create request. + + Starting version 4, the format event is deprecated and must not be + sent by compositors. Instead, use get_default_feedback or + get_surface_feedback. + + + + + + + This event advertises the formats that the server supports, along with + the modifiers supported for each format. All the supported modifiers + for all the supported formats are advertised once when the client + binds to this interface. A roundtrip after binding guarantees that + the client has received all supported format-modifier pairs. + + For legacy support, DRM_FORMAT_MOD_INVALID (that is, modifier_hi == + 0x00ffffff and modifier_lo == 0xffffffff) is allowed in this event. + It indicates that the server can support the format with an implicit + modifier. When a plane has DRM_FORMAT_MOD_INVALID as its modifier, it + is as if no explicit modifier is specified. The effective modifier + will be derived from the dmabuf. + + A compositor that sends valid modifiers and DRM_FORMAT_MOD_INVALID for + a given format supports both explicit modifiers and implicit modifiers. + + For the definition of the format and modifier codes, see the + zwp_linux_buffer_params_v1::create and zwp_linux_buffer_params_v1::add + requests. + + Starting version 4, the modifier event is deprecated and must not be + sent by compositors. Instead, use get_default_feedback or + get_surface_feedback. + + + + + + + + + + + This request creates a new wp_linux_dmabuf_feedback object not bound + to a particular surface. This object will deliver feedback about dmabuf + parameters to use if the client doesn't support per-surface feedback + (see get_surface_feedback). + + + + + + + This request creates a new wp_linux_dmabuf_feedback object for the + specified wl_surface. This object will deliver feedback about dmabuf + parameters to use for buffers attached to this surface. + + If the surface is destroyed before the wp_linux_dmabuf_feedback object, + the feedback object becomes inert. + + + + + + + + + This temporary object is a collection of dmabufs and other + parameters that together form a single logical buffer. The temporary + object may eventually create one wl_buffer unless cancelled by + destroying it before requesting 'create'. + + Single-planar formats only require one dmabuf, however + multi-planar formats may require more than one dmabuf. For all + formats, an 'add' request must be called once per plane (even if the + underlying dmabuf fd is identical). + + You must use consecutive plane indices ('plane_idx' argument for 'add') + from zero to the number of planes used by the drm_fourcc format code. + All planes required by the format must be given exactly once, but can + be given in any order. Each plane index can be set only once. + + + + + + + + + + + + + + + + Cleans up the temporary data sent to the server for dmabuf-based + wl_buffer creation. + + + + + + This request adds one dmabuf to the set in this + zwp_linux_buffer_params_v1. + + The 64-bit unsigned value combined from modifier_hi and modifier_lo + is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the + fb modifier, which is defined in drm_mode.h of Linux UAPI. + This is an opaque token. Drivers use this token to express tiling, + compression, etc. driver-specific modifications to the base format + defined by the DRM fourcc code. + + Starting from version 4, the invalid_format protocol error is sent if + the format + modifier pair was not advertised as supported. + + Starting from version 5, the invalid_format protocol error is sent if + all planes don't use the same modifier. + + This request raises the PLANE_IDX error if plane_idx is too large. + The error PLANE_SET is raised if attempting to set a plane that + was already set. + + + + + + + + + + + + + + + + + + This asks for creation of a wl_buffer from the added dmabuf + buffers. The wl_buffer is not created immediately but returned via + the 'created' event if the dmabuf sharing succeeds. The sharing + may fail at runtime for reasons a client cannot predict, in + which case the 'failed' event is triggered. + + The 'format' argument is a DRM_FORMAT code, as defined by the + libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the + authoritative source on how the format codes should work. + + The 'flags' is a bitfield of the flags defined in enum "flags". + 'y_invert' means the that the image needs to be y-flipped. + + Flag 'interlaced' means that the frame in the buffer is not + progressive as usual, but interlaced. An interlaced buffer as + supported here must always contain both top and bottom fields. + The top field always begins on the first pixel row. The temporal + ordering between the two fields is top field first, unless + 'bottom_first' is specified. It is undefined whether 'bottom_first' + is ignored if 'interlaced' is not set. + + This protocol does not convey any information about field rate, + duration, or timing, other than the relative ordering between the + two fields in one buffer. A compositor may have to estimate the + intended field rate from the incoming buffer rate. It is undefined + whether the time of receiving wl_surface.commit with a new buffer + attached, applying the wl_surface state, wl_surface.frame callback + trigger, presentation, or any other point in the compositor cycle + is used to measure the frame or field times. There is no support + for detecting missed or late frames/fields/buffers either, and + there is no support whatsoever for cooperating with interlaced + compositor output. + + The composited image quality resulting from the use of interlaced + buffers is explicitly undefined. A compositor may use elaborate + hardware features or software to deinterlace and create progressive + output frames from a sequence of interlaced input buffers, or it + may produce substandard image quality. However, compositors that + cannot guarantee reasonable image quality in all cases are recommended + to just reject all interlaced buffers. + + Any argument errors, including non-positive width or height, + mismatch between the number of planes and the format, bad + format, bad offset or stride, may be indicated by fatal protocol + errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, + OUT_OF_BOUNDS. + + Dmabuf import errors in the server that are not obvious client + bugs are returned via the 'failed' event as non-fatal. This + allows attempting dmabuf sharing and falling back in the client + if it fails. + + This request can be sent only once in the object's lifetime, after + which the only legal request is destroy. This object should be + destroyed after issuing a 'create' request. Attempting to use this + object after issuing 'create' raises ALREADY_USED protocol error. + + It is not mandatory to issue 'create'. If a client wants to + cancel the buffer creation, it can just destroy this object. + + + + + + + + + + This event indicates that the attempted buffer creation was + successful. It provides the new wl_buffer referencing the dmabuf(s). + + Upon receiving this event, the client should destroy the + zwp_linux_buffer_params_v1 object. + + + + + + + This event indicates that the attempted buffer creation has + failed. It usually means that one of the dmabuf constraints + has not been fulfilled. + + Upon receiving this event, the client should destroy the + zwp_linux_buffer_params_v1 object. + + + + + + This asks for immediate creation of a wl_buffer by importing the + added dmabufs. + + In case of import success, no event is sent from the server, and the + wl_buffer is ready to be used by the client. + + Upon import failure, either of the following may happen, as seen fit + by the implementation: + - the client is terminated with one of the following fatal protocol + errors: + - INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS, + in case of argument errors such as mismatch between the number + of planes and the format, bad format, non-positive width or + height, or bad offset or stride. + - INVALID_WL_BUFFER, in case the cause for failure is unknown or + plaform specific. + - the server creates an invalid wl_buffer, marks it as failed and + sends a 'failed' event to the client. The result of using this + invalid wl_buffer as an argument in any request by the client is + defined by the compositor implementation. + + This takes the same arguments as a 'create' request, and obeys the + same restrictions. + + + + + + + + + + + + This object advertises dmabuf parameters feedback. This includes the + preferred devices and the supported formats/modifiers. + + The parameters are sent once when this object is created and whenever they + change. The done event is always sent once after all parameters have been + sent. When a single parameter changes, all parameters are re-sent by the + compositor. + + Compositors can re-send the parameters when the current client buffer + allocations are sub-optimal. Compositors should not re-send the + parameters if re-allocating the buffers would not result in a more optimal + configuration. In particular, compositors should avoid sending the exact + same parameters multiple times in a row. + + The tranche_target_device and tranche_formats events are grouped by + tranches of preference. For each tranche, a tranche_target_device, one + tranche_flags and one or more tranche_formats events are sent, followed + by a tranche_done event finishing the list. The tranches are sent in + descending order of preference. All formats and modifiers in the same + tranche have the same preference. + + To send parameters, the compositor sends one main_device event, tranches + (each consisting of one tranche_target_device event, one tranche_flags + event, tranche_formats events and then a tranche_done event), then one + done event. + + + + + Using this request a client can tell the server that it is not going to + use the wp_linux_dmabuf_feedback object anymore. + + + + + + This event is sent after all parameters of a wp_linux_dmabuf_feedback + object have been sent. + + This allows changes to the wp_linux_dmabuf_feedback parameters to be + seen as atomic, even if they happen via multiple events. + + + + + + This event provides a file descriptor which can be memory-mapped to + access the format and modifier table. + + The table contains a tightly packed array of consecutive format + + modifier pairs. Each pair is 16 bytes wide. It contains a format as a + 32-bit unsigned integer, followed by 4 bytes of unused padding, and a + modifier as a 64-bit unsigned integer. The native endianness is used. + + The client must map the file descriptor in read-only private mode. + + Compositors are not allowed to mutate the table file contents once this + event has been sent. Instead, compositors must create a new, separate + table file and re-send feedback parameters. Compositors are allowed to + store duplicate format + modifier pairs in the table. + + + + + + + + This event advertises the main device that the server prefers to use + when direct scan-out to the target device isn't possible. The + advertised main device may be different for each + wp_linux_dmabuf_feedback object, and may change over time. + + There is exactly one main device. The compositor must send at least + one preference tranche with tranche_target_device equal to main_device. + + Clients need to create buffers that the main device can import and + read from, otherwise creating the dmabuf wl_buffer will fail (see the + wp_linux_buffer_params.create and create_immed requests for details). + The main device will also likely be kept active by the compositor, + so clients can use it instead of waking up another device for power + savings. + + In general the device is a DRM node. The DRM node type (primary vs. + render) is unspecified. Clients must not rely on the compositor sending + a particular node type. Clients cannot check two devices for equality + by comparing the dev_t value. + + If explicit modifiers are not supported and the client performs buffer + allocations on a different device than the main device, then the client + must force the buffer to have a linear layout. + + + + + + + This event splits tranche_target_device and tranche_formats events in + preference tranches. It is sent after a set of tranche_target_device + and tranche_formats events; it represents the end of a tranche. The + next tranche will have a lower preference. + + + + + + This event advertises the target device that the server prefers to use + for a buffer created given this tranche. The advertised target device + may be different for each preference tranche, and may change over time. + + There is exactly one target device per tranche. + + The target device may be a scan-out device, for example if the + compositor prefers to directly scan-out a buffer created given this + tranche. The target device may be a rendering device, for example if + the compositor prefers to texture from said buffer. + + The client can use this hint to allocate the buffer in a way that makes + it accessible from the target device, ideally directly. The buffer must + still be accessible from the main device, either through direct import + or through a potentially more expensive fallback path. If the buffer + can't be directly imported from the main device then clients must be + prepared for the compositor changing the tranche priority or making + wl_buffer creation fail (see the wp_linux_buffer_params.create and + create_immed requests for details). + + If the device is a DRM node, the DRM node type (primary vs. render) is + unspecified. Clients must not rely on the compositor sending a + particular node type. Clients cannot check two devices for equality by + comparing the dev_t value. + + This event is tied to a preference tranche, see the tranche_done event. + + + + + + + This event advertises the format + modifier combinations that the + compositor supports. + + It carries an array of indices, each referring to a format + modifier + pair in the last received format table (see the format_table event). + Each index is a 16-bit unsigned integer in native endianness. + + For legacy support, DRM_FORMAT_MOD_INVALID is an allowed modifier. + It indicates that the server can support the format with an implicit + modifier. When a buffer has DRM_FORMAT_MOD_INVALID as its modifier, it + is as if no explicit modifier is specified. The effective modifier + will be derived from the dmabuf. + + A compositor that sends valid modifiers and DRM_FORMAT_MOD_INVALID for + a given format supports both explicit modifiers and implicit modifiers. + + Compositors must not send duplicate format + modifier pairs within the + same tranche or across two different tranches with the same target + device and flags. + + This event is tied to a preference tranche, see the tranche_done event. + + For the definition of the format and modifier codes, see the + wp_linux_buffer_params.create request. + + + + + + + + + + + This event sets tranche-specific flags. + + The scanout flag is a hint that direct scan-out may be attempted by the + compositor on the target device if the client appropriately allocates a + buffer. How to allocate a buffer that can be scanned out on the target + device is implementation-defined. + + This event is tied to a preference tranche, see the tranche_done event. + + + + + + diff --git a/thirdparty/wayland-protocols/staging/commit-timing/README b/thirdparty/wayland-protocols/staging/commit-timing/README new file mode 100644 index 00000000000..4f130a72c16 --- /dev/null +++ b/thirdparty/wayland-protocols/staging/commit-timing/README @@ -0,0 +1,4 @@ +Commit Timing Protocol + +Maintainers: +Derek Foreman (@derekf) diff --git a/thirdparty/wayland-protocols/staging/commit-timing/commit-timing-v1.xml b/thirdparty/wayland-protocols/staging/commit-timing/commit-timing-v1.xml new file mode 100644 index 00000000000..cc42ea6961b --- /dev/null +++ b/thirdparty/wayland-protocols/staging/commit-timing/commit-timing-v1.xml @@ -0,0 +1,124 @@ + + + + + Copyright © 2023 Valve Corporation + + 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 (including the next + paragraph) 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. + + + + + When a compositor latches on to new content updates it will check for + any number of requirements of the available content updates (such as + fences of all buffers being signalled) to consider the update ready. + + This protocol provides a method for adding a time constraint to surface + content. This constraint indicates to the compositor that a content + update should be presented as closely as possible to, but not before, + a specified time. + + This protocol does not change the Wayland property that content + updates are applied in the order they are received, even when some + content updates contain timestamps and others do not. + + To provide timestamps, this global factory interface must be used to + acquire a wp_commit_timing_v1 object for a surface, which may then be + used to provide timestamp information for commits. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + + + + Informs the server that the client will no longer be using + this protocol object. Existing objects created by this object + are not affected. + + + + + + Establish a timing controller for a surface. + + Only one commit timer can be created for a surface, or a + commit_timer_exists protocol error will be generated. + + + + + + + + + An object to set a time constraint for a content update on a surface. + + + + + + + + + + + Provide a timing constraint for a surface content update. + + A set_timestamp request may be made before a wl_surface.commit to + tell the compositor that the content is intended to be presented + as closely as possible to, but not before, the specified time. + The time is in the domain of the compositor's presentation clock. + + An invalid_timestamp error will be generated for invalid tv_nsec. + + If a timestamp already exists on the surface, a timestamp_exists + error is generated. + + Requesting set_timestamp after the commit_timer object's surface is + destroyed will generate a "surface_destroyed" error. + + + + + + + + + Informs the server that the client will no longer be using + this protocol object. + + Existing timing constraints are not affected by the destruction. + + + + diff --git a/thirdparty/wayland-protocols/staging/fifo/README b/thirdparty/wayland-protocols/staging/fifo/README new file mode 100644 index 00000000000..01ac61f3da0 --- /dev/null +++ b/thirdparty/wayland-protocols/staging/fifo/README @@ -0,0 +1,4 @@ +Fifo Protocol + +Maintainers: +Derek Foreman (@derekf) diff --git a/thirdparty/wayland-protocols/staging/fifo/fifo-v1.xml b/thirdparty/wayland-protocols/staging/fifo/fifo-v1.xml new file mode 100644 index 00000000000..3255929ad0d --- /dev/null +++ b/thirdparty/wayland-protocols/staging/fifo/fifo-v1.xml @@ -0,0 +1,143 @@ + + + + Copyright © 2023 Valve Corporation + + 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 (including the next + paragraph) 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. + + + + + When a Wayland compositor considers applying a content update, + it must ensure all the update's readiness constraints (fences, etc) + are met. + + This protocol provides a way to use the completion of a display refresh + cycle as an additional readiness constraint. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + These fatal protocol errors may be emitted in response to + illegal requests. + + + + + + + Informs the server that the client will no longer be using + this protocol object. Existing objects created by this object + are not affected. + + + + + + Establish a fifo object for a surface that may be used to add + display refresh constraints to content updates. + + Only one such object may exist for a surface and attempting + to create more than one will result in an already_exists + protocol error. If a surface is acted on by multiple software + components, general best practice is that only the component + performing wl_surface.attach operations should use this protocol. + + + + + + + + + A fifo object for a surface that may be used to add + display refresh constraints to content updates. + + + + + These fatal protocol errors may be emitted in response to + illegal requests. + + + + + + + When the content update containing the "set_barrier" is applied, + it sets a "fifo_barrier" condition on the surface associated with + the fifo object. The condition is cleared immediately after the + following latching deadline for non-tearing presentation. + + The compositor may clear the condition early if it must do so to + ensure client forward progress assumptions. + + To wait for this condition to clear, use the "wait_barrier" request. + + "set_barrier" is double-buffered state, see wl_surface.commit. + + Requesting set_barrier after the fifo object's surface is + destroyed will generate a "surface_destroyed" error. + + + + + + Indicate that this content update is not ready while a + "fifo_barrier" condition is present on the surface. + + This means that when the content update containing "set_barrier" + was made active at a latching deadline, it will be active for + at least one refresh cycle. A content update which is allowed to + tear might become active after a latching deadline if no content + update became active at the deadline. + + The constraint must be ignored if the surface is a subsurface in + synchronized mode. If the surface is not being updated by the + compositor (off-screen, occluded) the compositor may ignore the + constraint. Clients must use an additional mechanism such as + frame callbacks or timestamps to ensure throttling occurs under + all conditions. + + "wait_barrier" is double-buffered state, see wl_surface.commit. + + Requesting "wait_barrier" after the fifo object's surface is + destroyed will generate a "surface_destroyed" error. + + + + + + Informs the server that the client will no longer be using + this protocol object. + + Surface state changes previously made by this protocol are + unaffected by this object's destruction. + + + + diff --git a/thirdparty/wayland-protocols/staging/linux-drm-syncobj/README b/thirdparty/wayland-protocols/staging/linux-drm-syncobj/README new file mode 100644 index 00000000000..25d6b93e710 --- /dev/null +++ b/thirdparty/wayland-protocols/staging/linux-drm-syncobj/README @@ -0,0 +1,4 @@ +Linux DRM syncobj protocol + +Maintainers: +Simon Ser (@emersion) diff --git a/thirdparty/wayland-protocols/staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml b/thirdparty/wayland-protocols/staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml new file mode 100644 index 00000000000..2c491eaf43a --- /dev/null +++ b/thirdparty/wayland-protocols/staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml @@ -0,0 +1,261 @@ + + + + Copyright 2016 The Chromium Authors. + Copyright 2017 Intel Corporation + Copyright 2018 Collabora, Ltd + Copyright 2021 Simon Ser + + 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 (including the next + paragraph) 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. + + + + This protocol allows clients to request explicit synchronization for + buffers. It is tied to the Linux DRM synchronization object framework. + + Synchronization refers to co-ordination of pipelined operations performed + on buffers. Most GPU clients will schedule an asynchronous operation to + render to the buffer, then immediately send the buffer to the compositor + to be attached to a surface. + + With implicit synchronization, ensuring that the rendering operation is + complete before the compositor displays the buffer is an implementation + detail handled by either the kernel or userspace graphics driver. + + By contrast, with explicit synchronization, DRM synchronization object + timeline points mark when the asynchronous operations are complete. When + submitting a buffer, the client provides a timeline point which will be + waited on before the compositor accesses the buffer, and another timeline + point that the compositor will signal when it no longer needs to access the + buffer contents for the purposes of the surface commit. + + Linux DRM synchronization objects are documented at: + https://dri.freedesktop.org/docs/drm/gpu/drm-mm.html#drm-sync-objects + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + This global is a factory interface, allowing clients to request + explicit synchronization for buffers on a per-surface basis. + + See wp_linux_drm_syncobj_surface_v1 for more information. + + + + + Destroy this explicit synchronization factory object. Other objects + shall not be affected by this request. + + + + + + + + + + + Instantiate an interface extension for the given wl_surface to provide + explicit synchronization. + + If the given wl_surface already has an explicit synchronization object + associated, the surface_exists protocol error is raised. + + Graphics APIs, like EGL or Vulkan, that manage the buffer queue and + commits of a wl_surface themselves, are likely to be using this + extension internally. If a client is using such an API for a + wl_surface, it should not directly use this extension on that surface, + to avoid raising a surface_exists protocol error. + + + + + + + + Import a DRM synchronization object timeline. + + If the FD cannot be imported, the invalid_timeline error is raised. + + + + + + + + + This object represents an explicit synchronization object timeline + imported by the client to the compositor. + + + + + Destroy the synchronization object timeline. Other objects are not + affected by this request, in particular timeline points set by + set_acquire_point and set_release_point are not unset. + + + + + + + This object is an add-on interface for wl_surface to enable explicit + synchronization. + + Each surface can be associated with only one object of this interface at + any time. + + Explicit synchronization is guaranteed to be supported for buffers + created with any version of the linux-dmabuf protocol. Compositors are + free to support explicit synchronization for additional buffer types. + If at surface commit time the attached buffer does not support explicit + synchronization, an unsupported_buffer error is raised. + + As long as the wp_linux_drm_syncobj_surface_v1 object is alive, the + compositor may ignore implicit synchronization for buffers attached and + committed to the wl_surface. The delivery of wl_buffer.release events + for buffers attached to the surface becomes undefined. + + Clients must set both acquire and release points if and only if a + non-null buffer is attached in the same surface commit. See the + no_buffer, no_acquire_point and no_release_point protocol errors. + + If at surface commit time the acquire and release DRM syncobj timelines + are identical, the acquire point value must be strictly less than the + release point value, or else the conflicting_points protocol error is + raised. + + + + + Destroy this surface synchronization object. + + Any timeline point set by this object with set_acquire_point or + set_release_point since the last commit may be discarded by the + compositor. Any timeline point set by this object before the last + commit will not be affected. + + + + + + + + + + + + + + + Set the timeline point that must be signalled before the compositor may + sample from the buffer attached with wl_surface.attach. + + The 64-bit unsigned value combined from point_hi and point_lo is the + point value. + + The acquire point is double-buffered state, and will be applied on the + next wl_surface.commit request for the associated surface. Thus, it + applies only to the buffer that is attached to the surface at commit + time. + + If an acquire point has already been attached during the same commit + cycle, the new point replaces the old one. + + If the associated wl_surface was destroyed, a no_surface error is + raised. + + If at surface commit time there is a pending acquire timeline point set + but no pending buffer attached, a no_buffer error is raised. If at + surface commit time there is a pending buffer attached but no pending + acquire timeline point set, the no_acquire_point protocol error is + raised. + + + + + + + + + Set the timeline point that must be signalled by the compositor when it + has finished its usage of the buffer attached with wl_surface.attach + for the relevant commit. + + Once the timeline point is signaled, and assuming the associated buffer + is not pending release from other wl_surface.commit requests, no + additional explicit or implicit synchronization with the compositor is + required to safely re-use the buffer. + + Note that clients cannot rely on the release point being always + signaled after the acquire point: compositors may release buffers + without ever reading from them. In addition, the compositor may use + different presentation paths for different commits, which may have + different release behavior. As a result, the compositor may signal the + release points in a different order than the client committed them. + + Because signaling a timeline point also signals every previous point, + it is generally not safe to use the same timeline object for the + release points of multiple buffers. The out-of-order signaling + described above may lead to a release point being signaled before the + compositor has finished reading. To avoid this, it is strongly + recommended that each buffer should use a separate timeline for its + release points. + + The 64-bit unsigned value combined from point_hi and point_lo is the + point value. + + The release point is double-buffered state, and will be applied on the + next wl_surface.commit request for the associated surface. Thus, it + applies only to the buffer that is attached to the surface at commit + time. + + If a release point has already been attached during the same commit + cycle, the new point replaces the old one. + + If the associated wl_surface was destroyed, a no_surface error is + raised. + + If at surface commit time there is a pending release timeline point set + but no pending buffer attached, a no_buffer error is raised. If at + surface commit time there is a pending buffer attached but no pending + release timeline point set, the no_release_point protocol error is + raised. + + + + + + + diff --git a/thirdparty/wayland-protocols/staging/tearing-control/README b/thirdparty/wayland-protocols/staging/tearing-control/README new file mode 100644 index 00000000000..b221e587d00 --- /dev/null +++ b/thirdparty/wayland-protocols/staging/tearing-control/README @@ -0,0 +1,4 @@ +Tearing control protocol + +Maintainers: +Xaver Hugl (@Zamundaaa) diff --git a/thirdparty/wayland-protocols/staging/tearing-control/tearing-control-v1.xml b/thirdparty/wayland-protocols/staging/tearing-control/tearing-control-v1.xml new file mode 100644 index 00000000000..9c44fbfca5a --- /dev/null +++ b/thirdparty/wayland-protocols/staging/tearing-control/tearing-control-v1.xml @@ -0,0 +1,123 @@ + + + + Copyright © 2021 Xaver Hugl + + 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 (including the next + paragraph) 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. + + + + + For some use cases like games or drawing tablets it can make sense to + reduce latency by accepting tearing with the use of asynchronous page + flips. This global is a factory interface, allowing clients to inform + which type of presentation the content of their surfaces is suitable for. + + Graphics APIs like EGL or Vulkan, that manage the buffer queue and commits + of a wl_surface themselves, are likely to be using this extension + internally. If a client is using such an API for a wl_surface, it should + not directly use this extension on that surface, to avoid raising a + tearing_control_exists protocol error. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + Destroy this tearing control factory object. Other objects, including + wp_tearing_control_v1 objects created by this factory, are not affected + by this request. + + + + + + + + + + Instantiate an interface extension for the given wl_surface to request + asynchronous page flips for presentation. + + If the given wl_surface already has a wp_tearing_control_v1 object + associated, the tearing_control_exists protocol error is raised. + + + + + + + + + An additional interface to a wl_surface object, which allows the client + to hint to the compositor if the content on the surface is suitable for + presentation with tearing. + The default presentation hint is vsync. See presentation_hint for more + details. + + If the associated wl_surface is destroyed, this object becomes inert and + should be destroyed. + + + + + This enum provides information for if submitted frames from the client + may be presented with tearing. + + + + The content of this surface is meant to be synchronized to the + vertical blanking period. This should not result in visible tearing + and may result in a delay before a surface commit is presented. + + + + + The content of this surface is meant to be presented with minimal + latency and tearing is acceptable. + + + + + + + Set the presentation hint for the associated wl_surface. This state is + double-buffered, see wl_surface.commit. + + The compositor is free to dynamically respect or ignore this hint based + on various conditions like hardware capabilities, surface state and + user preferences. + + + + + + + Destroy this surface tearing object and revert the presentation hint to + vsync. The change will be applied on the next wl_surface.commit. + + + + + diff --git a/thirdparty/wayland-protocols/unstable/linux-explicit-synchronization/README b/thirdparty/wayland-protocols/unstable/linux-explicit-synchronization/README new file mode 100644 index 00000000000..59bcb6f05b0 --- /dev/null +++ b/thirdparty/wayland-protocols/unstable/linux-explicit-synchronization/README @@ -0,0 +1,5 @@ +Linux explicit synchronization (dma-fence) protocol + +Maintainers: +Daniel Stone +Alexandros Frantzis diff --git a/thirdparty/wayland-protocols/unstable/linux-explicit-synchronization/linux-explicit-synchronization-unstable-v1.xml b/thirdparty/wayland-protocols/unstable/linux-explicit-synchronization/linux-explicit-synchronization-unstable-v1.xml new file mode 100644 index 00000000000..ac9164183f6 --- /dev/null +++ b/thirdparty/wayland-protocols/unstable/linux-explicit-synchronization/linux-explicit-synchronization-unstable-v1.xml @@ -0,0 +1,256 @@ + + + + + Copyright 2016 The Chromium Authors. + Copyright 2017 Intel Corporation + Copyright 2018 Collabora, Ltd + + 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 (including the next + paragraph) 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. + + + + + This global is a factory interface, allowing clients to request + explicit synchronization for buffers on a per-surface basis. + + See zwp_linux_surface_synchronization_v1 for more information. + + This interface is derived from Chromium's + zcr_linux_explicit_synchronization_v1. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + Destroy this explicit synchronization factory object. Other objects, + including zwp_linux_surface_synchronization_v1 objects created by this + factory, shall not be affected by this request. + + + + + + + + + + Instantiate an interface extension for the given wl_surface to provide + explicit synchronization. + + If the given wl_surface already has an explicit synchronization object + associated, the synchronization_exists protocol error is raised. + + Graphics APIs, like EGL or Vulkan, that manage the buffer queue and + commits of a wl_surface themselves, are likely to be using this + extension internally. If a client is using such an API for a + wl_surface, it should not directly use this extension on that surface, + to avoid raising a synchronization_exists protocol error. + + + + + + + + + + This object implements per-surface explicit synchronization. + + Synchronization refers to co-ordination of pipelined operations performed + on buffers. Most GPU clients will schedule an asynchronous operation to + render to the buffer, then immediately send the buffer to the compositor + to be attached to a surface. + + In implicit synchronization, ensuring that the rendering operation is + complete before the compositor displays the buffer is an implementation + detail handled by either the kernel or userspace graphics driver. + + By contrast, in explicit synchronization, dma_fence objects mark when the + asynchronous operations are complete. When submitting a buffer, the + client provides an acquire fence which will be waited on before the + compositor accesses the buffer. The Wayland server, through a + zwp_linux_buffer_release_v1 object, will inform the client with an event + which may be accompanied by a release fence, when the compositor will no + longer access the buffer contents due to the specific commit that + requested the release event. + + Each surface can be associated with only one object of this interface at + any time. + + In version 1 of this interface, explicit synchronization is only + guaranteed to be supported for buffers created with any version of the + wp_linux_dmabuf buffer factory. Version 2 additionally guarantees + explicit synchronization support for opaque EGL buffers, which is a type + of platform specific buffers described in the EGL_WL_bind_wayland_display + extension. Compositors are free to support explicit synchronization for + additional buffer types. + + + + + Destroy this explicit synchronization object. + + Any fence set by this object with set_acquire_fence since the last + commit will be discarded by the server. Any fences set by this object + before the last commit are not affected. + + zwp_linux_buffer_release_v1 objects created by this object are not + affected by this request. + + + + + + + + + + + + + + + Set the acquire fence that must be signaled before the compositor + may sample from the buffer attached with wl_surface.attach. The fence + is a dma_fence kernel object. + + The acquire fence is double-buffered state, and will be applied on the + next wl_surface.commit request for the associated surface. Thus, it + applies only to the buffer that is attached to the surface at commit + time. + + If the provided fd is not a valid dma_fence fd, then an INVALID_FENCE + error is raised. + + If a fence has already been attached during the same commit cycle, a + DUPLICATE_FENCE error is raised. + + If the associated wl_surface was destroyed, a NO_SURFACE error is + raised. + + If at surface commit time the attached buffer does not support explicit + synchronization, an UNSUPPORTED_BUFFER error is raised. + + If at surface commit time there is no buffer attached, a NO_BUFFER + error is raised. + + + + + + + Create a listener for the release of the buffer attached by the + client with wl_surface.attach. See zwp_linux_buffer_release_v1 + documentation for more information. + + The release object is double-buffered state, and will be associated + with the buffer that is attached to the surface at wl_surface.commit + time. + + If a zwp_linux_buffer_release_v1 object has already been requested for + the surface in the same commit cycle, a DUPLICATE_RELEASE error is + raised. + + If the associated wl_surface was destroyed, a NO_SURFACE error + is raised. + + If at surface commit time there is no buffer attached, a NO_BUFFER + error is raised. + + + + + + + + This object is instantiated in response to a + zwp_linux_surface_synchronization_v1.get_release request. + + It provides an alternative to wl_buffer.release events, providing a + unique release from a single wl_surface.commit request. The release event + also supports explicit synchronization, providing a fence FD for the + client to synchronize against. + + Exactly one event, either a fenced_release or an immediate_release, will + be emitted for the wl_surface.commit request. The compositor can choose + release by release which event it uses. + + This event does not replace wl_buffer.release events; servers are still + required to send those events. + + Once a buffer release object has delivered a 'fenced_release' or an + 'immediate_release' event it is automatically destroyed. + + + + + Sent when the compositor has finalised its usage of the associated + buffer for the relevant commit, providing a dma_fence which will be + signaled when all operations by the compositor on that buffer for that + commit have finished. + + Once the fence has signaled, and assuming the associated buffer is not + pending release from other wl_surface.commit requests, no additional + explicit or implicit synchronization is required to safely reuse or + destroy the buffer. + + This event destroys the zwp_linux_buffer_release_v1 object. + + + + + + + Sent when the compositor has finalised its usage of the associated + buffer for the relevant commit, and either performed no operations + using it, or has a guarantee that all its operations on that buffer for + that commit have finished. + + Once this event is received, and assuming the associated buffer is not + pending release from other wl_surface.commit requests, no additional + explicit or implicit synchronization is required to safely reuse or + destroy the buffer. + + This event destroys the zwp_linux_buffer_release_v1 object. + + + + +