mirror of
https://github.com/godotengine/godot.git
synced 2025-12-07 22:00:10 +00:00
Merge pull request #107435 from deralmas/wl-proxy
Wayland: Implement game embedding
This commit is contained in:
commit
688a6d02b2
25 changed files with 6053 additions and 23 deletions
|
|
@ -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"]:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
74
platform/linuxbsd/wayland/godot-embedding-compositor.xml
Normal file
74
platform/linuxbsd/wayland/godot-embedding-compositor.xml
Normal 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>
|
||||
2970
platform/linuxbsd/wayland/wayland_embedder.cpp
Normal file
2970
platform/linuxbsd/wayland/wayland_embedder.cpp
Normal file
File diff suppressed because it is too large
Load diff
630
platform/linuxbsd/wayland/wayland_embedder.h
Normal file
630
platform/linuxbsd/wayland/wayland_embedder.h
Normal 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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue