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.
This commit is contained in:
Dery Almas 2025-11-15 23:38:41 +01:00
parent ef34c3d534
commit bbf65ae72f
25 changed files with 6053 additions and 23 deletions

View file

@ -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"]:

View file

@ -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);

View file

@ -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;

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="godot_embedding_compositor">
<copyright>
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.
</copyright>
<interface name="godot_embedding_compositor" version="1">
<description summary="Main control interface for embedding compositor"/>
<event name="client">
<description summary="A new client connected to the compositor"/>
<arg name="client" type="new_id" interface="godot_embedded_client"/>
<arg name="pid" type="int"/>
</event>
</interface>
<interface name="godot_embedded_client" version="1">
<description summary="A client connected to the embedded compositor.">
Clients have only a single embedded window at a time, subject to change.
</description>
<request name="destroy" type="destructor"/>
<request name="set_embedded_window_rect">
<arg name="x" type="int"/>
<arg name="y" type="int"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
</request>
<request name="set_embedded_window_parent">
<description summary="(Re)map onto an xdg_toplevel"/>
<arg name="parent" type="object" interface="xdg_toplevel" allow-null="true"/>
</request>
<request name="focus_window"/>
<request name="embedded_window_request_close"/>
<event name="disconnected">
<description summary="The client got disconnected from the compositor">
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.
</description>
</event>
<event name="window_embedded"/>
<event name="window_focus_in"/>
<event name="window_focus_out"/>
</interface>
</protocol>

File diff suppressed because it is too large Load diff

View file

@ -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 <wayland-client-core.h>
#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 <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <poll.h>
#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<uint32_t> formats;
bool authenticated;
uint32_t capabilities;
};
struct WaylandShmGlobalData : WaylandObjectData {
LocalVector<uint32_t> formats;
};
struct Client {
struct GlobalIdInfo {
uint32_t id = INVALID_ID;
List<uint32_t>::Element *history_elem = nullptr;
GlobalIdInfo() = default;
GlobalIdInfo(uint32_t p_id, List<uint32_t>::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<uint32_t, HashSet<uint32_t>> registry_globals_instances;
HashSet<uint32_t> wl_registry_instances;
List<uint32_t> global_id_history;
AHashMap<uint32_t, GlobalIdInfo> global_ids;
AHashMap<uint32_t, uint32_t> local_ids;
// Objects with no equivalent on the real compositor.
AHashMap<uint32_t, WaylandObject> fake_objects;
// Objects which mirror events of a global object.
AHashMap<uint32_t, WaylandObject> global_instances;
uint32_t embedded_client_id = INVALID_ID;
uint32_t embedded_window_id = INVALID_ID;
List<int> 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<uint32_t> 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<uint32_t, uint32_t> 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<const struct wl_interface *> 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<struct pollfd> pollfds;
// Key is socket.
AHashMap<int, Client> clients;
Client *main_client = nullptr;
PooledList<WaylandObject> objects;
// Proxies allocated by the compositor. Their ID starts from 0xff000000.
LocalVector<WaylandObject> 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<uint32_t, uint32_t> registry_globals_names;
HashMap<uint32_t, RegistryGlobalInfo> registry_globals;
uint32_t registry_globals_counter = 0;
uint32_t godot_embedding_compositor_name = 0;
LocalVector<uint32_t> wl_seat_names;
Thread proxy_thread;
List<int> client_fds;
List<int> compositor_fds;
uint32_t serial_counter = 0;
uint32_t configure_serial_counter = 0;
uint32_t sync_callback_id = 0;
Ref<DirAccess> runtime_dir;
int lock_fd = -1;
String socket_path;
String socket_lock_path;
LocalVector<uint32_t> msg_buf;
LocalVector<uint8_t> 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<struct iovec> p_vecs, const LocalVector<int> &p_fds = LocalVector<int>());
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<union wl_argument> &p_args);
// Utility aliases.
static Error send_wayland_message(int p_socket, uint32_t p_id, uint32_t p_opcode, std::initializer_list<uint32_t> 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<union wl_argument> &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<union wl_argument> &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

View file

@ -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);
}

View file

@ -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<struct godot_embedded_client *> clients;
// Only a client per PID can create a window.
HashMap<int, struct godot_embedded_client *> 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();
};