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

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